From 37726c00a1203d9cc26b47adda07660a512acc81 Mon Sep 17 00:00:00 2001 From: lony2003 Date: Tue, 4 Mar 2025 11:00:01 +0800 Subject: [PATCH 01/22] style(springboot autoconfigure): use a PROPERTY_PREFIX instead of hardcoded prefix(dapr#1225) create a public static final value PROPERTY_PREFIX in DaprClientProperties change the prefix value in the ConfigurationProperties to DaprClientProperties.PROPERTY_PREFIX Dapr Cloud Config rely on that. Signed-off-by: lony2003 --- .../boot/autoconfigure/client/DaprClientProperties.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientProperties.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientProperties.java index 135416946b..5f7256df5f 100644 --- a/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientProperties.java +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientProperties.java @@ -16,8 +16,10 @@ import io.dapr.spring.data.DaprKeyValueAdapterResolver; import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties(prefix = "dapr.client") +@ConfigurationProperties(prefix = DaprClientProperties.PROPERTY_PREFIX) public class DaprClientProperties { + public static final String PROPERTY_PREFIX = "dapr.client"; + private String httpEndpoint; private String grpcEndpoint; private Integer httpPort; From 8e8b5fb2f5a94147e2980e9d06703ef4ac8a01eb Mon Sep 17 00:00:00 2001 From: lony2003 Date: Tue, 4 Mar 2025 11:00:42 +0800 Subject: [PATCH 02/22] 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 --- dapr-spring/dapr-spring-cloudconfig/pom.xml | 26 +++ .../DaprCloudConfigAutoConfiguration.java | 33 ++++ ...ConfigPropertiesDaprConnectionDetails.java | 46 +++++ .../config/DaprCloudConfigClientManager.java | 87 ++++++++++ .../config/DaprCloudConfigProperties.java | 54 ++++++ .../DaprConfigurationConfigDataLoader.java | 123 +++++++++++++ ...nfigurationConfigDataLocationResolver.java | 164 ++++++++++++++++++ .../DaprConfigurationConfigDataResource.java | 52 ++++++ .../DaprSecretStoreConfigDataLoader.java | 124 +++++++++++++ ...SecretStoreConfigDataLocationResolver.java | 164 ++++++++++++++++++ .../DaprSecretStoreConfigDataResource.java | 52 ++++++ .../parser/DaprSecretStoreParserHandler.java | 66 +++++++ ...itional-spring-configuration-metadata.json | 18 ++ .../main/resources/META-INF/spring.factories | 7 + ...ot.autoconfigure.AutoConfiguration.imports | 1 + dapr-spring/pom.xml | 1 + 16 files changed, 1018 insertions(+) create mode 100644 dapr-spring/dapr-spring-cloudconfig/pom.xml create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/autoconfigure/DaprCloudConfigAutoConfiguration.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/CloudConfigPropertiesDaprConnectionDetails.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/dapr-spring/dapr-spring-cloudconfig/pom.xml b/dapr-spring/dapr-spring-cloudconfig/pom.xml new file mode 100644 index 0000000000..9872068012 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + io.dapr.spring + dapr-spring-parent + 0.14.0-SNAPSHOT + + + dapr-spring-cloudconfig + dapr-spring-cloudconfig + Dapr Spring Cloud Config + jar + + + + io.dapr.spring + dapr-spring-boot-autoconfigure + ${project.parent.version} + compile + + + + \ No newline at end of file diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/autoconfigure/DaprCloudConfigAutoConfiguration.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/autoconfigure/DaprCloudConfigAutoConfiguration.java new file mode 100644 index 0000000000..fa01892869 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/autoconfigure/DaprCloudConfigAutoConfiguration.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016-2024 Team Fangkehou + * + * 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.dapr.spring.boot.cloudconfig.autoconfigure; + +import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import io.dapr.client.DaprClient; + +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(DaprCloudConfigProperties.class) +@ConditionalOnProperty(name = "dapr.secretstore.enabled", matchIfMissing = true) +@ConditionalOnClass(DaprClient.class) +public class DaprCloudConfigAutoConfiguration { + +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/CloudConfigPropertiesDaprConnectionDetails.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/CloudConfigPropertiesDaprConnectionDetails.java new file mode 100644 index 0000000000..ce617966c0 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/CloudConfigPropertiesDaprConnectionDetails.java @@ -0,0 +1,46 @@ +/* + * Copyright 2024 The Dapr 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.dapr.spring.boot.cloudconfig.config; + +import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties; +import io.dapr.spring.boot.autoconfigure.client.DaprConnectionDetails; + +class CloudConfigPropertiesDaprConnectionDetails implements DaprConnectionDetails { + + private final DaprClientProperties daprClientProperties; + + public CloudConfigPropertiesDaprConnectionDetails(DaprClientProperties daprClientProperties) { + this.daprClientProperties = daprClientProperties; + } + + @Override + public String httpEndpoint() { + return this.daprClientProperties.getHttpEndpoint(); + } + + @Override + public String grpcEndpoint() { + return this.daprClientProperties.getGrpcEndpoint(); + } + + @Override + public Integer httpPort() { + return this.daprClientProperties.getHttpPort(); + } + + @Override + public Integer grpcPort() { + return this.daprClientProperties.getGrpcPort(); + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java new file mode 100644 index 0000000000..c422033105 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2016-2024 Team Fangkehou + * + * 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.dapr.spring.boot.cloudconfig.config; + +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.client.DaprPreviewClient; +import io.dapr.config.Properties; +import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties; +import io.dapr.spring.boot.autoconfigure.client.DaprConnectionDetails; + + +public class DaprCloudConfigClientManager { + + private static DaprClient daprClient; + private static DaprPreviewClient daprPreviewClient; + private final DaprCloudConfigProperties daprCloudConfigProperties; + private final DaprClientProperties daprClientConfig; + + public DaprCloudConfigClientManager(DaprCloudConfigProperties daprCloudConfigProperties, + DaprClientProperties daprClientConfig) { + this.daprCloudConfigProperties = daprCloudConfigProperties; + this.daprClientConfig = daprClientConfig; + + DaprClientBuilder daprClientBuilder = createDaprClientBuilder( + createDaprConnectionDetails(daprClientConfig) + ); + + if (DaprCloudConfigClientManager.daprClient == null) { + DaprCloudConfigClientManager.daprClient = daprClientBuilder.build(); + } + + if (DaprCloudConfigClientManager.daprPreviewClient == null) { + DaprCloudConfigClientManager.daprPreviewClient = daprClientBuilder.buildPreviewClient(); + } + } + + private DaprConnectionDetails createDaprConnectionDetails(DaprClientProperties properties) { + return new CloudConfigPropertiesDaprConnectionDetails(properties); + } + DaprClientBuilder createDaprClientBuilder(DaprConnectionDetails daprConnectionDetails) { + DaprClientBuilder builder = new DaprClientBuilder(); + if (daprConnectionDetails.httpEndpoint() != null) { + builder.withPropertyOverride(Properties.HTTP_ENDPOINT, daprConnectionDetails.httpEndpoint()); + } + if (daprConnectionDetails.grpcEndpoint() != null) { + builder.withPropertyOverride(Properties.GRPC_ENDPOINT, daprConnectionDetails.grpcEndpoint()); + } + if (daprConnectionDetails.httpPort() != null) { + builder.withPropertyOverride(Properties.HTTP_PORT, String.valueOf(daprConnectionDetails.httpPort())); + } + if (daprConnectionDetails.grpcPort() != null) { + builder.withPropertyOverride(Properties.GRPC_PORT, String.valueOf(daprConnectionDetails.grpcPort())); + } + return builder; + } + + public static DaprPreviewClient getDaprPreviewClient() { + return DaprCloudConfigClientManager.daprPreviewClient; + } + + public static DaprClient getDaprClient() { + return DaprCloudConfigClientManager.daprClient; + } + + public DaprCloudConfigProperties getDaprCloudConfigProperties() { + return daprCloudConfigProperties; + } + + public DaprClientProperties getDaprClientConfig() { + return daprClientConfig; + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java new file mode 100644 index 0000000000..5224ceeac0 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016-2024 Team Fangkehou + * + * 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.dapr.spring.boot.cloudconfig.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * The properties for creating dapr client. + */ +@ConfigurationProperties(DaprCloudConfigProperties.PROPERTY_PREFIX) +public class DaprCloudConfigProperties { + + public static final String PROPERTY_PREFIX = "dapr.cloudconfig"; + + /** + * whether enable secret store + */ + private Boolean enabled = true; + + /** + * get config timeout + */ + private Integer timeout = 2000; + + public Integer getTimeout() { + return timeout; + } + + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java new file mode 100644 index 0000000000..06d9fd5ca4 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2016-2024 Team Fangkehou + * + * 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.dapr.spring.boot.cloudconfig.configdata.config; + +import io.dapr.client.DaprClient; +import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; +import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; +import io.dapr.spring.boot.cloudconfig.parser.DaprSecretStoreParserHandler; +import org.apache.commons.logging.Log; +import org.springframework.boot.context.config.ConfigData; +import org.springframework.boot.context.config.ConfigDataLoader; +import org.springframework.boot.context.config.ConfigDataLoaderContext; +import org.springframework.boot.context.config.ConfigDataResourceNotFoundException; +import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.core.env.PropertySource; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.springframework.boot.context.config.ConfigData.Option.*; + +public class DaprConfigurationConfigDataLoader implements ConfigDataLoader { + + private final Log log; + + private DaprClient daprClient; + + private DaprCloudConfigProperties daprSecretStoreConfig; + + public DaprConfigurationConfigDataLoader(DeferredLogFactory logFactory, DaprClient daprClient, + DaprCloudConfigProperties daprSecretStoreConfig) { + this.log = logFactory.getLog(getClass()); + this.daprClient = daprClient; + this.daprSecretStoreConfig = daprSecretStoreConfig; + } + + + /** + * Load {@link ConfigData} for the given resource. + * + * @param context the loader context + * @param resource the resource to load + * @return the loaded config data or {@code null} if the location should be skipped + * @throws IOException on IO error + * @throws ConfigDataResourceNotFoundException if the resource cannot be found + */ + @Override + public ConfigData load(ConfigDataLoaderContext context, DaprConfigurationConfigDataResource resource) + throws IOException, ConfigDataResourceNotFoundException { + DaprCloudConfigClientManager daprClientSecretStoreConfigManager = + getBean(context, DaprCloudConfigClientManager.class); + + daprClient = DaprCloudConfigClientManager.getDaprClient(); + daprSecretStoreConfig = daprClientSecretStoreConfigManager.getDaprCloudConfigProperties(); + + if (resource.getSecretName() == null) { + return fetchConfig(resource.getStoreName()); + } else { + return fetchConfig(resource.getStoreName(), resource.getSecretName()); + } + } + + private ConfigData fetchConfig(String storeName) { + Mono>> secretMapMono = daprClient.getBulkSecret(storeName); + + Map> secretMap = + secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); + + if (secretMap == null) { + return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } + + List> sourceList = new ArrayList<>(); + + for (Map.Entry> entry : secretMap.entrySet()) { + sourceList.addAll(DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(entry.getKey(), + entry.getValue())); + } + + return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } + + private ConfigData fetchConfig(String storeName, String secretName) { + Mono> secretMapMono = daprClient.getSecret(storeName, secretName); + + Map secretMap = secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); + + if (secretMap == null) { + return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } + + List> sourceList = new ArrayList<>( + DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(secretName, secretMap)); + + return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } + + protected T getBean(ConfigDataLoaderContext context, Class type) { + if (context.getBootstrapContext().isRegistered(type)) { + return context.getBootstrapContext().get(type); + } + return null; + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java new file mode 100644 index 0000000000..1df7e6ef7d --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2016-2024 Team Fangkehou + * + * 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.dapr.spring.boot.cloudconfig.configdata.config; + +import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties; +import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; +import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; +import org.apache.commons.logging.Log; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.ConfigurableBootstrapContext; +import org.springframework.boot.context.config.*; +import org.springframework.boot.context.properties.bind.BindHandler; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.core.Ordered; + +import java.util.ArrayList; +import java.util.List; + +public class DaprConfigurationConfigDataLocationResolver + implements ConfigDataLocationResolver, Ordered { + + public static final String PREFIX = "dapr:config:"; + + private final Log log; + + public DaprConfigurationConfigDataLocationResolver(DeferredLogFactory logFactory) { + this.log = logFactory.getLog(getClass()); + } + + /** + * Returns if the specified location address contains dapr prefix. + * + * @param context the location resolver context + * @param location the location to check. + * @return if the location is supported by this resolver + */ + @Override + public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { + log.debug(String.format("checking if %s suits for dapr config", location.toString())); + return location.hasPrefix(PREFIX); + } + + /** + * Resolve a {@link ConfigDataLocation} into one or more {@link ConfigDataResource} instances. + * + * @param context the location resolver context + * @param location the location that should be resolved + * @return a list of {@link ConfigDataResource resources} in ascending priority order. + * @throws ConfigDataLocationNotFoundException on a non-optional location that cannot be found + * @throws ConfigDataResourceNotFoundException if a resolved resource cannot be found + */ + @Override + public List resolve(ConfigDataLocationResolverContext context, + ConfigDataLocation location) + throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException { + + DaprCloudConfigProperties daprSecretStoreConfig = loadProperties(context); + DaprClientProperties daprClientConfig = loadClientProperties(context); + + ConfigurableBootstrapContext bootstrapContext = context + .getBootstrapContext(); + + registerConfigManager(daprSecretStoreConfig, daprClientConfig, bootstrapContext); + + List result = new ArrayList<>(); + + String[] secretConfig = location.getNonPrefixedValue(PREFIX).split("/"); + + log.info(String.format("there is %d of values in secretConfig", secretConfig.length)); + + switch (secretConfig.length) { + case 2: + log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' and secret name: '" + + secretConfig[1] + "' secret store for config"); + result.add( + new DaprConfigurationConfigDataResource(location.isOptional(), secretConfig[0], secretConfig[1])); + break; + case 1: + log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' secret store for config"); + result.add(new DaprConfigurationConfigDataResource(location.isOptional(), secretConfig[0], null)); + break; + default: + throw new ConfigDataLocationNotFoundException(location); + } + + return result; + } + + /** + * @return + */ + @Override + public int getOrder() { + return -1; + } + + private void registerConfigManager(DaprCloudConfigProperties properties, + DaprClientProperties clientConfig, + ConfigurableBootstrapContext bootstrapContext) { + if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) { + bootstrapContext.register(DaprCloudConfigClientManager.class, + BootstrapRegistry.InstanceSupplier + .of(new DaprCloudConfigClientManager(properties, clientConfig))); + } + } + + protected DaprCloudConfigProperties loadProperties( + ConfigDataLocationResolverContext context) { + Binder binder = context.getBinder(); + BindHandler bindHandler = getBindHandler(context); + + DaprCloudConfigProperties daprCloudConfigProperties; + if (context.getBootstrapContext().isRegistered(DaprCloudConfigProperties.class)) { + daprCloudConfigProperties = context.getBootstrapContext() + .get(DaprCloudConfigProperties.class); + } else { + daprCloudConfigProperties = binder + .bind(DaprCloudConfigProperties.PROPERTY_PREFIX, Bindable.of(DaprCloudConfigProperties.class), + bindHandler) + .orElseGet(DaprCloudConfigProperties::new); + } + + return daprCloudConfigProperties; + } + + protected DaprClientProperties loadClientProperties( + ConfigDataLocationResolverContext context) { + Binder binder = context.getBinder(); + BindHandler bindHandler = getBindHandler(context); + + DaprClientProperties daprClientConfig; + if (context.getBootstrapContext().isRegistered(DaprClientProperties.class)) { + daprClientConfig = context.getBootstrapContext() + .get(DaprClientProperties.class); + } else { + daprClientConfig = binder + .bind(DaprClientProperties.PROPERTY_PREFIX, Bindable.of(DaprClientProperties.class), + bindHandler) + .orElseGet(DaprClientProperties::new); + } + + return daprClientConfig; + } + + private BindHandler getBindHandler(ConfigDataLocationResolverContext context) { + return context.getBootstrapContext().getOrElse(BindHandler.class, null); + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java new file mode 100644 index 0000000000..19f00312e3 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016-2024 Team Fangkehou + * + * 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.dapr.spring.boot.cloudconfig.configdata.config; + +import org.springframework.boot.context.config.ConfigDataResource; + +public class DaprConfigurationConfigDataResource extends ConfigDataResource { + private final String storeName; + private final String secretName; + + /** + * Create a new non-optional {@link ConfigDataResource} instance. + */ + public DaprConfigurationConfigDataResource(String storeName, String secretName) { + this.storeName = storeName; + this.secretName = secretName; + } + + /** + * Create a new {@link ConfigDataResource} instance. + * + * @param optional if the resource is optional + * @since 2.4.6 + */ + public DaprConfigurationConfigDataResource(boolean optional, String storeName, String secretName) { + super(optional); + this.storeName = storeName; + this.secretName = secretName; + } + + public String getStoreName() { + return storeName; + } + + public String getSecretName() { + return secretName; + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java new file mode 100644 index 0000000000..a52ed62fa6 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2016-2024 Team Fangkehou + * + * 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.dapr.spring.boot.cloudconfig.configdata.secret; + +import static org.springframework.boot.context.config.ConfigData.Option.*; + +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; +import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; +import io.dapr.spring.boot.cloudconfig.parser.DaprSecretStoreParserHandler; +import org.apache.commons.logging.Log; +import org.springframework.boot.context.config.ConfigData; +import org.springframework.boot.context.config.ConfigDataLoader; +import org.springframework.boot.context.config.ConfigDataLoaderContext; +import org.springframework.boot.context.config.ConfigDataResourceNotFoundException; +import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.core.env.PropertySource; + +import io.dapr.client.DaprClient; +import reactor.core.publisher.Mono; + +public class DaprSecretStoreConfigDataLoader implements ConfigDataLoader { + + private final Log log; + + private DaprClient daprClient; + + private DaprCloudConfigProperties daprSecretStoreConfig; + + public DaprSecretStoreConfigDataLoader(DeferredLogFactory logFactory, DaprClient daprClient, + DaprCloudConfigProperties daprSecretStoreConfig) { + this.log = logFactory.getLog(getClass()); + this.daprClient = daprClient; + this.daprSecretStoreConfig = daprSecretStoreConfig; + } + + + /** + * Load {@link ConfigData} for the given resource. + * + * @param context the loader context + * @param resource the resource to load + * @return the loaded config data or {@code null} if the location should be skipped + * @throws IOException on IO error + * @throws ConfigDataResourceNotFoundException if the resource cannot be found + */ + @Override + public ConfigData load(ConfigDataLoaderContext context, DaprSecretStoreConfigDataResource resource) + throws IOException, ConfigDataResourceNotFoundException { + DaprCloudConfigClientManager daprClientSecretStoreConfigManager = + getBean(context, DaprCloudConfigClientManager.class); + + daprClient = DaprCloudConfigClientManager.getDaprClient(); + daprSecretStoreConfig = daprClientSecretStoreConfigManager.getDaprCloudConfigProperties(); + + if (resource.getSecretName() == null) { + return fetchConfig(resource.getStoreName()); + } else { + return fetchConfig(resource.getStoreName(), resource.getSecretName()); + } + } + + private ConfigData fetchConfig(String storeName) { + Mono>> secretMapMono = daprClient.getBulkSecret(storeName); + + Map> secretMap = + secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); + + if (secretMap == null) { + return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } + + List> sourceList = new ArrayList<>(); + + for (Map.Entry> entry : secretMap.entrySet()) { + sourceList.addAll(DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(entry.getKey(), + entry.getValue())); + } + + return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } + + private ConfigData fetchConfig(String storeName, String secretName) { + Mono> secretMapMono = daprClient.getSecret(storeName, secretName); + + Map secretMap = secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); + + if (secretMap == null) { + return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } + + List> sourceList = new ArrayList<>( + DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(secretName, secretMap)); + + return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } + + protected T getBean(ConfigDataLoaderContext context, Class type) { + if (context.getBootstrapContext().isRegistered(type)) { + return context.getBootstrapContext().get(type); + } + return null; + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java new file mode 100644 index 0000000000..fd715dd3af --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2016-2024 Team Fangkehou + * + * 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.dapr.spring.boot.cloudconfig.configdata.secret; + +import java.util.ArrayList; +import java.util.List; + +import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties; +import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; +import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; +import org.apache.commons.logging.Log; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.ConfigurableBootstrapContext; +import org.springframework.boot.context.config.*; +import org.springframework.boot.context.properties.bind.BindHandler; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.core.Ordered; + +public class DaprSecretStoreConfigDataLocationResolver + implements ConfigDataLocationResolver, Ordered { + + public static final String PREFIX = "dapr:secret:"; + + private final Log log; + + public DaprSecretStoreConfigDataLocationResolver(DeferredLogFactory logFactory) { + this.log = logFactory.getLog(getClass()); + } + + /** + * Returns if the specified location address contains dapr prefix. + * + * @param context the location resolver context + * @param location the location to check. + * @return if the location is supported by this resolver + */ + @Override + public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { + log.debug(String.format("checking if %s suits for dapr secret", location.toString())); + return location.hasPrefix(PREFIX); + } + + /** + * Resolve a {@link ConfigDataLocation} into one or more {@link ConfigDataResource} instances. + * + * @param context the location resolver context + * @param location the location that should be resolved + * @return a list of {@link ConfigDataResource resources} in ascending priority order. + * @throws ConfigDataLocationNotFoundException on a non-optional location that cannot be found + * @throws ConfigDataResourceNotFoundException if a resolved resource cannot be found + */ + @Override + public List resolve(ConfigDataLocationResolverContext context, + ConfigDataLocation location) + throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException { + + DaprCloudConfigProperties daprSecretStoreConfig = loadProperties(context); + DaprClientProperties daprClientConfig = loadClientProperties(context); + + ConfigurableBootstrapContext bootstrapContext = context + .getBootstrapContext(); + + registerConfigManager(daprSecretStoreConfig, daprClientConfig, bootstrapContext); + + List result = new ArrayList<>(); + + String[] secretConfig = location.getNonPrefixedValue(PREFIX).split("/"); + + log.info(String.format("there is %d of values in secretConfig", secretConfig.length)); + + switch (secretConfig.length) { + case 2: + log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' and secret name: '" + + secretConfig[1] + "' secret store for config"); + result.add( + new DaprSecretStoreConfigDataResource(location.isOptional(), secretConfig[0], secretConfig[1])); + break; + case 1: + log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' secret store for config"); + result.add(new DaprSecretStoreConfigDataResource(location.isOptional(), secretConfig[0], null)); + break; + default: + throw new ConfigDataLocationNotFoundException(location); + } + + return result; + } + + /** + * @return + */ + @Override + public int getOrder() { + return -1; + } + + private void registerConfigManager(DaprCloudConfigProperties properties, + DaprClientProperties clientConfig, + ConfigurableBootstrapContext bootstrapContext) { + if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) { + bootstrapContext.register(DaprCloudConfigClientManager.class, + BootstrapRegistry.InstanceSupplier + .of(new DaprCloudConfigClientManager(properties, clientConfig))); + } + } + + protected DaprCloudConfigProperties loadProperties( + ConfigDataLocationResolverContext context) { + Binder binder = context.getBinder(); + BindHandler bindHandler = getBindHandler(context); + + DaprCloudConfigProperties daprCloudConfigProperties; + if (context.getBootstrapContext().isRegistered(DaprCloudConfigProperties.class)) { + daprCloudConfigProperties = context.getBootstrapContext() + .get(DaprCloudConfigProperties.class); + } else { + daprCloudConfigProperties = binder + .bind(DaprCloudConfigProperties.PROPERTY_PREFIX, Bindable.of(DaprCloudConfigProperties.class), + bindHandler) + .orElseGet(DaprCloudConfigProperties::new); + } + + return daprCloudConfigProperties; + } + + protected DaprClientProperties loadClientProperties( + ConfigDataLocationResolverContext context) { + Binder binder = context.getBinder(); + BindHandler bindHandler = getBindHandler(context); + + DaprClientProperties daprClientConfig; + if (context.getBootstrapContext().isRegistered(DaprClientProperties.class)) { + daprClientConfig = context.getBootstrapContext() + .get(DaprClientProperties.class); + } else { + daprClientConfig = binder + .bind(DaprClientProperties.PROPERTY_PREFIX, Bindable.of(DaprClientProperties.class), + bindHandler) + .orElseGet(DaprClientProperties::new); + } + + return daprClientConfig; + } + + private BindHandler getBindHandler(ConfigDataLocationResolverContext context) { + return context.getBootstrapContext().getOrElse(BindHandler.class, null); + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java new file mode 100644 index 0000000000..5ff0cd5f79 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016-2024 Team Fangkehou + * + * 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.dapr.spring.boot.cloudconfig.configdata.secret; + +import org.springframework.boot.context.config.ConfigDataResource; + +public class DaprSecretStoreConfigDataResource extends ConfigDataResource { + private final String storeName; + private final String secretName; + + /** + * Create a new non-optional {@link ConfigDataResource} instance. + */ + public DaprSecretStoreConfigDataResource(String storeName, String secretName) { + this.storeName = storeName; + this.secretName = secretName; + } + + /** + * Create a new {@link ConfigDataResource} instance. + * + * @param optional if the resource is optional + * @since 2.4.6 + */ + public DaprSecretStoreConfigDataResource(boolean optional, String storeName, String secretName) { + super(optional); + this.storeName = storeName; + this.secretName = secretName; + } + + public String getStoreName() { + return storeName; + } + + public String getSecretName() { + return secretName; + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java new file mode 100644 index 0000000000..f3c8ed29fe --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016-2024 Team Fangkehou + * + * 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.dapr.spring.boot.cloudconfig.parser; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.springframework.boot.env.PropertySourceLoader; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.SpringFactoriesLoader; + +public class DaprSecretStoreParserHandler { + + private static List propertySourceLoaders; + + private DaprSecretStoreParserHandler() { + propertySourceLoaders = SpringFactoriesLoader + .loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); + } + + public List> parseDaprSecretStoreData(String configName, Map configValue) { + List> result = new ArrayList<>(); + + List configList = new ArrayList<>(); + configValue.forEach((key, value) -> configList.add(String.format("%s=%s", key, value))); + + Resource configResult = new ByteArrayResource(String.join("\n", configList).getBytes()); + + for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) { + try { + result.addAll(propertySourceLoader.load(configName, configResult)); + } catch (IOException ignored) { + } + } + + return result; + } + + public static DaprSecretStoreParserHandler getInstance() { + return ParserHandler.HANDLER; + } + + private static class ParserHandler { + + private static final DaprSecretStoreParserHandler HANDLER = new DaprSecretStoreParserHandler(); + + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000000..d187e117ab --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,18 @@ +{ + "properties": [ + { + "name": "dapr.cloudconfig.enabled", + "type": "java.lang.Boolean", + "defaultValue": true, + "sourceType": "io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties", + "description": "enable dapr secret store or not." + }, + { + "name": "dapr.cloudconfig.timeout", + "type": "java.lang.Integer", + "defaultValue": 2000, + "sourceType": "io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties", + "description": "timeout for getting dapr secret store." + } + ] +} \ No newline at end of file diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..c737527d49 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories @@ -0,0 +1,7 @@ +# ConfigData Location Resolvers +org.springframework.boot.context.config.ConfigDataLocationResolver=\ +io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLocationResolver + +# ConfigData Loaders +org.springframework.boot.context.config.ConfigDataLoader=\ +io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLoader diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..068c69bd08 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +io.dapr.spring.boot.cloudconfig.autoconfigure.DaprCloudConfigAutoConfiguration diff --git a/dapr-spring/pom.xml b/dapr-spring/pom.xml index fe4ebaa172..4dac964402 100644 --- a/dapr-spring/pom.xml +++ b/dapr-spring/pom.xml @@ -25,6 +25,7 @@ dapr-spring-boot-tests dapr-spring-boot-starters/dapr-spring-boot-starter dapr-spring-boot-starters/dapr-spring-boot-starter-test + dapr-spring-cloudconfig From 3b55b4c7a029675678c81853009a4134ad7d2092 Mon Sep 17 00:00:00 2001 From: lony2003 Date: Wed, 5 Mar 2025 09:48:06 +0800 Subject: [PATCH 03/22] style(springboot cloudconfig): update file header Signed-off-by: lony2003 --- .../DaprCloudConfigAutoConfiguration.java | 11 ++++------- .../CloudConfigPropertiesDaprConnectionDetails.java | 2 +- .../config/DaprCloudConfigClientManager.java | 9 +++------ .../cloudconfig/config/DaprCloudConfigProperties.java | 9 +++------ .../config/DaprConfigurationConfigDataLoader.java | 9 +++------ .../DaprConfigurationConfigDataLocationResolver.java | 9 +++------ .../config/DaprConfigurationConfigDataResource.java | 9 +++------ .../secret/DaprSecretStoreConfigDataLoader.java | 9 +++------ .../DaprSecretStoreConfigDataLocationResolver.java | 9 +++------ .../secret/DaprSecretStoreConfigDataResource.java | 9 +++------ .../parser/DaprSecretStoreParserHandler.java | 9 +++------ 11 files changed, 32 insertions(+), 62 deletions(-) diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/autoconfigure/DaprCloudConfigAutoConfiguration.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/autoconfigure/DaprCloudConfigAutoConfiguration.java index fa01892869..3eef6c7aef 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/autoconfigure/DaprCloudConfigAutoConfiguration.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/autoconfigure/DaprCloudConfigAutoConfiguration.java @@ -1,18 +1,15 @@ /* - * Copyright (c) 2016-2024 Team Fangkehou - * + * Copyright 2025 The Dapr 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. - */ +limitations under the License. +*/ package io.dapr.spring.boot.cloudconfig.autoconfigure; @@ -26,7 +23,7 @@ @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(DaprCloudConfigProperties.class) -@ConditionalOnProperty(name = "dapr.secretstore.enabled", matchIfMissing = true) +@ConditionalOnProperty(name = DaprCloudConfigProperties.PROPERTY_PREFIX + ".enabled", matchIfMissing = true) @ConditionalOnClass(DaprClient.class) public class DaprCloudConfigAutoConfiguration { diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/CloudConfigPropertiesDaprConnectionDetails.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/CloudConfigPropertiesDaprConnectionDetails.java index ce617966c0..8f56c383ba 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/CloudConfigPropertiesDaprConnectionDetails.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/CloudConfigPropertiesDaprConnectionDetails.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Dapr Authors + * Copyright 2025 The Dapr 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 diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java index c422033105..0f68b3b1de 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java @@ -1,18 +1,15 @@ /* - * Copyright (c) 2016-2024 Team Fangkehou - * + * Copyright 2025 The Dapr 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. - */ +limitations under the License. +*/ package io.dapr.spring.boot.cloudconfig.config; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java index 5224ceeac0..23ea677550 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java @@ -1,18 +1,15 @@ /* - * Copyright (c) 2016-2024 Team Fangkehou - * + * Copyright 2025 The Dapr 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. - */ +limitations under the License. +*/ package io.dapr.spring.boot.cloudconfig.config; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java index 06d9fd5ca4..c699209776 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java @@ -1,18 +1,15 @@ /* - * Copyright (c) 2016-2024 Team Fangkehou - * + * Copyright 2025 The Dapr 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. - */ +limitations under the License. +*/ package io.dapr.spring.boot.cloudconfig.configdata.config; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java index 1df7e6ef7d..ffe4b90a20 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java @@ -1,18 +1,15 @@ /* - * Copyright (c) 2016-2024 Team Fangkehou - * + * Copyright 2025 The Dapr 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. - */ +limitations under the License. +*/ package io.dapr.spring.boot.cloudconfig.configdata.config; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java index 19f00312e3..609936d4bc 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java @@ -1,18 +1,15 @@ /* - * Copyright (c) 2016-2024 Team Fangkehou - * + * Copyright 2025 The Dapr 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. - */ +limitations under the License. +*/ package io.dapr.spring.boot.cloudconfig.configdata.config; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java index a52ed62fa6..be6a798dbe 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java @@ -1,18 +1,15 @@ /* - * Copyright (c) 2016-2024 Team Fangkehou - * + * Copyright 2025 The Dapr 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. - */ +limitations under the License. +*/ package io.dapr.spring.boot.cloudconfig.configdata.secret; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java index fd715dd3af..865bd22bf7 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java @@ -1,18 +1,15 @@ /* - * Copyright (c) 2016-2024 Team Fangkehou - * + * Copyright 2025 The Dapr 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. - */ +limitations under the License. +*/ package io.dapr.spring.boot.cloudconfig.configdata.secret; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java index 5ff0cd5f79..1a6730052d 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java @@ -1,18 +1,15 @@ /* - * Copyright (c) 2016-2024 Team Fangkehou - * + * Copyright 2025 The Dapr 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. - */ +limitations under the License. +*/ package io.dapr.spring.boot.cloudconfig.configdata.secret; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java index f3c8ed29fe..2983c0296a 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java @@ -1,18 +1,15 @@ /* - * Copyright (c) 2016-2024 Team Fangkehou - * + * Copyright 2025 The Dapr 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. - */ +limitations under the License. +*/ package io.dapr.spring.boot.cloudconfig.parser; From c318d8b71784bd68f1d606e3a5b36112a2de77a2 Mon Sep 17 00:00:00 2001 From: lony2003 Date: Wed, 5 Mar 2025 10:32:05 +0800 Subject: [PATCH 04/22] style(springboot cloudconfig): change code style to match checkstyle Signed-off-by: lony2003 --- .../DaprCloudConfigAutoConfiguration.java | 3 +- .../config/DaprCloudConfigClientManager.java | 93 +++---- .../config/DaprCloudConfigProperties.java | 42 +-- .../DaprConfigurationConfigDataLoader.java | 132 ++++----- ...nfigurationConfigDataLocationResolver.java | 247 ++++++++--------- .../DaprConfigurationConfigDataResource.java | 52 ++-- .../DaprSecretStoreConfigDataLoader.java | 151 +++++------ ...SecretStoreConfigDataLocationResolver.java | 253 +++++++++--------- .../DaprSecretStoreConfigDataResource.java | 52 ++-- .../parser/DaprSecretStoreParserHandler.java | 56 ++-- 10 files changed, 547 insertions(+), 534 deletions(-) diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/autoconfigure/DaprCloudConfigAutoConfiguration.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/autoconfigure/DaprCloudConfigAutoConfiguration.java index 3eef6c7aef..f3283728bc 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/autoconfigure/DaprCloudConfigAutoConfiguration.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/autoconfigure/DaprCloudConfigAutoConfiguration.java @@ -13,14 +13,13 @@ package io.dapr.spring.boot.cloudconfig.autoconfigure; +import io.dapr.client.DaprClient; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; -import io.dapr.client.DaprClient; - @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(DaprCloudConfigProperties.class) @ConditionalOnProperty(name = DaprCloudConfigProperties.PROPERTY_PREFIX + ".enabled", matchIfMissing = true) diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java index 0f68b3b1de..f461b7af2b 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java @@ -23,62 +23,63 @@ public class DaprCloudConfigClientManager { - private static DaprClient daprClient; - private static DaprPreviewClient daprPreviewClient; - private final DaprCloudConfigProperties daprCloudConfigProperties; - private final DaprClientProperties daprClientConfig; + private static DaprClient daprClient; + private static DaprPreviewClient daprPreviewClient; + private final DaprCloudConfigProperties daprCloudConfigProperties; + private final DaprClientProperties daprClientConfig; - public DaprCloudConfigClientManager(DaprCloudConfigProperties daprCloudConfigProperties, - DaprClientProperties daprClientConfig) { - this.daprCloudConfigProperties = daprCloudConfigProperties; - this.daprClientConfig = daprClientConfig; + public DaprCloudConfigClientManager(DaprCloudConfigProperties daprCloudConfigProperties, + DaprClientProperties daprClientConfig) { + this.daprCloudConfigProperties = daprCloudConfigProperties; + this.daprClientConfig = daprClientConfig; - DaprClientBuilder daprClientBuilder = createDaprClientBuilder( - createDaprConnectionDetails(daprClientConfig) - ); + DaprClientBuilder daprClientBuilder = createDaprClientBuilder( + createDaprConnectionDetails(daprClientConfig) + ); - if (DaprCloudConfigClientManager.daprClient == null) { - DaprCloudConfigClientManager.daprClient = daprClientBuilder.build(); - } + if (DaprCloudConfigClientManager.daprClient == null) { + DaprCloudConfigClientManager.daprClient = daprClientBuilder.build(); + } - if (DaprCloudConfigClientManager.daprPreviewClient == null) { - DaprCloudConfigClientManager.daprPreviewClient = daprClientBuilder.buildPreviewClient(); - } + if (DaprCloudConfigClientManager.daprPreviewClient == null) { + DaprCloudConfigClientManager.daprPreviewClient = daprClientBuilder.buildPreviewClient(); } + } + + public static DaprPreviewClient getDaprPreviewClient() { + return DaprCloudConfigClientManager.daprPreviewClient; + } + + public static DaprClient getDaprClient() { + return DaprCloudConfigClientManager.daprClient; + } + + private DaprConnectionDetails createDaprConnectionDetails(DaprClientProperties properties) { + return new CloudConfigPropertiesDaprConnectionDetails(properties); + } - private DaprConnectionDetails createDaprConnectionDetails(DaprClientProperties properties) { - return new CloudConfigPropertiesDaprConnectionDetails(properties); + DaprClientBuilder createDaprClientBuilder(DaprConnectionDetails daprConnectionDetails) { + DaprClientBuilder builder = new DaprClientBuilder(); + if (daprConnectionDetails.httpEndpoint() != null) { + builder.withPropertyOverride(Properties.HTTP_ENDPOINT, daprConnectionDetails.httpEndpoint()); } - DaprClientBuilder createDaprClientBuilder(DaprConnectionDetails daprConnectionDetails) { - DaprClientBuilder builder = new DaprClientBuilder(); - if (daprConnectionDetails.httpEndpoint() != null) { - builder.withPropertyOverride(Properties.HTTP_ENDPOINT, daprConnectionDetails.httpEndpoint()); - } - if (daprConnectionDetails.grpcEndpoint() != null) { - builder.withPropertyOverride(Properties.GRPC_ENDPOINT, daprConnectionDetails.grpcEndpoint()); - } - if (daprConnectionDetails.httpPort() != null) { - builder.withPropertyOverride(Properties.HTTP_PORT, String.valueOf(daprConnectionDetails.httpPort())); - } - if (daprConnectionDetails.grpcPort() != null) { - builder.withPropertyOverride(Properties.GRPC_PORT, String.valueOf(daprConnectionDetails.grpcPort())); - } - return builder; + if (daprConnectionDetails.grpcEndpoint() != null) { + builder.withPropertyOverride(Properties.GRPC_ENDPOINT, daprConnectionDetails.grpcEndpoint()); } - - public static DaprPreviewClient getDaprPreviewClient() { - return DaprCloudConfigClientManager.daprPreviewClient; + if (daprConnectionDetails.httpPort() != null) { + builder.withPropertyOverride(Properties.HTTP_PORT, String.valueOf(daprConnectionDetails.httpPort())); } - - public static DaprClient getDaprClient() { - return DaprCloudConfigClientManager.daprClient; + if (daprConnectionDetails.grpcPort() != null) { + builder.withPropertyOverride(Properties.GRPC_PORT, String.valueOf(daprConnectionDetails.grpcPort())); } + return builder; + } - public DaprCloudConfigProperties getDaprCloudConfigProperties() { - return daprCloudConfigProperties; - } + public DaprCloudConfigProperties getDaprCloudConfigProperties() { + return daprCloudConfigProperties; + } - public DaprClientProperties getDaprClientConfig() { - return daprClientConfig; - } + public DaprClientProperties getDaprClientConfig() { + return daprClientConfig; + } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java index 23ea677550..ddc94e02cc 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java @@ -21,31 +21,31 @@ @ConfigurationProperties(DaprCloudConfigProperties.PROPERTY_PREFIX) public class DaprCloudConfigProperties { - public static final String PROPERTY_PREFIX = "dapr.cloudconfig"; + public static final String PROPERTY_PREFIX = "dapr.cloudconfig"; - /** - * whether enable secret store - */ - private Boolean enabled = true; + /** + * whether enable secret store + */ + private Boolean enabled = true; - /** - * get config timeout - */ - private Integer timeout = 2000; + /** + * get config timeout + */ + private Integer timeout = 2000; - public Integer getTimeout() { - return timeout; - } + public Integer getTimeout() { + return timeout; + } - public void setTimeout(Integer timeout) { - this.timeout = timeout; - } + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } - public Boolean getEnabled() { - return enabled; - } + public Boolean getEnabled() { + return enabled; + } - public void setEnabled(Boolean enabled) { - this.enabled = enabled; - } + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java index c699209776..0f9988f864 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java @@ -33,88 +33,90 @@ import java.util.List; import java.util.Map; -import static org.springframework.boot.context.config.ConfigData.Option.*; +import static org.springframework.boot.context.config.ConfigData.Option.IGNORE_IMPORTS; +import static org.springframework.boot.context.config.ConfigData.Option.IGNORE_PROFILES; +import static org.springframework.boot.context.config.ConfigData.Option.PROFILE_SPECIFIC; public class DaprConfigurationConfigDataLoader implements ConfigDataLoader { - private final Log log; + private final Log log; + + private DaprClient daprClient; + + private DaprCloudConfigProperties daprSecretStoreConfig; + + public DaprConfigurationConfigDataLoader(DeferredLogFactory logFactory, DaprClient daprClient, + DaprCloudConfigProperties daprSecretStoreConfig) { + this.log = logFactory.getLog(getClass()); + this.daprClient = daprClient; + this.daprSecretStoreConfig = daprSecretStoreConfig; + } + + + /** + * Load {@link ConfigData} for the given resource. + * + * @param context the loader context + * @param resource the resource to load + * @return the loaded config data or {@code null} if the location should be skipped + * @throws IOException on IO error + * @throws ConfigDataResourceNotFoundException if the resource cannot be found + */ + @Override + public ConfigData load(ConfigDataLoaderContext context, DaprConfigurationConfigDataResource resource) + throws IOException, ConfigDataResourceNotFoundException { + DaprCloudConfigClientManager daprClientSecretStoreConfigManager = + getBean(context, DaprCloudConfigClientManager.class); + + daprClient = DaprCloudConfigClientManager.getDaprClient(); + daprSecretStoreConfig = daprClientSecretStoreConfigManager.getDaprCloudConfigProperties(); + + if (resource.getSecretName() == null) { + return fetchConfig(resource.getStoreName()); + } else { + return fetchConfig(resource.getStoreName(), resource.getSecretName()); + } + } - private DaprClient daprClient; + private ConfigData fetchConfig(String storeName) { + Mono>> secretMapMono = daprClient.getBulkSecret(storeName); - private DaprCloudConfigProperties daprSecretStoreConfig; + Map> secretMap = + secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); - public DaprConfigurationConfigDataLoader(DeferredLogFactory logFactory, DaprClient daprClient, - DaprCloudConfigProperties daprSecretStoreConfig) { - this.log = logFactory.getLog(getClass()); - this.daprClient = daprClient; - this.daprSecretStoreConfig = daprSecretStoreConfig; + if (secretMap == null) { + return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); } + List> sourceList = new ArrayList<>(); - /** - * Load {@link ConfigData} for the given resource. - * - * @param context the loader context - * @param resource the resource to load - * @return the loaded config data or {@code null} if the location should be skipped - * @throws IOException on IO error - * @throws ConfigDataResourceNotFoundException if the resource cannot be found - */ - @Override - public ConfigData load(ConfigDataLoaderContext context, DaprConfigurationConfigDataResource resource) - throws IOException, ConfigDataResourceNotFoundException { - DaprCloudConfigClientManager daprClientSecretStoreConfigManager = - getBean(context, DaprCloudConfigClientManager.class); - - daprClient = DaprCloudConfigClientManager.getDaprClient(); - daprSecretStoreConfig = daprClientSecretStoreConfigManager.getDaprCloudConfigProperties(); - - if (resource.getSecretName() == null) { - return fetchConfig(resource.getStoreName()); - } else { - return fetchConfig(resource.getStoreName(), resource.getSecretName()); - } + for (Map.Entry> entry : secretMap.entrySet()) { + sourceList.addAll(DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(entry.getKey(), + entry.getValue())); } - private ConfigData fetchConfig(String storeName) { - Mono>> secretMapMono = daprClient.getBulkSecret(storeName); - - Map> secretMap = - secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); + return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } - if (secretMap == null) { - return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); - } + private ConfigData fetchConfig(String storeName, String secretName) { + Mono> secretMapMono = daprClient.getSecret(storeName, secretName); - List> sourceList = new ArrayList<>(); + Map secretMap = secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); - for (Map.Entry> entry : secretMap.entrySet()) { - sourceList.addAll(DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(entry.getKey(), - entry.getValue())); - } - - return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + if (secretMap == null) { + return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); } - private ConfigData fetchConfig(String storeName, String secretName) { - Mono> secretMapMono = daprClient.getSecret(storeName, secretName); - - Map secretMap = secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); + List> sourceList = new ArrayList<>( + DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(secretName, secretMap)); - if (secretMap == null) { - return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); - } - - List> sourceList = new ArrayList<>( - DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(secretName, secretMap)); - - return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); - } + return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } - protected T getBean(ConfigDataLoaderContext context, Class type) { - if (context.getBootstrapContext().isRegistered(type)) { - return context.getBootstrapContext().get(type); - } - return null; + protected T getBean(ConfigDataLoaderContext context, Class type) { + if (context.getBootstrapContext().isRegistered(type)) { + return context.getBootstrapContext().get(type); } + return null; + } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java index ffe4b90a20..deae7bdc5b 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java @@ -19,7 +19,12 @@ import org.apache.commons.logging.Log; import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.ConfigurableBootstrapContext; -import org.springframework.boot.context.config.*; +import org.springframework.boot.context.config.ConfigDataLocation; +import org.springframework.boot.context.config.ConfigDataLocationNotFoundException; +import org.springframework.boot.context.config.ConfigDataLocationResolver; +import org.springframework.boot.context.config.ConfigDataLocationResolverContext; +import org.springframework.boot.context.config.ConfigDataResource; +import org.springframework.boot.context.config.ConfigDataResourceNotFoundException; import org.springframework.boot.context.properties.bind.BindHandler; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; @@ -30,132 +35,132 @@ import java.util.List; public class DaprConfigurationConfigDataLocationResolver - implements ConfigDataLocationResolver, Ordered { - - public static final String PREFIX = "dapr:config:"; - - private final Log log; - - public DaprConfigurationConfigDataLocationResolver(DeferredLogFactory logFactory) { - this.log = logFactory.getLog(getClass()); - } - - /** - * Returns if the specified location address contains dapr prefix. - * - * @param context the location resolver context - * @param location the location to check. - * @return if the location is supported by this resolver - */ - @Override - public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { - log.debug(String.format("checking if %s suits for dapr config", location.toString())); - return location.hasPrefix(PREFIX); + implements ConfigDataLocationResolver, Ordered { + + public static final String PREFIX = "dapr:config:"; + + private final Log log; + + public DaprConfigurationConfigDataLocationResolver(DeferredLogFactory logFactory) { + this.log = logFactory.getLog(getClass()); + } + + /** + * Returns if the specified location address contains dapr prefix. + * + * @param context the location resolver context + * @param location the location to check. + * @return if the location is supported by this resolver + */ + @Override + public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { + log.debug(String.format("checking if %s suits for dapr config", location.toString())); + return location.hasPrefix(PREFIX); + } + + /** + * Resolve a {@link ConfigDataLocation} into one or more {@link ConfigDataResource} instances. + * + * @param context the location resolver context + * @param location the location that should be resolved + * @return a list of {@link ConfigDataResource resources} in ascending priority order. + * @throws ConfigDataLocationNotFoundException on a non-optional location that cannot be found + * @throws ConfigDataResourceNotFoundException if a resolved resource cannot be found + */ + @Override + public List resolve(ConfigDataLocationResolverContext context, + ConfigDataLocation location) + throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException { + + DaprCloudConfigProperties daprSecretStoreConfig = loadProperties(context); + DaprClientProperties daprClientConfig = loadClientProperties(context); + + ConfigurableBootstrapContext bootstrapContext = context + .getBootstrapContext(); + + registerConfigManager(daprSecretStoreConfig, daprClientConfig, bootstrapContext); + + List result = new ArrayList<>(); + + String[] secretConfig = location.getNonPrefixedValue(PREFIX).split("/"); + + log.info(String.format("there is %d of values in secretConfig", secretConfig.length)); + + switch (secretConfig.length) { + case 2: + log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' and secret name: '" + + secretConfig[1] + "' secret store for config"); + result.add( + new DaprConfigurationConfigDataResource(location.isOptional(), secretConfig[0], secretConfig[1])); + break; + case 1: + log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' secret store for config"); + result.add(new DaprConfigurationConfigDataResource(location.isOptional(), secretConfig[0], null)); + break; + default: + throw new ConfigDataLocationNotFoundException(location); } - /** - * Resolve a {@link ConfigDataLocation} into one or more {@link ConfigDataResource} instances. - * - * @param context the location resolver context - * @param location the location that should be resolved - * @return a list of {@link ConfigDataResource resources} in ascending priority order. - * @throws ConfigDataLocationNotFoundException on a non-optional location that cannot be found - * @throws ConfigDataResourceNotFoundException if a resolved resource cannot be found - */ - @Override - public List resolve(ConfigDataLocationResolverContext context, - ConfigDataLocation location) - throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException { - - DaprCloudConfigProperties daprSecretStoreConfig = loadProperties(context); - DaprClientProperties daprClientConfig = loadClientProperties(context); - - ConfigurableBootstrapContext bootstrapContext = context - .getBootstrapContext(); - - registerConfigManager(daprSecretStoreConfig, daprClientConfig, bootstrapContext); - - List result = new ArrayList<>(); - - String[] secretConfig = location.getNonPrefixedValue(PREFIX).split("/"); - - log.info(String.format("there is %d of values in secretConfig", secretConfig.length)); - - switch (secretConfig.length) { - case 2: - log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' and secret name: '" - + secretConfig[1] + "' secret store for config"); - result.add( - new DaprConfigurationConfigDataResource(location.isOptional(), secretConfig[0], secretConfig[1])); - break; - case 1: - log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' secret store for config"); - result.add(new DaprConfigurationConfigDataResource(location.isOptional(), secretConfig[0], null)); - break; - default: - throw new ConfigDataLocationNotFoundException(location); - } - - return result; + return result; + } + + /** + * @return + */ + @Override + public int getOrder() { + return -1; + } + + private void registerConfigManager(DaprCloudConfigProperties properties, + DaprClientProperties clientConfig, + ConfigurableBootstrapContext bootstrapContext) { + if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) { + bootstrapContext.register(DaprCloudConfigClientManager.class, + BootstrapRegistry.InstanceSupplier + .of(new DaprCloudConfigClientManager(properties, clientConfig))); } - - /** - * @return - */ - @Override - public int getOrder() { - return -1; + } + + protected DaprCloudConfigProperties loadProperties( + ConfigDataLocationResolverContext context) { + Binder binder = context.getBinder(); + BindHandler bindHandler = getBindHandler(context); + + DaprCloudConfigProperties daprCloudConfigProperties; + if (context.getBootstrapContext().isRegistered(DaprCloudConfigProperties.class)) { + daprCloudConfigProperties = context.getBootstrapContext() + .get(DaprCloudConfigProperties.class); + } else { + daprCloudConfigProperties = binder + .bind(DaprCloudConfigProperties.PROPERTY_PREFIX, Bindable.of(DaprCloudConfigProperties.class), + bindHandler) + .orElseGet(DaprCloudConfigProperties::new); } - private void registerConfigManager(DaprCloudConfigProperties properties, - DaprClientProperties clientConfig, - ConfigurableBootstrapContext bootstrapContext) { - if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) { - bootstrapContext.register(DaprCloudConfigClientManager.class, - BootstrapRegistry.InstanceSupplier - .of(new DaprCloudConfigClientManager(properties, clientConfig))); - } + return daprCloudConfigProperties; + } + + protected DaprClientProperties loadClientProperties( + ConfigDataLocationResolverContext context) { + Binder binder = context.getBinder(); + BindHandler bindHandler = getBindHandler(context); + + DaprClientProperties daprClientConfig; + if (context.getBootstrapContext().isRegistered(DaprClientProperties.class)) { + daprClientConfig = context.getBootstrapContext() + .get(DaprClientProperties.class); + } else { + daprClientConfig = binder + .bind(DaprClientProperties.PROPERTY_PREFIX, Bindable.of(DaprClientProperties.class), + bindHandler) + .orElseGet(DaprClientProperties::new); } - protected DaprCloudConfigProperties loadProperties( - ConfigDataLocationResolverContext context) { - Binder binder = context.getBinder(); - BindHandler bindHandler = getBindHandler(context); - - DaprCloudConfigProperties daprCloudConfigProperties; - if (context.getBootstrapContext().isRegistered(DaprCloudConfigProperties.class)) { - daprCloudConfigProperties = context.getBootstrapContext() - .get(DaprCloudConfigProperties.class); - } else { - daprCloudConfigProperties = binder - .bind(DaprCloudConfigProperties.PROPERTY_PREFIX, Bindable.of(DaprCloudConfigProperties.class), - bindHandler) - .orElseGet(DaprCloudConfigProperties::new); - } - - return daprCloudConfigProperties; - } + return daprClientConfig; + } - protected DaprClientProperties loadClientProperties( - ConfigDataLocationResolverContext context) { - Binder binder = context.getBinder(); - BindHandler bindHandler = getBindHandler(context); - - DaprClientProperties daprClientConfig; - if (context.getBootstrapContext().isRegistered(DaprClientProperties.class)) { - daprClientConfig = context.getBootstrapContext() - .get(DaprClientProperties.class); - } else { - daprClientConfig = binder - .bind(DaprClientProperties.PROPERTY_PREFIX, Bindable.of(DaprClientProperties.class), - bindHandler) - .orElseGet(DaprClientProperties::new); - } - - return daprClientConfig; - } - - private BindHandler getBindHandler(ConfigDataLocationResolverContext context) { - return context.getBootstrapContext().getOrElse(BindHandler.class, null); - } + private BindHandler getBindHandler(ConfigDataLocationResolverContext context) { + return context.getBootstrapContext().getOrElse(BindHandler.class, null); + } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java index 609936d4bc..cebfa91820 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java @@ -16,34 +16,34 @@ import org.springframework.boot.context.config.ConfigDataResource; public class DaprConfigurationConfigDataResource extends ConfigDataResource { - private final String storeName; - private final String secretName; + private final String storeName; + private final String secretName; - /** - * Create a new non-optional {@link ConfigDataResource} instance. - */ - public DaprConfigurationConfigDataResource(String storeName, String secretName) { - this.storeName = storeName; - this.secretName = secretName; - } + /** + * Create a new non-optional {@link ConfigDataResource} instance. + */ + public DaprConfigurationConfigDataResource(String storeName, String secretName) { + this.storeName = storeName; + this.secretName = secretName; + } - /** - * Create a new {@link ConfigDataResource} instance. - * - * @param optional if the resource is optional - * @since 2.4.6 - */ - public DaprConfigurationConfigDataResource(boolean optional, String storeName, String secretName) { - super(optional); - this.storeName = storeName; - this.secretName = secretName; - } + /** + * Create a new {@link ConfigDataResource} instance. + * + * @param optional if the resource is optional + * @since 2.4.6 + */ + public DaprConfigurationConfigDataResource(boolean optional, String storeName, String secretName) { + super(optional); + this.storeName = storeName; + this.secretName = secretName; + } - public String getStoreName() { - return storeName; - } + public String getStoreName() { + return storeName; + } - public String getSecretName() { - return secretName; - } + public String getSecretName() { + return secretName; + } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java index be6a798dbe..dc06f5f17e 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java @@ -13,15 +13,7 @@ package io.dapr.spring.boot.cloudconfig.configdata.secret; -import static org.springframework.boot.context.config.ConfigData.Option.*; - -import java.io.IOException; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - +import io.dapr.client.DaprClient; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; import io.dapr.spring.boot.cloudconfig.parser.DaprSecretStoreParserHandler; @@ -32,90 +24,99 @@ import org.springframework.boot.context.config.ConfigDataResourceNotFoundException; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.env.PropertySource; - -import io.dapr.client.DaprClient; import reactor.core.publisher.Mono; -public class DaprSecretStoreConfigDataLoader implements ConfigDataLoader { - - private final Log log; +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; - private DaprClient daprClient; +import static org.springframework.boot.context.config.ConfigData.Option.IGNORE_IMPORTS; +import static org.springframework.boot.context.config.ConfigData.Option.IGNORE_PROFILES; +import static org.springframework.boot.context.config.ConfigData.Option.PROFILE_SPECIFIC; - private DaprCloudConfigProperties daprSecretStoreConfig; +public class DaprSecretStoreConfigDataLoader implements ConfigDataLoader { - public DaprSecretStoreConfigDataLoader(DeferredLogFactory logFactory, DaprClient daprClient, - DaprCloudConfigProperties daprSecretStoreConfig) { - this.log = logFactory.getLog(getClass()); - this.daprClient = daprClient; - this.daprSecretStoreConfig = daprSecretStoreConfig; + private final Log log; + + private DaprClient daprClient; + + private DaprCloudConfigProperties daprSecretStoreConfig; + + public DaprSecretStoreConfigDataLoader(DeferredLogFactory logFactory, DaprClient daprClient, + DaprCloudConfigProperties daprSecretStoreConfig) { + this.log = logFactory.getLog(getClass()); + this.daprClient = daprClient; + this.daprSecretStoreConfig = daprSecretStoreConfig; + } + + + /** + * Load {@link ConfigData} for the given resource. + * + * @param context the loader context + * @param resource the resource to load + * @return the loaded config data or {@code null} if the location should be skipped + * @throws IOException on IO error + * @throws ConfigDataResourceNotFoundException if the resource cannot be found + */ + @Override + public ConfigData load(ConfigDataLoaderContext context, DaprSecretStoreConfigDataResource resource) + throws IOException, ConfigDataResourceNotFoundException { + DaprCloudConfigClientManager daprClientSecretStoreConfigManager = + getBean(context, DaprCloudConfigClientManager.class); + + daprClient = DaprCloudConfigClientManager.getDaprClient(); + daprSecretStoreConfig = daprClientSecretStoreConfigManager.getDaprCloudConfigProperties(); + + if (resource.getSecretName() == null) { + return fetchConfig(resource.getStoreName()); + } else { + return fetchConfig(resource.getStoreName(), resource.getSecretName()); } + } + private ConfigData fetchConfig(String storeName) { + Mono>> secretMapMono = daprClient.getBulkSecret(storeName); - /** - * Load {@link ConfigData} for the given resource. - * - * @param context the loader context - * @param resource the resource to load - * @return the loaded config data or {@code null} if the location should be skipped - * @throws IOException on IO error - * @throws ConfigDataResourceNotFoundException if the resource cannot be found - */ - @Override - public ConfigData load(ConfigDataLoaderContext context, DaprSecretStoreConfigDataResource resource) - throws IOException, ConfigDataResourceNotFoundException { - DaprCloudConfigClientManager daprClientSecretStoreConfigManager = - getBean(context, DaprCloudConfigClientManager.class); - - daprClient = DaprCloudConfigClientManager.getDaprClient(); - daprSecretStoreConfig = daprClientSecretStoreConfigManager.getDaprCloudConfigProperties(); - - if (resource.getSecretName() == null) { - return fetchConfig(resource.getStoreName()); - } else { - return fetchConfig(resource.getStoreName(), resource.getSecretName()); - } + Map> secretMap = + secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); + + if (secretMap == null) { + return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); } - private ConfigData fetchConfig(String storeName) { - Mono>> secretMapMono = daprClient.getBulkSecret(storeName); + List> sourceList = new ArrayList<>(); - Map> secretMap = - secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); + for (Map.Entry> entry : secretMap.entrySet()) { + sourceList.addAll(DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(entry.getKey(), + entry.getValue())); + } - if (secretMap == null) { - return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); - } + return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } - List> sourceList = new ArrayList<>(); + private ConfigData fetchConfig(String storeName, String secretName) { + Mono> secretMapMono = daprClient.getSecret(storeName, secretName); - for (Map.Entry> entry : secretMap.entrySet()) { - sourceList.addAll(DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(entry.getKey(), - entry.getValue())); - } + Map secretMap = secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); - return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + if (secretMap == null) { + return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); } - private ConfigData fetchConfig(String storeName, String secretName) { - Mono> secretMapMono = daprClient.getSecret(storeName, secretName); - - Map secretMap = secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); + List> sourceList = new ArrayList<>( + DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(secretName, secretMap)); - if (secretMap == null) { - return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); - } - - List> sourceList = new ArrayList<>( - DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(secretName, secretMap)); - - return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); - } + return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } - protected T getBean(ConfigDataLoaderContext context, Class type) { - if (context.getBootstrapContext().isRegistered(type)) { - return context.getBootstrapContext().get(type); - } - return null; + protected T getBean(ConfigDataLoaderContext context, Class type) { + if (context.getBootstrapContext().isRegistered(type)) { + return context.getBootstrapContext().get(type); } + return null; + } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java index 865bd22bf7..3785668a27 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java @@ -13,149 +13,154 @@ package io.dapr.spring.boot.cloudconfig.configdata.secret; -import java.util.ArrayList; -import java.util.List; - import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; import org.apache.commons.logging.Log; import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.ConfigurableBootstrapContext; -import org.springframework.boot.context.config.*; +import org.springframework.boot.context.config.ConfigDataLocation; +import org.springframework.boot.context.config.ConfigDataLocationNotFoundException; +import org.springframework.boot.context.config.ConfigDataLocationResolver; +import org.springframework.boot.context.config.ConfigDataLocationResolverContext; +import org.springframework.boot.context.config.ConfigDataResource; +import org.springframework.boot.context.config.ConfigDataResourceNotFoundException; import org.springframework.boot.context.properties.bind.BindHandler; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.Ordered; -public class DaprSecretStoreConfigDataLocationResolver - implements ConfigDataLocationResolver, Ordered { - - public static final String PREFIX = "dapr:secret:"; - - private final Log log; - - public DaprSecretStoreConfigDataLocationResolver(DeferredLogFactory logFactory) { - this.log = logFactory.getLog(getClass()); - } - - /** - * Returns if the specified location address contains dapr prefix. - * - * @param context the location resolver context - * @param location the location to check. - * @return if the location is supported by this resolver - */ - @Override - public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { - log.debug(String.format("checking if %s suits for dapr secret", location.toString())); - return location.hasPrefix(PREFIX); - } +import java.util.ArrayList; +import java.util.List; - /** - * Resolve a {@link ConfigDataLocation} into one or more {@link ConfigDataResource} instances. - * - * @param context the location resolver context - * @param location the location that should be resolved - * @return a list of {@link ConfigDataResource resources} in ascending priority order. - * @throws ConfigDataLocationNotFoundException on a non-optional location that cannot be found - * @throws ConfigDataResourceNotFoundException if a resolved resource cannot be found - */ - @Override - public List resolve(ConfigDataLocationResolverContext context, - ConfigDataLocation location) - throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException { - - DaprCloudConfigProperties daprSecretStoreConfig = loadProperties(context); - DaprClientProperties daprClientConfig = loadClientProperties(context); - - ConfigurableBootstrapContext bootstrapContext = context - .getBootstrapContext(); - - registerConfigManager(daprSecretStoreConfig, daprClientConfig, bootstrapContext); - - List result = new ArrayList<>(); - - String[] secretConfig = location.getNonPrefixedValue(PREFIX).split("/"); - - log.info(String.format("there is %d of values in secretConfig", secretConfig.length)); - - switch (secretConfig.length) { - case 2: - log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' and secret name: '" - + secretConfig[1] + "' secret store for config"); - result.add( - new DaprSecretStoreConfigDataResource(location.isOptional(), secretConfig[0], secretConfig[1])); - break; - case 1: - log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' secret store for config"); - result.add(new DaprSecretStoreConfigDataResource(location.isOptional(), secretConfig[0], null)); - break; - default: - throw new ConfigDataLocationNotFoundException(location); - } - - return result; +public class DaprSecretStoreConfigDataLocationResolver + implements ConfigDataLocationResolver, Ordered { + + public static final String PREFIX = "dapr:secret:"; + + private final Log log; + + public DaprSecretStoreConfigDataLocationResolver(DeferredLogFactory logFactory) { + this.log = logFactory.getLog(getClass()); + } + + /** + * Returns if the specified location address contains dapr prefix. + * + * @param context the location resolver context + * @param location the location to check. + * @return if the location is supported by this resolver + */ + @Override + public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { + log.debug(String.format("checking if %s suits for dapr secret", location.toString())); + return location.hasPrefix(PREFIX); + } + + /** + * Resolve a {@link ConfigDataLocation} into one or more {@link ConfigDataResource} instances. + * + * @param context the location resolver context + * @param location the location that should be resolved + * @return a list of {@link ConfigDataResource resources} in ascending priority order. + * @throws ConfigDataLocationNotFoundException on a non-optional location that cannot be found + * @throws ConfigDataResourceNotFoundException if a resolved resource cannot be found + */ + @Override + public List resolve(ConfigDataLocationResolverContext context, + ConfigDataLocation location) + throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException { + + DaprCloudConfigProperties daprSecretStoreConfig = loadProperties(context); + DaprClientProperties daprClientConfig = loadClientProperties(context); + + ConfigurableBootstrapContext bootstrapContext = context + .getBootstrapContext(); + + registerConfigManager(daprSecretStoreConfig, daprClientConfig, bootstrapContext); + + List result = new ArrayList<>(); + + String[] secretConfig = location.getNonPrefixedValue(PREFIX).split("/"); + + log.info(String.format("there is %d of values in secretConfig", secretConfig.length)); + + switch (secretConfig.length) { + case 2: + log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' and secret name: '" + + secretConfig[1] + "' secret store for config"); + result.add( + new DaprSecretStoreConfigDataResource(location.isOptional(), secretConfig[0], secretConfig[1])); + break; + case 1: + log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' secret store for config"); + result.add(new DaprSecretStoreConfigDataResource(location.isOptional(), secretConfig[0], null)); + break; + default: + throw new ConfigDataLocationNotFoundException(location); } - /** - * @return - */ - @Override - public int getOrder() { - return -1; + return result; + } + + /** + * @return + */ + @Override + public int getOrder() { + return -1; + } + + private void registerConfigManager(DaprCloudConfigProperties properties, + DaprClientProperties clientConfig, + ConfigurableBootstrapContext bootstrapContext) { + if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) { + bootstrapContext.register(DaprCloudConfigClientManager.class, + BootstrapRegistry.InstanceSupplier + .of(new DaprCloudConfigClientManager(properties, clientConfig))); } - - private void registerConfigManager(DaprCloudConfigProperties properties, - DaprClientProperties clientConfig, - ConfigurableBootstrapContext bootstrapContext) { - if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) { - bootstrapContext.register(DaprCloudConfigClientManager.class, - BootstrapRegistry.InstanceSupplier - .of(new DaprCloudConfigClientManager(properties, clientConfig))); - } + } + + protected DaprCloudConfigProperties loadProperties( + ConfigDataLocationResolverContext context) { + Binder binder = context.getBinder(); + BindHandler bindHandler = getBindHandler(context); + + DaprCloudConfigProperties daprCloudConfigProperties; + if (context.getBootstrapContext().isRegistered(DaprCloudConfigProperties.class)) { + daprCloudConfigProperties = context.getBootstrapContext() + .get(DaprCloudConfigProperties.class); + } else { + daprCloudConfigProperties = binder + .bind(DaprCloudConfigProperties.PROPERTY_PREFIX, Bindable.of(DaprCloudConfigProperties.class), + bindHandler) + .orElseGet(DaprCloudConfigProperties::new); } - protected DaprCloudConfigProperties loadProperties( - ConfigDataLocationResolverContext context) { - Binder binder = context.getBinder(); - BindHandler bindHandler = getBindHandler(context); - - DaprCloudConfigProperties daprCloudConfigProperties; - if (context.getBootstrapContext().isRegistered(DaprCloudConfigProperties.class)) { - daprCloudConfigProperties = context.getBootstrapContext() - .get(DaprCloudConfigProperties.class); - } else { - daprCloudConfigProperties = binder - .bind(DaprCloudConfigProperties.PROPERTY_PREFIX, Bindable.of(DaprCloudConfigProperties.class), - bindHandler) - .orElseGet(DaprCloudConfigProperties::new); - } - - return daprCloudConfigProperties; + return daprCloudConfigProperties; + } + + protected DaprClientProperties loadClientProperties( + ConfigDataLocationResolverContext context) { + Binder binder = context.getBinder(); + BindHandler bindHandler = getBindHandler(context); + + DaprClientProperties daprClientConfig; + if (context.getBootstrapContext().isRegistered(DaprClientProperties.class)) { + daprClientConfig = context.getBootstrapContext() + .get(DaprClientProperties.class); + } else { + daprClientConfig = binder + .bind(DaprClientProperties.PROPERTY_PREFIX, Bindable.of(DaprClientProperties.class), + bindHandler) + .orElseGet(DaprClientProperties::new); } - protected DaprClientProperties loadClientProperties( - ConfigDataLocationResolverContext context) { - Binder binder = context.getBinder(); - BindHandler bindHandler = getBindHandler(context); - - DaprClientProperties daprClientConfig; - if (context.getBootstrapContext().isRegistered(DaprClientProperties.class)) { - daprClientConfig = context.getBootstrapContext() - .get(DaprClientProperties.class); - } else { - daprClientConfig = binder - .bind(DaprClientProperties.PROPERTY_PREFIX, Bindable.of(DaprClientProperties.class), - bindHandler) - .orElseGet(DaprClientProperties::new); - } - - return daprClientConfig; - } + return daprClientConfig; + } - private BindHandler getBindHandler(ConfigDataLocationResolverContext context) { - return context.getBootstrapContext().getOrElse(BindHandler.class, null); - } + private BindHandler getBindHandler(ConfigDataLocationResolverContext context) { + return context.getBootstrapContext().getOrElse(BindHandler.class, null); + } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java index 1a6730052d..f9b08360b3 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java @@ -16,34 +16,34 @@ import org.springframework.boot.context.config.ConfigDataResource; public class DaprSecretStoreConfigDataResource extends ConfigDataResource { - private final String storeName; - private final String secretName; + private final String storeName; + private final String secretName; - /** - * Create a new non-optional {@link ConfigDataResource} instance. - */ - public DaprSecretStoreConfigDataResource(String storeName, String secretName) { - this.storeName = storeName; - this.secretName = secretName; - } + /** + * Create a new non-optional {@link ConfigDataResource} instance. + */ + public DaprSecretStoreConfigDataResource(String storeName, String secretName) { + this.storeName = storeName; + this.secretName = secretName; + } - /** - * Create a new {@link ConfigDataResource} instance. - * - * @param optional if the resource is optional - * @since 2.4.6 - */ - public DaprSecretStoreConfigDataResource(boolean optional, String storeName, String secretName) { - super(optional); - this.storeName = storeName; - this.secretName = secretName; - } + /** + * Create a new {@link ConfigDataResource} instance. + * + * @param optional if the resource is optional + * @since 2.4.6 + */ + public DaprSecretStoreConfigDataResource(boolean optional, String storeName, String secretName) { + super(optional); + this.storeName = storeName; + this.secretName = secretName; + } - public String getStoreName() { - return storeName; - } + public String getStoreName() { + return storeName; + } - public String getSecretName() { - return secretName; - } + public String getSecretName() { + return secretName; + } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java index 2983c0296a..a360725689 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java @@ -13,51 +13,51 @@ package io.dapr.spring.boot.cloudconfig.parser; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - import org.springframework.boot.env.PropertySourceLoader; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.SpringFactoriesLoader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + public class DaprSecretStoreParserHandler { - private static List propertySourceLoaders; + private static List propertySourceLoaders; - private DaprSecretStoreParserHandler() { - propertySourceLoaders = SpringFactoriesLoader - .loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); - } + private DaprSecretStoreParserHandler() { + propertySourceLoaders = SpringFactoriesLoader + .loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); + } - public List> parseDaprSecretStoreData(String configName, Map configValue) { - List> result = new ArrayList<>(); + public static DaprSecretStoreParserHandler getInstance() { + return ParserHandler.HANDLER; + } - List configList = new ArrayList<>(); - configValue.forEach((key, value) -> configList.add(String.format("%s=%s", key, value))); + public List> parseDaprSecretStoreData(String configName, Map configValue) { + List> result = new ArrayList<>(); - Resource configResult = new ByteArrayResource(String.join("\n", configList).getBytes()); + List configList = new ArrayList<>(); + configValue.forEach((key, value) -> configList.add(String.format("%s=%s", key, value))); - for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) { - try { - result.addAll(propertySourceLoader.load(configName, configResult)); - } catch (IOException ignored) { - } - } + Resource configResult = new ByteArrayResource(String.join("\n", configList).getBytes()); - return result; + for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) { + try { + result.addAll(propertySourceLoader.load(configName, configResult)); + } catch (IOException ignored) { + } } - public static DaprSecretStoreParserHandler getInstance() { - return ParserHandler.HANDLER; - } + return result; + } - private static class ParserHandler { + private static class ParserHandler { - private static final DaprSecretStoreParserHandler HANDLER = new DaprSecretStoreParserHandler(); + private static final DaprSecretStoreParserHandler HANDLER = new DaprSecretStoreParserHandler(); - } + } } From 3fb8cab8ae460c3ad8d1298af4b8e142fed3480a Mon Sep 17 00:00:00 2001 From: lony2003 Date: Thu, 6 Mar 2025 23:02:23 +0800 Subject: [PATCH 05/22] 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 --- .../config/DaprCloudConfigClientManager.java | 7 +- .../config/DaprCloudConfigProperties.java | 4 +- .../configdata/DaprCloudConfigType.java | 17 +++ .../DaprSecretStoreConfigParserHandler.java | 102 +++++++++++++++ .../DaprConfigurationConfigDataLoader.java | 93 +++++++++----- ...nfigurationConfigDataLocationResolver.java | 66 ++++++---- .../DaprConfigurationConfigDataResource.java | 38 ++++-- .../DaprSecretStoreConfigDataLoader.java | 121 +++++++++++++----- ...SecretStoreConfigDataLocationResolver.java | 57 +++++---- .../DaprSecretStoreConfigDataResource.java | 31 ++++- .../parser/DaprSecretStoreParserHandler.java | 63 --------- ...itional-spring-configuration-metadata.json | 4 +- .../main/resources/META-INF/spring.factories | 6 +- 13 files changed, 411 insertions(+), 198 deletions(-) create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigType.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprSecretStoreConfigParserHandler.java delete mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java index f461b7af2b..d33d6b34d6 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java @@ -20,7 +20,6 @@ import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties; import io.dapr.spring.boot.autoconfigure.client.DaprConnectionDetails; - public class DaprCloudConfigClientManager { private static DaprClient daprClient; @@ -28,6 +27,12 @@ public class DaprCloudConfigClientManager { private final DaprCloudConfigProperties daprCloudConfigProperties; private final DaprClientProperties daprClientConfig; + /** + * Create a DaprCloudConfigClientManager to create Config-Specific Dapr Client. + * + * @param daprCloudConfigProperties Properties of Dapr Cloud Config + * @param daprClientConfig Properties of Dapr Client + */ public DaprCloudConfigClientManager(DaprCloudConfigProperties daprCloudConfigProperties, DaprClientProperties daprClientConfig) { this.daprCloudConfigProperties = daprCloudConfigProperties; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java index ddc94e02cc..0c78783ad5 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java @@ -24,12 +24,12 @@ public class DaprCloudConfigProperties { public static final String PROPERTY_PREFIX = "dapr.cloudconfig"; /** - * whether enable secret store + * whether enable cloud config. */ private Boolean enabled = true; /** - * get config timeout + * get config timeout (include wait for dapr sidecar). */ private Integer timeout = 2000; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigType.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigType.java new file mode 100644 index 0000000000..d903ca2723 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigType.java @@ -0,0 +1,17 @@ +package io.dapr.spring.boot.cloudconfig.configdata; + +public enum DaprCloudConfigType { + Doc, + Value; + + /** + * Get Type from String. + * @param value type specified in schema + * @return type enum + */ + public static DaprCloudConfigType fromString(String value) { + return "doc".equals(value) + ? DaprCloudConfigType.Doc + : DaprCloudConfigType.Value; + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprSecretStoreConfigParserHandler.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprSecretStoreConfigParserHandler.java new file mode 100644 index 0000000000..c547ffcae1 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprSecretStoreConfigParserHandler.java @@ -0,0 +1,102 @@ +/* + * Copyright 2025 The Dapr 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.dapr.spring.boot.cloudconfig.configdata; + +import io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataResource; +import org.springframework.boot.env.PropertySourceLoader; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DaprSecretStoreConfigParserHandler { + + private static List propertySourceLoaders; + + private DaprSecretStoreConfigParserHandler() { + propertySourceLoaders = SpringFactoriesLoader + .loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); + } + + public static DaprSecretStoreConfigParserHandler getInstance() { + return ParserHandler.HANDLER; + } + + /** + * Parse Secret using PropertySourceLoaders. + * + *

+ * if type = doc, will treat all values as a property source (both "properties" or "yaml" format supported) + *

+ * + *

+ * if type = value, will transform key and value to "key=value" format ("properties" format) + *

+ * + * @param configName name of the config + * @param configValue value of the config + * @param type value type + * @return property source list + */ + public List> parseDaprSecretStoreData( + String configName, + Map configValue, + DaprCloudConfigType type + ) { + List> result = new ArrayList<>(); + + Map configResults = getConfigResult(configValue, type); + + configResults.forEach((key, configResult) -> { + for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) { + String fullConfigName = StringUtils.hasText(key) ? configName + "." + key : configName; + try { + result.addAll(propertySourceLoader.load(fullConfigName, configResult)); + } catch (IOException ignored) { + continue; + } + } + }); + + return result; + } + + private Map getConfigResult( + Map configValue, + DaprCloudConfigType type + ) { + Map result = new HashMap<>(); + if (DaprCloudConfigType.Doc.equals(type)) { + configValue.forEach((key, value) -> result.put(key, new ByteArrayResource(value.getBytes()))); + } else { + List configList = new ArrayList<>(); + configValue.forEach((key, value) -> configList.add(String.format("%s=%s", key, value))); + result.put("", new ByteArrayResource(String.join("\n", configList).getBytes())); + } + return result; + } + + private static class ParserHandler { + + private static final DaprSecretStoreConfigParserHandler HANDLER = new DaprSecretStoreConfigParserHandler(); + + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java index 0f9988f864..57433c1772 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java @@ -14,9 +14,11 @@ package io.dapr.spring.boot.cloudconfig.configdata.config; import io.dapr.client.DaprClient; +import io.dapr.client.domain.ConfigurationItem; +import io.dapr.client.domain.GetConfigurationRequest; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; -import io.dapr.spring.boot.cloudconfig.parser.DaprSecretStoreParserHandler; +import io.dapr.spring.boot.cloudconfig.configdata.DaprSecretStoreConfigParserHandler; import org.apache.commons.logging.Log; import org.springframework.boot.context.config.ConfigData; import org.springframework.boot.context.config.ConfigDataLoader; @@ -24,12 +26,13 @@ import org.springframework.boot.context.config.ConfigDataResourceNotFoundException; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.env.PropertySource; +import org.springframework.util.StringUtils; import reactor.core.publisher.Mono; import java.io.IOException; import java.time.Duration; import java.util.ArrayList; -import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -45,6 +48,13 @@ public class DaprConfigurationConfigDataLoader implements ConfigDataLoader>> secretMapMono = daprClient.getBulkSecret(storeName); + waitForSidecar(); - Map> secretMap = - secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); - - if (secretMap == null) { - return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); - } - - List> sourceList = new ArrayList<>(); + return fetchConfig(resource); + } - for (Map.Entry> entry : secretMap.entrySet()) { - sourceList.addAll(DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(entry.getKey(), - entry.getValue())); + private void waitForSidecar() throws IOException { + try { + daprClient.waitForSidecar(daprSecretStoreConfig.getTimeout()) + .retry(3) + .block(); + } catch (RuntimeException e) { + log.info(e.getMessage(), e); + throw new IOException("Failed to wait for sidecar", e); } - - return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); } - private ConfigData fetchConfig(String storeName, String secretName) { - Mono> secretMapMono = daprClient.getSecret(storeName, secretName); - - Map secretMap = secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); - - if (secretMap == null) { - return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + private ConfigData fetchConfig(DaprConfigurationConfigDataResource resource) + throws IOException, ConfigDataResourceNotFoundException { + Mono> secretMapMono = daprClient.getConfiguration(new GetConfigurationRequest( + resource.getStoreName(), + StringUtils.hasText(resource.getConfigName()) + ? List.of(resource.getConfigName()) + : null + )); + + try { + Map secretMap = + secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); + + if (secretMap == null) { + log.info("Config not found"); + throw new ConfigDataResourceNotFoundException(resource); + } + + List> sourceList = new ArrayList<>(); + + Map configMap = new HashMap<>(); + secretMap.forEach((key, value) -> { + configMap.put(value.getKey(), value.getValue()); + }); + + sourceList.addAll(DaprSecretStoreConfigParserHandler.getInstance().parseDaprSecretStoreData( + resource.getStoreName(), + configMap, + resource.getType() + )); + + return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } catch (RuntimeException e) { + log.info("Failed to get config from sidecar: " + e.getMessage(), e); + throw new IOException("Failed to get config from sidecar", e); } - List> sourceList = new ArrayList<>( - DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(secretName, secretMap)); - - return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); } protected T getBean(ConfigDataLoaderContext context, Class type) { diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java index deae7bdc5b..cf1b50bcdc 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java @@ -16,6 +16,7 @@ import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; +import io.dapr.spring.boot.cloudconfig.configdata.DaprCloudConfigType; import org.apache.commons.logging.Log; import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.ConfigurableBootstrapContext; @@ -30,6 +31,10 @@ import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.Ordered; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; import java.util.ArrayList; import java.util.List; @@ -82,31 +87,42 @@ public List resolve(ConfigDataLocationResol List result = new ArrayList<>(); - String[] secretConfig = location.getNonPrefixedValue(PREFIX).split("/"); - - log.info(String.format("there is %d of values in secretConfig", secretConfig.length)); - - switch (secretConfig.length) { - case 2: - log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' and secret name: '" - + secretConfig[1] + "' secret store for config"); - result.add( - new DaprConfigurationConfigDataResource(location.isOptional(), secretConfig[0], secretConfig[1])); - break; - case 1: - log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' secret store for config"); - result.add(new DaprConfigurationConfigDataResource(location.isOptional(), secretConfig[0], null)); - break; - default: - throw new ConfigDataLocationNotFoundException(location); + // To avoid UriComponentsBuilder to decode a wrong host. + String fullConfig = "config://" + location.getNonPrefixedValue(PREFIX); + + UriComponents configUri = UriComponentsBuilder.fromUriString(fullConfig).build(); + + String storeName = configUri.getHost(); + String configName = StringUtils.hasText(configUri.getPath()) + ? StringUtils.trimLeadingCharacter(configUri.getPath(), '/') + : null; + + MultiValueMap configQuery = configUri.getQueryParams(); + DaprCloudConfigType configType = DaprCloudConfigType.fromString(configQuery.getFirst("type")); + Boolean subscribe = StringUtils.hasText(configQuery.getFirst("subscribe")) + && Boolean.parseBoolean(configQuery.getFirst("subscribe")); + + + if (configName == null) { + log.debug("Dapr Cloud Config now gains store name: '" + storeName + "' configuration for config"); + result.add(new DaprConfigurationConfigDataResource(location.isOptional(), storeName, + null, configType, subscribe)); + + } else if (configName.contains("/")) { + throw new ConfigDataLocationNotFoundException(location); + + } else { + log.debug("Dapr Cloud Config now gains store name: '" + storeName + "' and config name: '" + + configName + "' configuration for config"); + result.add( + new DaprConfigurationConfigDataResource(location.isOptional(), storeName, configName, + configType, subscribe)); + } return result; } - /** - * @return - */ @Override public int getOrder() { return -1; @@ -115,10 +131,12 @@ public int getOrder() { private void registerConfigManager(DaprCloudConfigProperties properties, DaprClientProperties clientConfig, ConfigurableBootstrapContext bootstrapContext) { - if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) { - bootstrapContext.register(DaprCloudConfigClientManager.class, - BootstrapRegistry.InstanceSupplier - .of(new DaprCloudConfigClientManager(properties, clientConfig))); + synchronized (DaprCloudConfigClientManager.class) { + if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) { + bootstrapContext.register(DaprCloudConfigClientManager.class, + BootstrapRegistry.InstanceSupplier + .of(new DaprCloudConfigClientManager(properties, clientConfig))); + } } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java index cebfa91820..662287043c 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java @@ -13,37 +13,57 @@ package io.dapr.spring.boot.cloudconfig.configdata.config; +import io.dapr.spring.boot.cloudconfig.configdata.DaprCloudConfigType; import org.springframework.boot.context.config.ConfigDataResource; +import org.springframework.lang.Nullable; public class DaprConfigurationConfigDataResource extends ConfigDataResource { private final String storeName; - private final String secretName; + private final String configName; + private final DaprCloudConfigType type; + private final Boolean subscribe; /** * Create a new non-optional {@link ConfigDataResource} instance. + * @param storeName store name + * @param configName config name + * @param type value type + * @param subscribe subscribe for update */ - public DaprConfigurationConfigDataResource(String storeName, String secretName) { + public DaprConfigurationConfigDataResource(String storeName, @Nullable String configName, + DaprCloudConfigType type, Boolean subscribe) { this.storeName = storeName; - this.secretName = secretName; + this.configName = configName; + this.type = type; + this.subscribe = subscribe; } /** * Create a new {@link ConfigDataResource} instance. - * * @param optional if the resource is optional - * @since 2.4.6 + * @param storeName store name + * @param configName config name + * @param type value type + * @param subscribe subscribe for update */ - public DaprConfigurationConfigDataResource(boolean optional, String storeName, String secretName) { + public DaprConfigurationConfigDataResource(boolean optional, String storeName, @Nullable String configName, + DaprCloudConfigType type, Boolean subscribe) { super(optional); this.storeName = storeName; - this.secretName = secretName; + this.configName = configName; + this.type = type; + this.subscribe = subscribe; } public String getStoreName() { return storeName; } - public String getSecretName() { - return secretName; + public String getConfigName() { + return configName; + } + + public DaprCloudConfigType getType() { + return type; } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java index dc06f5f17e..49f4be06e5 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java @@ -16,7 +16,7 @@ import io.dapr.client.DaprClient; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; -import io.dapr.spring.boot.cloudconfig.parser.DaprSecretStoreParserHandler; +import io.dapr.spring.boot.cloudconfig.configdata.DaprSecretStoreConfigParserHandler; import org.apache.commons.logging.Log; import org.springframework.boot.context.config.ConfigData; import org.springframework.boot.context.config.ConfigDataLoader; @@ -29,7 +29,6 @@ import java.io.IOException; import java.time.Duration; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -43,13 +42,20 @@ public class DaprSecretStoreConfigDataLoader implements ConfigDataLoader>> secretMapMono = daprClient.getBulkSecret(storeName); - - Map> secretMap = - secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); - - if (secretMap == null) { - return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + private void waitForSidecar() throws IOException { + try { + daprClient.waitForSidecar(daprCloudConfigProperties.getTimeout()) + .retry(3) + .block(); + } catch (RuntimeException e) { + log.info("Failed to get secret from sidecar: " + e.getMessage(), e); + throw new IOException("Failed to get secret from sidecar", e); } + } - List> sourceList = new ArrayList<>(); + /** + * Get Bulk Secret from Store. + * @param resource Secret Data Resource to fetch + * @return config data + * @throws IOException for block returns exception + * @throws ConfigDataResourceNotFoundException for secret not found + */ + private ConfigData fetchBulkSecret(DaprSecretStoreConfigDataResource resource) + throws IOException, ConfigDataResourceNotFoundException { - for (Map.Entry> entry : secretMap.entrySet()) { - sourceList.addAll(DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(entry.getKey(), - entry.getValue())); - } + Mono>> secretMapMono = daprClient.getBulkSecret(resource.getStoreName()); - return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); - } + try { + Map> secretMap = + secretMapMono.block(Duration.ofMillis(daprCloudConfigProperties.getTimeout())); - private ConfigData fetchConfig(String storeName, String secretName) { - Mono> secretMapMono = daprClient.getSecret(storeName, secretName); + if (secretMap == null) { + log.info("Secret not found"); + throw new ConfigDataResourceNotFoundException(resource); + } - Map secretMap = secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); + List> sourceList = new ArrayList<>(); - if (secretMap == null) { - return new ConfigData(Collections.emptyList(), IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); - } + for (Map.Entry> entry : secretMap.entrySet()) { + sourceList.addAll(DaprSecretStoreConfigParserHandler.getInstance().parseDaprSecretStoreData( + resource.getStoreName() + ":" + entry.getKey(), + entry.getValue(), + resource.getType() + )); + } - List> sourceList = new ArrayList<>( - DaprSecretStoreParserHandler.getInstance().parseDaprSecretStoreData(secretName, secretMap)); + return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } catch (RuntimeException e) { + log.info("Failed to get secret from sidecar: " + e.getMessage(), e); + throw new IOException("Failed to get secret from sidecar", e); + } + } - return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + /** + * Get Secret from Store. + * @param resource Secret Data Resource to fetch + * @return config data + * @throws IOException for block returns exception + * @throws ConfigDataResourceNotFoundException for secret not found + */ + private ConfigData fetchSecret(DaprSecretStoreConfigDataResource resource) + throws IOException, ConfigDataResourceNotFoundException { + Mono> secretMapMono = daprClient.getSecret(resource.getStoreName(), resource.getSecretName()); + + try { + Map secretMap = secretMapMono.block(Duration.ofMillis(daprCloudConfigProperties.getTimeout())); + + if (secretMap == null) { + log.info("Secret not found"); + throw new ConfigDataResourceNotFoundException(resource); + } + + List> sourceList = new ArrayList<>( + DaprSecretStoreConfigParserHandler.getInstance().parseDaprSecretStoreData( + resource.getStoreName() + ":" + resource.getSecretName(), + secretMap, + resource.getType() + )); + + return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); + } catch (RuntimeException e) { + log.info(e.getMessage(), e); + throw new IOException("Failed to wait for sidecar", e); + } } protected T getBean(ConfigDataLoaderContext context, Class type) { diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java index 3785668a27..b1819cacc5 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java @@ -16,6 +16,7 @@ import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; +import io.dapr.spring.boot.cloudconfig.configdata.DaprCloudConfigType; import org.apache.commons.logging.Log; import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.ConfigurableBootstrapContext; @@ -30,6 +31,9 @@ import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.Ordered; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; import java.util.ArrayList; import java.util.List; @@ -82,31 +86,34 @@ public List resolve(ConfigDataLocationResolve List result = new ArrayList<>(); - String[] secretConfig = location.getNonPrefixedValue(PREFIX).split("/"); - - log.info(String.format("there is %d of values in secretConfig", secretConfig.length)); - - switch (secretConfig.length) { - case 2: - log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' and secret name: '" - + secretConfig[1] + "' secret store for config"); - result.add( - new DaprSecretStoreConfigDataResource(location.isOptional(), secretConfig[0], secretConfig[1])); - break; - case 1: - log.debug("Dapr Secret Store now gains store name: '" + secretConfig[0] + "' secret store for config"); - result.add(new DaprSecretStoreConfigDataResource(location.isOptional(), secretConfig[0], null)); - break; - default: - throw new ConfigDataLocationNotFoundException(location); + // To avoid UriComponentsBuilder to decode a wrong host. + String fullConfig = "secret://" + location.getNonPrefixedValue(PREFIX); + + UriComponents configUri = UriComponentsBuilder.fromUriString(fullConfig).build(); + + String storeName = configUri.getHost(); + String secretName = StringUtils.hasText(configUri.getPath()) + ? StringUtils.trimLeadingCharacter(configUri.getPath(), '/') + : null; + + String typeQuery = configUri.getQueryParams().getFirst("type"); + DaprCloudConfigType secretType = DaprCloudConfigType.fromString(typeQuery); + + if (secretName == null) { + log.debug("Dapr Secret Store now gains store name: '" + storeName + "' secret store for config"); + result.add(new DaprSecretStoreConfigDataResource(location.isOptional(), storeName, null, secretType)); + } else if (secretName.contains("/")) { + throw new ConfigDataLocationNotFoundException(location); + } else { + log.debug("Dapr Secret Store now gains store name: '" + storeName + "' and secret name: '" + + secretName + "' secret store for config"); + result.add( + new DaprSecretStoreConfigDataResource(location.isOptional(), storeName, secretName, secretType)); } return result; } - /** - * @return - */ @Override public int getOrder() { return -1; @@ -115,10 +122,12 @@ public int getOrder() { private void registerConfigManager(DaprCloudConfigProperties properties, DaprClientProperties clientConfig, ConfigurableBootstrapContext bootstrapContext) { - if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) { - bootstrapContext.register(DaprCloudConfigClientManager.class, - BootstrapRegistry.InstanceSupplier - .of(new DaprCloudConfigClientManager(properties, clientConfig))); + synchronized (DaprCloudConfigClientManager.class) { + if (!bootstrapContext.isRegistered(DaprCloudConfigClientManager.class)) { + bootstrapContext.register(DaprCloudConfigClientManager.class, + BootstrapRegistry.InstanceSupplier + .of(new DaprCloudConfigClientManager(properties, clientConfig))); + } } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java index f9b08360b3..71a4d41d13 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java @@ -13,30 +13,42 @@ package io.dapr.spring.boot.cloudconfig.configdata.secret; +import io.dapr.spring.boot.cloudconfig.configdata.DaprCloudConfigType; import org.springframework.boot.context.config.ConfigDataResource; +import org.springframework.lang.Nullable; public class DaprSecretStoreConfigDataResource extends ConfigDataResource { private final String storeName; private final String secretName; + private final DaprCloudConfigType type; /** * Create a new non-optional {@link ConfigDataResource} instance. + * + * @param storeName store name + * @param secretName secret name + * @param type secret type */ - public DaprSecretStoreConfigDataResource(String storeName, String secretName) { + public DaprSecretStoreConfigDataResource(String storeName, @Nullable String secretName, DaprCloudConfigType type) { this.storeName = storeName; this.secretName = secretName; + this.type = type; } /** * Create a new {@link ConfigDataResource} instance. * * @param optional if the resource is optional - * @since 2.4.6 + * @param storeName store name + * @param secretName secret name + * @param type secret type */ - public DaprSecretStoreConfigDataResource(boolean optional, String storeName, String secretName) { + public DaprSecretStoreConfigDataResource(boolean optional, String storeName, + @Nullable String secretName, DaprCloudConfigType type) { super(optional); this.storeName = storeName; this.secretName = secretName; + this.type = type; } public String getStoreName() { @@ -46,4 +58,17 @@ public String getStoreName() { public String getSecretName() { return secretName; } + + public DaprCloudConfigType getType() { + return type; + } + + @Override + public String toString() { + return "DaprSecretStoreConfigDataResource{" + + "storeName='" + storeName + '\'' + + ", secretName='" + secretName + '\'' + + ", type=" + type + + '}'; + } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java deleted file mode 100644 index a360725689..0000000000 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/parser/DaprSecretStoreParserHandler.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2025 The Dapr 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.dapr.spring.boot.cloudconfig.parser; - -import org.springframework.boot.env.PropertySourceLoader; -import org.springframework.core.env.PropertySource; -import org.springframework.core.io.ByteArrayResource; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.SpringFactoriesLoader; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class DaprSecretStoreParserHandler { - - private static List propertySourceLoaders; - - private DaprSecretStoreParserHandler() { - propertySourceLoaders = SpringFactoriesLoader - .loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); - } - - public static DaprSecretStoreParserHandler getInstance() { - return ParserHandler.HANDLER; - } - - public List> parseDaprSecretStoreData(String configName, Map configValue) { - List> result = new ArrayList<>(); - - List configList = new ArrayList<>(); - configValue.forEach((key, value) -> configList.add(String.format("%s=%s", key, value))); - - Resource configResult = new ByteArrayResource(String.join("\n", configList).getBytes()); - - for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) { - try { - result.addAll(propertySourceLoader.load(configName, configResult)); - } catch (IOException ignored) { - } - } - - return result; - } - - private static class ParserHandler { - - private static final DaprSecretStoreParserHandler HANDLER = new DaprSecretStoreParserHandler(); - - } -} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/additional-spring-configuration-metadata.json index d187e117ab..7c057581ef 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -5,14 +5,14 @@ "type": "java.lang.Boolean", "defaultValue": true, "sourceType": "io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties", - "description": "enable dapr secret store or not." + "description": "enable dapr cloud config or not." }, { "name": "dapr.cloudconfig.timeout", "type": "java.lang.Integer", "defaultValue": 2000, "sourceType": "io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties", - "description": "timeout for getting dapr secret store." + "description": "timeout for getting dapr config (include wait for dapr sidecar)." } ] } \ No newline at end of file diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories index c737527d49..80ac3af21e 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories @@ -1,7 +1,9 @@ # ConfigData Location Resolvers org.springframework.boot.context.config.ConfigDataLocationResolver=\ -io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLocationResolver +io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLocationResolver\ +io.dapr.spring.boot.cloudconfig.configdata.config.DaprConfigurationConfigDataLocationResolver # ConfigData Loaders org.springframework.boot.context.config.ConfigDataLoader=\ -io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLoader +io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLoader\ +io.dapr.spring.boot.cloudconfig.configdata.config.DaprConfigurationConfigDataLoader From 6724b46a535722d97ee2a65eb9a753598fe6e5ec Mon Sep 17 00:00:00 2001 From: lony2003 Date: Thu, 6 Mar 2025 23:09:33 +0800 Subject: [PATCH 06/22] fix(springboot cloudconfig): Update version of dapr-spring-parent Signed-off-by: lony2003 --- dapr-spring/dapr-spring-cloudconfig/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dapr-spring/dapr-spring-cloudconfig/pom.xml b/dapr-spring/dapr-spring-cloudconfig/pom.xml index 9872068012..25fea66b55 100644 --- a/dapr-spring/dapr-spring-cloudconfig/pom.xml +++ b/dapr-spring/dapr-spring-cloudconfig/pom.xml @@ -6,7 +6,7 @@ io.dapr.spring dapr-spring-parent - 0.14.0-SNAPSHOT + 0.15.0-SNAPSHOT dapr-spring-cloudconfig From 49c1ab6448407d698fedc2415351b23308b25f02 Mon Sep 17 00:00:00 2001 From: lony2003 Date: Fri, 7 Mar 2025 19:30:57 +0800 Subject: [PATCH 07/22] feat(springboot cloudconfig): test cases for cloud config Signed-off-by: lony2003 --- .../dapr-spring-boot-starter/pom.xml | 5 ++ dapr-spring/dapr-spring-cloudconfig/pom.xml | 52 +++++++++++++++++++ .../spotbugs-exclude.xml | 17 ++++++ .../config/DaprCloudConfigClientManager.java | 22 ++++---- .../config/DaprCloudConfigProperties.java | 26 ++++++++++ ...java => DaprCloudConfigParserHandler.java} | 15 +++--- .../DaprConfigurationConfigDataLoader.java | 30 ++++++----- ...nfigurationConfigDataLocationResolver.java | 6 ++- .../DaprSecretStoreConfigDataLoader.java | 26 ++++++---- ...SecretStoreConfigDataLocationResolver.java | 5 +- ...itional-spring-configuration-metadata.json | 14 +++++ .../main/resources/META-INF/spring.factories | 4 +- .../CloudConfigTestApplication.java | 14 +++++ .../boot/cloudconfig/CloudConfigTests.java | 36 +++++++++++++ .../cloudconfig/config/MultipleConfig.java | 43 +++++++++++++++ .../boot/cloudconfig/config/SingleConfig.java | 44 ++++++++++++++++ .../src/test/resources/application.yaml | 16 ++++++ 17 files changed, 327 insertions(+), 48 deletions(-) create mode 100644 dapr-spring/dapr-spring-cloudconfig/spotbugs-exclude.xml rename dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/{DaprSecretStoreConfigParserHandler.java => DaprCloudConfigParserHandler.java} (86%) create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTestApplication.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTests.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/MultipleConfig.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/SingleConfig.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml diff --git a/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml index 623040b378..3a2f73ed6f 100644 --- a/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml +++ b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml @@ -45,6 +45,11 @@ dapr-spring-workflows ${project.parent.version} + + io.dapr.spring + dapr-spring-cloudconfig + ${project.parent.version} + diff --git a/dapr-spring/dapr-spring-cloudconfig/pom.xml b/dapr-spring/dapr-spring-cloudconfig/pom.xml index 25fea66b55..b337abff49 100644 --- a/dapr-spring/dapr-spring-cloudconfig/pom.xml +++ b/dapr-spring/dapr-spring-cloudconfig/pom.xml @@ -21,6 +21,58 @@ ${project.parent.version} compile + + + org.springframework.boot + spring-boot-starter-web + test + + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + com.vaadin.external.google + android-json + + + + + io.dapr + testcontainers-dapr + ${dapr.sdk.alpha.version} + test + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.8.2.0 + + ./spotbugs-exclude.xml + ${spotbugs.fail} + true + + + + validate + validate + + check + + + + + + + \ No newline at end of file diff --git a/dapr-spring/dapr-spring-cloudconfig/spotbugs-exclude.xml b/dapr-spring/dapr-spring-cloudconfig/spotbugs-exclude.xml new file mode 100644 index 0000000000..c586b55a6c --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/spotbugs-exclude.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java index d33d6b34d6..46c0ed4c94 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java @@ -22,8 +22,8 @@ public class DaprCloudConfigClientManager { - private static DaprClient daprClient; - private static DaprPreviewClient daprPreviewClient; + private DaprClient daprClient; + private DaprPreviewClient daprPreviewClient; private final DaprCloudConfigProperties daprCloudConfigProperties; private final DaprClientProperties daprClientConfig; @@ -31,7 +31,7 @@ public class DaprCloudConfigClientManager { * Create a DaprCloudConfigClientManager to create Config-Specific Dapr Client. * * @param daprCloudConfigProperties Properties of Dapr Cloud Config - * @param daprClientConfig Properties of Dapr Client + * @param daprClientConfig Properties of Dapr Client */ public DaprCloudConfigClientManager(DaprCloudConfigProperties daprCloudConfigProperties, DaprClientProperties daprClientConfig) { @@ -42,21 +42,17 @@ public DaprCloudConfigClientManager(DaprCloudConfigProperties daprCloudConfigPro createDaprConnectionDetails(daprClientConfig) ); - if (DaprCloudConfigClientManager.daprClient == null) { - DaprCloudConfigClientManager.daprClient = daprClientBuilder.build(); - } + this.daprClient = daprClientBuilder.build(); - if (DaprCloudConfigClientManager.daprPreviewClient == null) { - DaprCloudConfigClientManager.daprPreviewClient = daprClientBuilder.buildPreviewClient(); - } + this.daprPreviewClient = daprClientBuilder.buildPreviewClient(); } - public static DaprPreviewClient getDaprPreviewClient() { - return DaprCloudConfigClientManager.daprPreviewClient; + public DaprPreviewClient getDaprPreviewClient() { + return this.daprPreviewClient; } - public static DaprClient getDaprClient() { - return DaprCloudConfigClientManager.daprClient; + public DaprClient getDaprClient() { + return this.daprClient; } private DaprConnectionDetails createDaprConnectionDetails(DaprClientProperties properties) { diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java index 0c78783ad5..7087f91254 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigProperties.java @@ -28,6 +28,16 @@ public class DaprCloudConfigProperties { */ private Boolean enabled = true; + /** + * whether enable dapr client wait for sidecar, if no response, will throw IOException. + */ + private Boolean waitSidecarEnabled = false; + + /** + * retries of dapr client wait for sidecar. + */ + private Integer waitSidecarRetries = 3; + /** * get config timeout (include wait for dapr sidecar). */ @@ -48,4 +58,20 @@ public Boolean getEnabled() { public void setEnabled(Boolean enabled) { this.enabled = enabled; } + + public Boolean getWaitSidecarEnabled() { + return waitSidecarEnabled; + } + + public void setWaitSidecarEnabled(Boolean waitSidecarEnabled) { + this.waitSidecarEnabled = waitSidecarEnabled; + } + + public Integer getWaitSidecarRetries() { + return waitSidecarRetries; + } + + public void setWaitSidecarRetries(Integer waitSidecarRetries) { + this.waitSidecarRetries = waitSidecarRetries; + } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprSecretStoreConfigParserHandler.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigParserHandler.java similarity index 86% rename from dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprSecretStoreConfigParserHandler.java rename to dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigParserHandler.java index c547ffcae1..ba5ce416bf 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprSecretStoreConfigParserHandler.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigParserHandler.java @@ -13,7 +13,6 @@ package io.dapr.spring.boot.cloudconfig.configdata; -import io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataResource; import org.springframework.boot.env.PropertySourceLoader; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ByteArrayResource; @@ -22,21 +21,22 @@ import org.springframework.util.StringUtils; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -public class DaprSecretStoreConfigParserHandler { +public class DaprCloudConfigParserHandler { private static List propertySourceLoaders; - private DaprSecretStoreConfigParserHandler() { + private DaprCloudConfigParserHandler() { propertySourceLoaders = SpringFactoriesLoader .loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); } - public static DaprSecretStoreConfigParserHandler getInstance() { + public static DaprCloudConfigParserHandler getInstance() { return ParserHandler.HANDLER; } @@ -85,18 +85,19 @@ private Map getConfigResult( ) { Map result = new HashMap<>(); if (DaprCloudConfigType.Doc.equals(type)) { - configValue.forEach((key, value) -> result.put(key, new ByteArrayResource(value.getBytes()))); + configValue.forEach((key, value) -> result.put(key, + new ByteArrayResource(value.getBytes(StandardCharsets.UTF_8)))); } else { List configList = new ArrayList<>(); configValue.forEach((key, value) -> configList.add(String.format("%s=%s", key, value))); - result.put("", new ByteArrayResource(String.join("\n", configList).getBytes())); + result.put("", new ByteArrayResource(String.join("\n", configList).getBytes(StandardCharsets.UTF_8))); } return result; } private static class ParserHandler { - private static final DaprSecretStoreConfigParserHandler HANDLER = new DaprSecretStoreConfigParserHandler(); + private static final DaprCloudConfigParserHandler HANDLER = new DaprCloudConfigParserHandler(); } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java index 57433c1772..f185bde330 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java @@ -18,7 +18,7 @@ import io.dapr.client.domain.GetConfigurationRequest; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; -import io.dapr.spring.boot.cloudconfig.configdata.DaprSecretStoreConfigParserHandler; +import io.dapr.spring.boot.cloudconfig.configdata.DaprCloudConfigParserHandler; import org.apache.commons.logging.Log; import org.springframework.boot.context.config.ConfigData; import org.springframework.boot.context.config.ConfigDataLoader; @@ -46,20 +46,20 @@ public class DaprConfigurationConfigDataLoader implements ConfigDataLoader secretMap = - secretMapMono.block(Duration.ofMillis(daprSecretStoreConfig.getTimeout())); + secretMapMono.block(Duration.ofMillis(daprCloudConfigProperties.getTimeout())); if (secretMap == null) { log.info("Config not found"); @@ -122,7 +128,7 @@ private ConfigData fetchConfig(DaprConfigurationConfigDataResource resource) configMap.put(value.getKey(), value.getValue()); }); - sourceList.addAll(DaprSecretStoreConfigParserHandler.getInstance().parseDaprSecretStoreData( + sourceList.addAll(DaprCloudConfigParserHandler.getInstance().parseDaprSecretStoreData( resource.getStoreName(), configMap, resource.getType() diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java index cf1b50bcdc..10a3384c51 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java @@ -93,8 +93,10 @@ public List resolve(ConfigDataLocationResol UriComponents configUri = UriComponentsBuilder.fromUriString(fullConfig).build(); String storeName = configUri.getHost(); - String configName = StringUtils.hasText(configUri.getPath()) - ? StringUtils.trimLeadingCharacter(configUri.getPath(), '/') + + String configPath = configUri.getPath(); + String configName = StringUtils.hasText(configPath) + ? StringUtils.trimLeadingCharacter(configPath, '/') : null; MultiValueMap configQuery = configUri.getQueryParams(); diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java index 49f4be06e5..1d07f4a20d 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java @@ -16,7 +16,7 @@ import io.dapr.client.DaprClient; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; -import io.dapr.spring.boot.cloudconfig.configdata.DaprSecretStoreConfigParserHandler; +import io.dapr.spring.boot.cloudconfig.configdata.DaprCloudConfigParserHandler; import org.apache.commons.logging.Log; import org.springframework.boot.context.config.ConfigData; import org.springframework.boot.context.config.ConfigDataLoader; @@ -74,10 +74,16 @@ public ConfigData load(ConfigDataLoaderContext context, DaprSecretStoreConfigDat DaprCloudConfigClientManager daprCloudConfigClientManager = getBean(context, DaprCloudConfigClientManager.class); - daprClient = DaprCloudConfigClientManager.getDaprClient(); + daprClient = daprCloudConfigClientManager.getDaprClient(); daprCloudConfigProperties = daprCloudConfigClientManager.getDaprCloudConfigProperties(); - waitForSidecar(); + if (!daprCloudConfigProperties.getEnabled()) { + return ConfigData.EMPTY; + } + + if (daprCloudConfigProperties.getWaitSidecarEnabled()) { + waitForSidecar(); + } if (resource.getSecretName() == null) { return fetchBulkSecret(resource); @@ -89,11 +95,11 @@ public ConfigData load(ConfigDataLoaderContext context, DaprSecretStoreConfigDat private void waitForSidecar() throws IOException { try { daprClient.waitForSidecar(daprCloudConfigProperties.getTimeout()) - .retry(3) + .retry(daprCloudConfigProperties.getWaitSidecarRetries()) .block(); } catch (RuntimeException e) { - log.info("Failed to get secret from sidecar: " + e.getMessage(), e); - throw new IOException("Failed to get secret from sidecar", e); + log.info("Failed to wait for sidecar: " + e.getMessage(), e); + throw new IOException("Failed to wait for sidecar", e); } } @@ -121,7 +127,7 @@ private ConfigData fetchBulkSecret(DaprSecretStoreConfigDataResource resource) List> sourceList = new ArrayList<>(); for (Map.Entry> entry : secretMap.entrySet()) { - sourceList.addAll(DaprSecretStoreConfigParserHandler.getInstance().parseDaprSecretStoreData( + sourceList.addAll(DaprCloudConfigParserHandler.getInstance().parseDaprSecretStoreData( resource.getStoreName() + ":" + entry.getKey(), entry.getValue(), resource.getType() @@ -155,7 +161,7 @@ private ConfigData fetchSecret(DaprSecretStoreConfigDataResource resource) } List> sourceList = new ArrayList<>( - DaprSecretStoreConfigParserHandler.getInstance().parseDaprSecretStoreData( + DaprCloudConfigParserHandler.getInstance().parseDaprSecretStoreData( resource.getStoreName() + ":" + resource.getSecretName(), secretMap, resource.getType() @@ -163,8 +169,8 @@ private ConfigData fetchSecret(DaprSecretStoreConfigDataResource resource) return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); } catch (RuntimeException e) { - log.info(e.getMessage(), e); - throw new IOException("Failed to wait for sidecar", e); + log.info("Failed to get secret from sidecar: " + e.getMessage(), e); + throw new IOException("Failed to get secret from sidecar", e); } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java index b1819cacc5..e332260dfe 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java @@ -92,8 +92,9 @@ public List resolve(ConfigDataLocationResolve UriComponents configUri = UriComponentsBuilder.fromUriString(fullConfig).build(); String storeName = configUri.getHost(); - String secretName = StringUtils.hasText(configUri.getPath()) - ? StringUtils.trimLeadingCharacter(configUri.getPath(), '/') + String secretPath = configUri.getPath(); + String secretName = StringUtils.hasText(secretPath) + ? StringUtils.trimLeadingCharacter(secretPath, '/') : null; String typeQuery = configUri.getQueryParams().getFirst("type"); diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 7c057581ef..928939cb5e 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -13,6 +13,20 @@ "defaultValue": 2000, "sourceType": "io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties", "description": "timeout for getting dapr config (include wait for dapr sidecar)." + }, + { + "name": "dapr.cloudconfig.wait-sidecar-enabled", + "type": "java.lang.Boolean", + "defaultValue": false, + "sourceType": "io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties", + "description": "whether enable dapr client wait for sidecar, if no response, will throw IOException." + }, + { + "name": "dapr.cloudconfig.wait-sidecar-retries", + "type": "java.lang.Integer", + "defaultValue": 3, + "sourceType": "io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties", + "description": "retries of dapr client wait for sidecar." } ] } \ No newline at end of file diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories index 80ac3af21e..3214bed85b 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories @@ -1,9 +1,9 @@ # ConfigData Location Resolvers org.springframework.boot.context.config.ConfigDataLocationResolver=\ -io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLocationResolver\ +io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLocationResolver,\ io.dapr.spring.boot.cloudconfig.configdata.config.DaprConfigurationConfigDataLocationResolver # ConfigData Loaders org.springframework.boot.context.config.ConfigDataLoader=\ -io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLoader\ +io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLoader,\ io.dapr.spring.boot.cloudconfig.configdata.config.DaprConfigurationConfigDataLoader diff --git a/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTestApplication.java b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTestApplication.java new file mode 100644 index 0000000000..a69b67a408 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTestApplication.java @@ -0,0 +1,14 @@ +package io.dapr.spring.boot.cloudconfig; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan(basePackages = "io.dapr.spring.boot.cloudconfig.config") +public class CloudConfigTestApplication { + public static void main(String[] args) { + SpringApplication.run(CloudConfigTestApplication.class, args); + } + +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTests.java b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTests.java new file mode 100644 index 0000000000..b1b1f128f9 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTests.java @@ -0,0 +1,36 @@ +package io.dapr.spring.boot.cloudconfig; + +import io.dapr.spring.boot.cloudconfig.config.MultipleConfig; +import io.dapr.spring.boot.cloudconfig.config.SingleConfig; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(classes = {CloudConfigTestApplication.class, MultipleConfig.class, SingleConfig.class}) +public class CloudConfigTests { + + @Autowired + MultipleConfig multipleConfig; + + @Autowired + SingleConfig singleConfig; + + @Test + public void testSecretStoreConfig() { + assertEquals("testvalue", singleConfig.getSingleValueSecret()); + assertEquals("config", singleConfig.getMultiValuedSecret()); + + assertEquals("spring", multipleConfig.getMultipleSecretConfigV1()); + assertEquals("dapr", multipleConfig.getMultipleSecretConfigV2()); + + } + + @Test + public void testConfigurationConfig() { + assertEquals("testvalue", singleConfig.getSingleValueConfig()); + + assertEquals("cloud", multipleConfig.getMultipleConfigurationConfigV3()); + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/MultipleConfig.java b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/MultipleConfig.java new file mode 100644 index 0000000000..f7d9a6dcb2 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/MultipleConfig.java @@ -0,0 +1,43 @@ +package io.dapr.spring.boot.cloudconfig.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class MultipleConfig { + + //should be spring + @Value("dapr.spring.demo-config-secret.multivalue.v1") + private String multipleSecretConfigV1; + //should be dapr + @Value("dapr.spring.demo-config-secret.multivalue.v2") + private String multipleSecretConfigV2; + + //should be cloud + @Value("dapr.spring.demo-config-config.multivalue.v3") + private String multipleConfigurationConfigV3; + + public String getMultipleSecretConfigV1() { + return multipleSecretConfigV1; + } + + public void setMultipleSecretConfigV1(String multipleSecretConfigV1) { + this.multipleSecretConfigV1 = multipleSecretConfigV1; + } + + public String getMultipleSecretConfigV2() { + return multipleSecretConfigV2; + } + + public void setMultipleSecretConfigV2(String multipleSecretConfigV2) { + this.multipleSecretConfigV2 = multipleSecretConfigV2; + } + + public String getMultipleConfigurationConfigV3() { + return multipleConfigurationConfigV3; + } + + public void setMultipleConfigurationConfigV3(String multipleConfigurationConfigV3) { + this.multipleConfigurationConfigV3 = multipleConfigurationConfigV3; + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/SingleConfig.java b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/SingleConfig.java new file mode 100644 index 0000000000..67cb002438 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/SingleConfig.java @@ -0,0 +1,44 @@ +package io.dapr.spring.boot.cloudconfig.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class SingleConfig { + + // should be testvalue + @Value("dapr.spring.demo-config-secret.singlevalue") + private String singleValueSecret; + + // should be config + @Value("dapr.spring.demo-config-secret.multivalue.v4") + private String multiValuedSecret; + + // should be testvalue + @Value("dapr.spring.demo-config-config.singlevalue") + private String singleValueConfig; + + public String getSingleValueSecret() { + return singleValueSecret; + } + + public void setSingleValueSecret(String singleValueSecret) { + this.singleValueSecret = singleValueSecret; + } + + public String getMultiValuedSecret() { + return multiValuedSecret; + } + + public void setMultiValuedSecret(String multiValuedSecret) { + this.multiValuedSecret = multiValuedSecret; + } + + public String getSingleValueConfig() { + return singleValueConfig; + } + + public void setSingleValueConfig(String singleValueConfig) { + this.singleValueConfig = singleValueConfig; + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml b/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml new file mode 100644 index 0000000000..6cd67e7380 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml @@ -0,0 +1,16 @@ +spring: + application: + name: producer-app + config: + import: + - dapr:secret:democonfig/multivalue-properties?type=doc + - dapr:secret:democonfig/dapr.spring.demo-config.singlevalue?type=value + - dapr:secret:democonfigmulti/value1?type=value + - dapr:config:democonfigconf/dapr.spring.demo-config-config.singlevalue?type=value + - dapr:config:democonfigconf/multivalue-yaml?type=doc + +dapr: + cloudconfig: + enabled: true + # in case some connection issue + wait-sidecar-enabled: true From b6de0b88745387bca238adb7542c6cdacdb62478 Mon Sep 17 00:00:00 2001 From: lony2003 Date: Sat, 8 Mar 2025 00:25:26 +0800 Subject: [PATCH 08/22] fix(springboot cloudconfig): Fix problems of PropertySourceLoader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Springboot‘s PropertySourceLoader will return a invalid source if it doesn't support that instead of throw errors. So we have to clear that what kind of loader we want to use, that is , that kind of resource it is. Signed-off-by: lony2003 --- dapr-spring/dapr-spring-cloudconfig/pom.xml | 43 +++++++++++------- .../spotbugs-exclude.xml | 4 ++ .../config/DaprCloudConfigClientManager.java | 20 +++++---- .../DaprCloudConfigParserHandler.java | 42 ++++++++++++++++-- .../configdata/DaprCloudConfigType.java | 8 ++-- .../DaprConfigurationConfigDataLoader.java | 13 +++--- ...nfigurationConfigDataLocationResolver.java | 3 +- .../DaprSecretStoreConfigDataLoader.java | 13 ++++-- ...SecretStoreConfigDataLocationResolver.java | 7 ++- .../main/resources/META-INF/spring.factories | 8 ++-- .../boot/cloudconfig/CloudConfigTests.java | 44 ++++++++++++++++++- .../cloudconfig/config/MultipleConfig.java | 6 +-- .../boot/cloudconfig/config/SingleConfig.java | 16 +------ .../src/test/resources/application.yaml | 13 ++++-- 14 files changed, 171 insertions(+), 69 deletions(-) diff --git a/dapr-spring/dapr-spring-cloudconfig/pom.xml b/dapr-spring/dapr-spring-cloudconfig/pom.xml index b337abff49..35c584a112 100644 --- a/dapr-spring/dapr-spring-cloudconfig/pom.xml +++ b/dapr-spring/dapr-spring-cloudconfig/pom.xml @@ -22,31 +22,44 @@ compile + + + + + + + + + + + + - org.springframework.boot - spring-boot-starter-web + io.dapr.spring + dapr-spring-data + ${project.parent.version} test - org.testcontainers - testcontainers + io.dapr.spring + dapr-spring-messaging + ${project.parent.version} test - org.testcontainers - junit-jupiter + io.dapr.spring + dapr-spring-workflows + ${project.parent.version} + test + + + org.springframework.boot + spring-boot-starter-web test - - - com.vaadin.external.google - android-json - - - io.dapr - testcontainers-dapr - ${dapr.sdk.alpha.version} + org.mockito + mockito-core test diff --git a/dapr-spring/dapr-spring-cloudconfig/spotbugs-exclude.xml b/dapr-spring/dapr-spring-cloudconfig/spotbugs-exclude.xml index c586b55a6c..a3108aba0c 100644 --- a/dapr-spring/dapr-spring-cloudconfig/spotbugs-exclude.xml +++ b/dapr-spring/dapr-spring-cloudconfig/spotbugs-exclude.xml @@ -14,4 +14,8 @@ + + + + diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java index 46c0ed4c94..3038a152ce 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/config/DaprCloudConfigClientManager.java @@ -22,8 +22,8 @@ public class DaprCloudConfigClientManager { - private DaprClient daprClient; - private DaprPreviewClient daprPreviewClient; + private static DaprClient daprClient; + private static DaprPreviewClient daprPreviewClient; private final DaprCloudConfigProperties daprCloudConfigProperties; private final DaprClientProperties daprClientConfig; @@ -42,17 +42,21 @@ public DaprCloudConfigClientManager(DaprCloudConfigProperties daprCloudConfigPro createDaprConnectionDetails(daprClientConfig) ); - this.daprClient = daprClientBuilder.build(); + if (DaprCloudConfigClientManager.daprClient == null) { + DaprCloudConfigClientManager.daprClient = daprClientBuilder.build(); + } - this.daprPreviewClient = daprClientBuilder.buildPreviewClient(); + if (DaprCloudConfigClientManager.daprPreviewClient == null) { + DaprCloudConfigClientManager.daprPreviewClient = daprClientBuilder.buildPreviewClient(); + } } - public DaprPreviewClient getDaprPreviewClient() { - return this.daprPreviewClient; + public static DaprPreviewClient getDaprPreviewClient() { + return DaprCloudConfigClientManager.daprPreviewClient; } - public DaprClient getDaprClient() { - return this.daprClient; + public static DaprClient getDaprClient() { + return DaprCloudConfigClientManager.daprClient; } private DaprConnectionDetails createDaprConnectionDetails(DaprClientProperties properties) { diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigParserHandler.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigParserHandler.java index ba5ce416bf..86942f4061 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigParserHandler.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigParserHandler.java @@ -14,6 +14,7 @@ package io.dapr.spring.boot.cloudconfig.configdata; import org.springframework.boot.env.PropertySourceLoader; +import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; @@ -23,6 +24,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -32,8 +34,25 @@ public class DaprCloudConfigParserHandler { private static List propertySourceLoaders; private DaprCloudConfigParserHandler() { - propertySourceLoaders = SpringFactoriesLoader + List loaders = SpringFactoriesLoader .loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); + + //Range loaders (Yaml as the first) + int yamlIndex = -1; + for (int i = 0; i < loaders.size(); i++) { + if (loaders.get(i) instanceof YamlPropertySourceLoader) { + yamlIndex = i; + break; + } + } + + // found yaml loader then move to the front + if (yamlIndex != -1) { + PropertySourceLoader yamlSourceLoader = loaders.remove(yamlIndex); + loaders.add(0, yamlSourceLoader); + } + + propertySourceLoaders = loaders; } public static DaprCloudConfigParserHandler getInstance() { @@ -56,7 +75,7 @@ public static DaprCloudConfigParserHandler getInstance() { * @param type value type * @return property source list */ - public List> parseDaprSecretStoreData( + public List> parseDaprCloudConfigData( String configName, Map configValue, DaprCloudConfigType type @@ -64,15 +83,20 @@ public List> parseDaprSecretStoreData( List> result = new ArrayList<>(); Map configResults = getConfigResult(configValue, type); + String extension = DaprCloudConfigType.DocYaml.equals(type) ? ".yaml" : ".properties"; configResults.forEach((key, configResult) -> { for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) { + if (!canLoadFileExtension(propertySourceLoader, extension)) { + continue; + } String fullConfigName = StringUtils.hasText(key) ? configName + "." + key : configName; try { result.addAll(propertySourceLoader.load(fullConfigName, configResult)); } catch (IOException ignored) { continue; } + return; } }); @@ -84,7 +108,7 @@ private Map getConfigResult( DaprCloudConfigType type ) { Map result = new HashMap<>(); - if (DaprCloudConfigType.Doc.equals(type)) { + if (DaprCloudConfigType.DocYaml.equals(type) || DaprCloudConfigType.DocProperties.equals(type)) { configValue.forEach((key, value) -> result.put(key, new ByteArrayResource(value.getBytes(StandardCharsets.UTF_8)))); } else { @@ -95,6 +119,18 @@ private Map getConfigResult( return result; } + /** + * check the current extension can be processed. + * @param loader the propertySourceLoader + * @param extension file extension + * @return if can match extension + */ + private boolean canLoadFileExtension(PropertySourceLoader loader, String extension) { + return Arrays.stream(loader.getFileExtensions()) + .anyMatch((fileExtension) -> StringUtils.endsWithIgnoreCase(extension, + fileExtension)); + } + private static class ParserHandler { private static final DaprCloudConfigParserHandler HANDLER = new DaprCloudConfigParserHandler(); diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigType.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigType.java index d903ca2723..832cb3b78b 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigType.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigType.java @@ -1,17 +1,19 @@ package io.dapr.spring.boot.cloudconfig.configdata; public enum DaprCloudConfigType { - Doc, + DocProperties, + DocYaml, Value; /** * Get Type from String. * @param value type specified in schema + * @param docType type of doc (if specified) * @return type enum */ - public static DaprCloudConfigType fromString(String value) { + public static DaprCloudConfigType fromString(String value, String docType) { return "doc".equals(value) - ? DaprCloudConfigType.Doc + ? ("yaml".equals(docType) ? DaprCloudConfigType.DocYaml : DaprCloudConfigType.DocProperties) : DaprCloudConfigType.Value; } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java index f185bde330..c97b6c8cd0 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLoader.java @@ -78,7 +78,7 @@ public ConfigData load(ConfigDataLoaderContext context, DaprConfigurationConfigD DaprCloudConfigClientManager daprClientSecretStoreConfigManager = getBean(context, DaprCloudConfigClientManager.class); - daprClient = daprClientSecretStoreConfigManager.getDaprClient(); + daprClient = DaprCloudConfigClientManager.getDaprClient(); daprCloudConfigProperties = daprClientSecretStoreConfigManager.getDaprCloudConfigProperties(); if (!daprCloudConfigProperties.getEnabled()) { @@ -121,17 +121,16 @@ private ConfigData fetchConfig(DaprConfigurationConfigDataResource resource) throw new ConfigDataResourceNotFoundException(resource); } - List> sourceList = new ArrayList<>(); - Map configMap = new HashMap<>(); secretMap.forEach((key, value) -> { configMap.put(value.getKey(), value.getValue()); }); - sourceList.addAll(DaprCloudConfigParserHandler.getInstance().parseDaprSecretStoreData( - resource.getStoreName(), - configMap, - resource.getType() + List> sourceList = + new ArrayList<>(DaprCloudConfigParserHandler.getInstance().parseDaprCloudConfigData( + resource.getStoreName(), + configMap, + resource.getType() )); return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java index 10a3384c51..6ea4d8a76f 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java @@ -100,7 +100,8 @@ public List resolve(ConfigDataLocationResol : null; MultiValueMap configQuery = configUri.getQueryParams(); - DaprCloudConfigType configType = DaprCloudConfigType.fromString(configQuery.getFirst("type")); + DaprCloudConfigType configType = DaprCloudConfigType.fromString(configQuery.getFirst("type"), + configQuery.getFirst("doc-type")); Boolean subscribe = StringUtils.hasText(configQuery.getFirst("subscribe")) && Boolean.parseBoolean(configQuery.getFirst("subscribe")); diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java index 1d07f4a20d..aaa094a74f 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLoader.java @@ -74,7 +74,7 @@ public ConfigData load(ConfigDataLoaderContext context, DaprSecretStoreConfigDat DaprCloudConfigClientManager daprCloudConfigClientManager = getBean(context, DaprCloudConfigClientManager.class); - daprClient = daprCloudConfigClientManager.getDaprClient(); + daprClient = DaprCloudConfigClientManager.getDaprClient(); daprCloudConfigProperties = daprCloudConfigClientManager.getDaprCloudConfigProperties(); if (!daprCloudConfigProperties.getEnabled()) { @@ -127,13 +127,15 @@ private ConfigData fetchBulkSecret(DaprSecretStoreConfigDataResource resource) List> sourceList = new ArrayList<>(); for (Map.Entry> entry : secretMap.entrySet()) { - sourceList.addAll(DaprCloudConfigParserHandler.getInstance().parseDaprSecretStoreData( + sourceList.addAll(DaprCloudConfigParserHandler.getInstance().parseDaprCloudConfigData( resource.getStoreName() + ":" + entry.getKey(), entry.getValue(), resource.getType() )); } + log.debug(String.format("now gain %d data source in secret, storename = %s", + sourceList.size(), resource.getStoreName())); return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); } catch (RuntimeException e) { log.info("Failed to get secret from sidecar: " + e.getMessage(), e); @@ -160,13 +162,18 @@ private ConfigData fetchSecret(DaprSecretStoreConfigDataResource resource) throw new ConfigDataResourceNotFoundException(resource); } + log.debug(String.format("now gain %d secretMap in secret, storename = %s, secretname = %s", + secretMap.size(), resource.getStoreName(), resource.getSecretName())); + List> sourceList = new ArrayList<>( - DaprCloudConfigParserHandler.getInstance().parseDaprSecretStoreData( + DaprCloudConfigParserHandler.getInstance().parseDaprCloudConfigData( resource.getStoreName() + ":" + resource.getSecretName(), secretMap, resource.getType() )); + log.debug(String.format("now gain %d data source in secret, storename = %s, secretname = %s", + sourceList.size(), resource.getStoreName(), resource.getSecretName())); return new ConfigData(sourceList, IGNORE_IMPORTS, IGNORE_PROFILES, PROFILE_SPECIFIC); } catch (RuntimeException e) { log.info("Failed to get secret from sidecar: " + e.getMessage(), e); diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java index e332260dfe..eebf3be828 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java @@ -31,6 +31,7 @@ import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.Ordered; +import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -97,8 +98,10 @@ public List resolve(ConfigDataLocationResolve ? StringUtils.trimLeadingCharacter(secretPath, '/') : null; - String typeQuery = configUri.getQueryParams().getFirst("type"); - DaprCloudConfigType secretType = DaprCloudConfigType.fromString(typeQuery); + + MultiValueMap typeQuery = configUri.getQueryParams(); + DaprCloudConfigType secretType = DaprCloudConfigType.fromString(typeQuery.getFirst("type"), + typeQuery.getFirst("doc-type")); if (secretName == null) { log.debug("Dapr Secret Store now gains store name: '" + storeName + "' secret store for config"); diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories index 3214bed85b..3050977cc1 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/resources/META-INF/spring.factories @@ -1,9 +1,9 @@ # ConfigData Location Resolvers org.springframework.boot.context.config.ConfigDataLocationResolver=\ -io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLocationResolver,\ -io.dapr.spring.boot.cloudconfig.configdata.config.DaprConfigurationConfigDataLocationResolver + io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLocationResolver,\ + io.dapr.spring.boot.cloudconfig.configdata.config.DaprConfigurationConfigDataLocationResolver # ConfigData Loaders org.springframework.boot.context.config.ConfigDataLoader=\ -io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLoader,\ -io.dapr.spring.boot.cloudconfig.configdata.config.DaprConfigurationConfigDataLoader + io.dapr.spring.boot.cloudconfig.configdata.secret.DaprSecretStoreConfigDataLoader,\ + io.dapr.spring.boot.cloudconfig.configdata.config.DaprConfigurationConfigDataLoader diff --git a/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTests.java b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTests.java index b1b1f128f9..cbb1891a76 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTests.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTests.java @@ -1,16 +1,58 @@ package io.dapr.spring.boot.cloudconfig; +import io.dapr.client.DaprClient; +import io.dapr.client.domain.ConfigurationItem; +import io.dapr.client.domain.GetConfigurationRequest; +import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; import io.dapr.spring.boot.cloudconfig.config.MultipleConfig; import io.dapr.spring.boot.cloudconfig.config.SingleConfig; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.util.ReflectionTestUtils; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; @SpringBootTest(classes = {CloudConfigTestApplication.class, MultipleConfig.class, SingleConfig.class}) public class CloudConfigTests { + static { + try { + DaprClient daprClient = Mockito.mock(DaprClient.class); + Mockito.when(daprClient.waitForSidecar(Mockito.anyInt())).thenReturn(Mono.empty()); + + Map multiValueProperties = new HashMap<>(); + multiValueProperties.put("multivalue-properties", "dapr.spring.democonfigsecret.multivalue.v1=spring\ndapr.spring.democonfigsecret.multivalue.v2=dapr"); + Mockito.when(daprClient.getSecret(Mockito.eq("democonfig"), Mockito.eq("multivalue-properties"))).thenReturn(Mono.just(multiValueProperties)); + + Map singleValueProperties = new HashMap<>(); + singleValueProperties.put("dapr.spring.democonfigsecret.singlevalue", "testvalue"); + Mockito.when(daprClient.getSecret(Mockito.eq("democonfig"), Mockito.eq("dapr.spring.democonfigsecret.singlevalue"))).thenReturn(Mono.just(singleValueProperties)); + + Map singleValueConfigurationItems = new HashMap<>(); + singleValueConfigurationItems.put("dapr.spring.democonfigconfig.singlevalue", new ConfigurationItem("dapr.spring.democonfigconfig.singlevalue", "testvalue", "")); + Mockito.when(daprClient.getConfiguration(Mockito.refEq(new GetConfigurationRequest("democonfigconf", List.of("dapr.spring.democonfigconfig.singlevalue")), "metadata"))).thenReturn(Mono.just(singleValueConfigurationItems)); + + Map multiValueConfigurationItems = new HashMap<>(); + multiValueConfigurationItems.put("multivalue-yaml", new ConfigurationItem("multivalue-yaml", "dapr:\n spring:\n democonfigconfig:\n multivalue:\n v3: cloud", "")); + Mockito.when(daprClient.getConfiguration(Mockito.refEq(new GetConfigurationRequest("democonfigconf", List.of("multivalue-yaml")), "metadata"))).thenReturn(Mono.just(multiValueConfigurationItems)); + + ReflectionTestUtils.setField(DaprCloudConfigClientManager.class, "daprClient", + daprClient); + + } + catch (Exception ignore) { + ignore.printStackTrace(); + + } + } + @Autowired MultipleConfig multipleConfig; @@ -20,11 +62,9 @@ public class CloudConfigTests { @Test public void testSecretStoreConfig() { assertEquals("testvalue", singleConfig.getSingleValueSecret()); - assertEquals("config", singleConfig.getMultiValuedSecret()); assertEquals("spring", multipleConfig.getMultipleSecretConfigV1()); assertEquals("dapr", multipleConfig.getMultipleSecretConfigV2()); - } @Test diff --git a/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/MultipleConfig.java b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/MultipleConfig.java index f7d9a6dcb2..a3bf7328df 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/MultipleConfig.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/MultipleConfig.java @@ -7,14 +7,14 @@ public class MultipleConfig { //should be spring - @Value("dapr.spring.demo-config-secret.multivalue.v1") + @Value("${dapr.spring.democonfigsecret.multivalue.v1}") private String multipleSecretConfigV1; //should be dapr - @Value("dapr.spring.demo-config-secret.multivalue.v2") + @Value("${dapr.spring.democonfigsecret.multivalue.v2}") private String multipleSecretConfigV2; //should be cloud - @Value("dapr.spring.demo-config-config.multivalue.v3") + @Value("${dapr.spring.democonfigconfig.multivalue.v3}") private String multipleConfigurationConfigV3; public String getMultipleSecretConfigV1() { diff --git a/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/SingleConfig.java b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/SingleConfig.java index 67cb002438..d18cc0d3a5 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/SingleConfig.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/config/SingleConfig.java @@ -7,15 +7,11 @@ public class SingleConfig { // should be testvalue - @Value("dapr.spring.demo-config-secret.singlevalue") + @Value("${dapr.spring.democonfigsecret.singlevalue}") private String singleValueSecret; - // should be config - @Value("dapr.spring.demo-config-secret.multivalue.v4") - private String multiValuedSecret; - // should be testvalue - @Value("dapr.spring.demo-config-config.singlevalue") + @Value("${dapr.spring.democonfigconfig.singlevalue}") private String singleValueConfig; public String getSingleValueSecret() { @@ -26,14 +22,6 @@ public void setSingleValueSecret(String singleValueSecret) { this.singleValueSecret = singleValueSecret; } - public String getMultiValuedSecret() { - return multiValuedSecret; - } - - public void setMultiValuedSecret(String multiValuedSecret) { - this.multiValuedSecret = multiValuedSecret; - } - public String getSingleValueConfig() { return singleValueConfig; } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml b/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml index 6cd67e7380..b84f1ed04e 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml +++ b/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml @@ -4,13 +4,18 @@ spring: config: import: - dapr:secret:democonfig/multivalue-properties?type=doc - - dapr:secret:democonfig/dapr.spring.demo-config.singlevalue?type=value - - dapr:secret:democonfigmulti/value1?type=value - - dapr:config:democonfigconf/dapr.spring.demo-config-config.singlevalue?type=value - - dapr:config:democonfigconf/multivalue-yaml?type=doc + - dapr:secret:democonfig/dapr.spring.democonfigsecret.singlevalue?type=value + #- dapr:secret:democonfigmulti/value1?type=value + - dapr:config:democonfigconf/dapr.spring.democonfigconfig.singlevalue?type=value + - dapr:config:democonfigconf/multivalue-yaml?type=doc&doc-type=yaml + dapr: cloudconfig: enabled: true # in case some connection issue wait-sidecar-enabled: true + +logging: + level: + root: debug From 3359473acf22bca7004f08fdc3d512e72fbd0704 Mon Sep 17 00:00:00 2001 From: lony2003 Date: Sat, 8 Mar 2025 10:00:50 +0800 Subject: [PATCH 09/22] feat(springboot cloudconfig): Change the behavior of Config Type Enum of Config Type has lack of file extension suport, reimplement it as class Signed-off-by: lony2003 --- .../DaprCloudConfigParserHandler.java | 6 ++++-- ...nfigurationConfigDataLocationResolver.java | 2 +- .../DaprConfigurationConfigDataResource.java | 2 +- ...SecretStoreConfigDataLocationResolver.java | 2 +- .../DaprSecretStoreConfigDataResource.java | 2 +- .../{ => types}/DaprCloudConfigType.java | 12 +++++------ .../cloudconfig/configdata/types/DocType.java | 20 +++++++++++++++++++ .../configdata/types/ValueType.java | 4 ++++ spotbugs-exclude.xml | 6 ++++++ 9 files changed, 43 insertions(+), 13 deletions(-) rename dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/{ => types}/DaprCloudConfigType.java (50%) create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/types/DocType.java create mode 100644 dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/types/ValueType.java diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigParserHandler.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigParserHandler.java index 86942f4061..fdb58fdeb1 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigParserHandler.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigParserHandler.java @@ -13,6 +13,8 @@ package io.dapr.spring.boot.cloudconfig.configdata; +import io.dapr.spring.boot.cloudconfig.configdata.types.DaprCloudConfigType; +import io.dapr.spring.boot.cloudconfig.configdata.types.DocType; import org.springframework.boot.env.PropertySourceLoader; import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.core.env.PropertySource; @@ -83,7 +85,7 @@ public List> parseDaprCloudConfigData( List> result = new ArrayList<>(); Map configResults = getConfigResult(configValue, type); - String extension = DaprCloudConfigType.DocYaml.equals(type) ? ".yaml" : ".properties"; + String extension = type instanceof DocType ? ((DocType) type).getDocExtension() : ".properties"; configResults.forEach((key, configResult) -> { for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) { @@ -108,7 +110,7 @@ private Map getConfigResult( DaprCloudConfigType type ) { Map result = new HashMap<>(); - if (DaprCloudConfigType.DocYaml.equals(type) || DaprCloudConfigType.DocProperties.equals(type)) { + if (type instanceof DocType) { configValue.forEach((key, value) -> result.put(key, new ByteArrayResource(value.getBytes(StandardCharsets.UTF_8)))); } else { diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java index 6ea4d8a76f..a650064696 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataLocationResolver.java @@ -16,7 +16,7 @@ import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; -import io.dapr.spring.boot.cloudconfig.configdata.DaprCloudConfigType; +import io.dapr.spring.boot.cloudconfig.configdata.types.DaprCloudConfigType; import org.apache.commons.logging.Log; import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.ConfigurableBootstrapContext; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java index 662287043c..5d94cb10b9 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/config/DaprConfigurationConfigDataResource.java @@ -13,7 +13,7 @@ package io.dapr.spring.boot.cloudconfig.configdata.config; -import io.dapr.spring.boot.cloudconfig.configdata.DaprCloudConfigType; +import io.dapr.spring.boot.cloudconfig.configdata.types.DaprCloudConfigType; import org.springframework.boot.context.config.ConfigDataResource; import org.springframework.lang.Nullable; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java index eebf3be828..41070ea341 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataLocationResolver.java @@ -16,7 +16,7 @@ import io.dapr.spring.boot.autoconfigure.client.DaprClientProperties; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigClientManager; import io.dapr.spring.boot.cloudconfig.config.DaprCloudConfigProperties; -import io.dapr.spring.boot.cloudconfig.configdata.DaprCloudConfigType; +import io.dapr.spring.boot.cloudconfig.configdata.types.DaprCloudConfigType; import org.apache.commons.logging.Log; import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.ConfigurableBootstrapContext; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java index 71a4d41d13..c87fee75cd 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/secret/DaprSecretStoreConfigDataResource.java @@ -13,7 +13,7 @@ package io.dapr.spring.boot.cloudconfig.configdata.secret; -import io.dapr.spring.boot.cloudconfig.configdata.DaprCloudConfigType; +import io.dapr.spring.boot.cloudconfig.configdata.types.DaprCloudConfigType; import org.springframework.boot.context.config.ConfigDataResource; import org.springframework.lang.Nullable; diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigType.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/types/DaprCloudConfigType.java similarity index 50% rename from dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigType.java rename to dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/types/DaprCloudConfigType.java index 832cb3b78b..527f5cfe20 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/DaprCloudConfigType.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/types/DaprCloudConfigType.java @@ -1,10 +1,8 @@ -package io.dapr.spring.boot.cloudconfig.configdata; +package io.dapr.spring.boot.cloudconfig.configdata.types; -public enum DaprCloudConfigType { - DocProperties, - DocYaml, - Value; +import org.springframework.util.StringUtils; +public class DaprCloudConfigType { /** * Get Type from String. * @param value type specified in schema @@ -13,7 +11,7 @@ public enum DaprCloudConfigType { */ public static DaprCloudConfigType fromString(String value, String docType) { return "doc".equals(value) - ? ("yaml".equals(docType) ? DaprCloudConfigType.DocYaml : DaprCloudConfigType.DocProperties) - : DaprCloudConfigType.Value; + ? new DocType(StringUtils.hasText(docType) ? docType : "properties") + : new ValueType(); } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/types/DocType.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/types/DocType.java new file mode 100644 index 0000000000..31928c627d --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/types/DocType.java @@ -0,0 +1,20 @@ +package io.dapr.spring.boot.cloudconfig.configdata.types; + +import org.springframework.util.StringUtils; + +public class DocType extends DaprCloudConfigType { + private final String docType; + + public DocType(String docType) { + this.docType = StringUtils.hasText(docType) ? docType : "properties"; + } + + public String getDocType() { + return docType; + } + + public String getDocExtension() { + String type = getDocType(); + return "." + StringUtils.trimLeadingCharacter(type, '.'); + } +} diff --git a/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/types/ValueType.java b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/types/ValueType.java new file mode 100644 index 0000000000..bd1f68d519 --- /dev/null +++ b/dapr-spring/dapr-spring-cloudconfig/src/main/java/io/dapr/spring/boot/cloudconfig/configdata/types/ValueType.java @@ -0,0 +1,4 @@ +package io.dapr.spring.boot.cloudconfig.configdata.types; + +public class ValueType extends DaprCloudConfigType { +} diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index f0111ca638..35f66c7d95 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -51,4 +51,10 @@ + + + + + + \ No newline at end of file From b7e660ede98a979cddb23cdb36b80bff59e4a6d7 Mon Sep 17 00:00:00 2001 From: lony2003 Date: Sat, 8 Mar 2025 11:53:43 +0800 Subject: [PATCH 10/22] test(springboot cloudconfig): Implemented integration test for secret and config store Signed-off-by: lony2003 --- .../components/secret-spring/multivalued.json | 13 +++ .../secret-spring/singlevalued.json | 5 + sdk-tests/components/secret.json | 2 +- .../secretstore-springboot-multivalued.yaml | 14 +++ .../secretstore-springboot-singlevalued.yaml | 12 +++ sdk-tests/pom.xml | 11 +++ .../spring/cloudconfig/DaprCloudConfigIT.java | 96 +++++++++++++++++++ .../cloudconfig/DaprConfigurationStores.java | 5 + .../spring/cloudconfig/DaprSecretStoreIT.java | 91 ++++++++++++++++++ .../spring/cloudconfig/DaprSecretStores.java | 23 +++++ .../TestDaprCloudConfigConfiguration.java | 11 +++ 11 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 sdk-tests/components/secret-spring/multivalued.json create mode 100644 sdk-tests/components/secret-spring/singlevalued.json create mode 100644 sdk-tests/components/secretstore-springboot-multivalued.yaml create mode 100644 sdk-tests/components/secretstore-springboot-singlevalued.yaml create mode 100644 sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprCloudConfigIT.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprConfigurationStores.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStoreIT.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStores.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/TestDaprCloudConfigConfiguration.java diff --git a/sdk-tests/components/secret-spring/multivalued.json b/sdk-tests/components/secret-spring/multivalued.json new file mode 100644 index 0000000000..8cfa56e9ac --- /dev/null +++ b/sdk-tests/components/secret-spring/multivalued.json @@ -0,0 +1,13 @@ +{ + "value1": { + "dapr": { + "spring": { + "demo-config-secret": { + "multivalue": { + "v4": "config" + } + } + } + } + } +} \ No newline at end of file diff --git a/sdk-tests/components/secret-spring/singlevalued.json b/sdk-tests/components/secret-spring/singlevalued.json new file mode 100644 index 0000000000..6e9fd591e5 --- /dev/null +++ b/sdk-tests/components/secret-spring/singlevalued.json @@ -0,0 +1,5 @@ +{ + "dapr.spring.demo-config-secret.singlevalue": "testvalue", + "multivalue-properties": "dapr.spring.demo-config-secret.multivalue.v1=spring\ndapr.spring.demo-config-secret.multivalue.v2=dapr", + "multivalue-yaml": "dapr:\n spring:\n demo-config-secret:\n multivalue:\n v3: cloud" +} \ No newline at end of file diff --git a/sdk-tests/components/secret.json b/sdk-tests/components/secret.json index 9e26dfeeb6..9eeb959bb4 100644 --- a/sdk-tests/components/secret.json +++ b/sdk-tests/components/secret.json @@ -1 +1 @@ -{} \ No newline at end of file +{"94022f35-25ab-4ffa-a4a0-9b45b602b50c":{"name":"Jon Doe"},"e1f219a2-2689-4689-a18d-372bdf989b05":{"year":"2020","title":"The Metrics IV"}} \ No newline at end of file diff --git a/sdk-tests/components/secretstore-springboot-multivalued.yaml b/sdk-tests/components/secretstore-springboot-multivalued.yaml new file mode 100644 index 0000000000..24ac13d589 --- /dev/null +++ b/sdk-tests/components/secretstore-springboot-multivalued.yaml @@ -0,0 +1,14 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: democonfigMultivalued +spec: + type: secretstores.local.file + version: v1 + metadata: + - name: secretsFile + value: "./components/secret-spring/multivalued.json" + - name: nestedSeparator + value: "." + - name: multiValued + value: "true" diff --git a/sdk-tests/components/secretstore-springboot-singlevalued.yaml b/sdk-tests/components/secretstore-springboot-singlevalued.yaml new file mode 100644 index 0000000000..17a8580f54 --- /dev/null +++ b/sdk-tests/components/secretstore-springboot-singlevalued.yaml @@ -0,0 +1,12 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: democonfig +spec: + type: secretstores.local.file + version: v1 + metadata: + - name: secretsFile + value: "./components/secret-spring/singlevalued.json" + - name: multiValued + value: "false" diff --git a/sdk-tests/pom.xml b/sdk-tests/pom.xml index c1ffacad0e..0cb9f17897 100644 --- a/sdk-tests/pom.xml +++ b/sdk-tests/pom.xml @@ -69,6 +69,11 @@ commons-cli 1.9.0 + + redis.clients + jedis + 5.2.0 + io.grpc grpc-protobuf @@ -228,6 +233,12 @@ ${testcontainers-test.version} test + + com.redis + testcontainers-redis + 2.2.4 + test + jakarta.annotation jakarta.annotation-api diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprCloudConfigIT.java b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprCloudConfigIT.java new file mode 100644 index 0000000000..16aceb0b53 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprCloudConfigIT.java @@ -0,0 +1,96 @@ +package io.dapr.it.spring.cloudconfig; + +import com.github.dockerjava.api.command.InspectContainerResponse; +import com.redis.testcontainers.RedisContainer; +import io.dapr.testcontainers.Component; +import io.dapr.testcontainers.DaprContainer; +import io.dapr.testcontainers.DaprLogLevel; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.testcontainers.containers.Network; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import redis.clients.jedis.Jedis; + +import java.util.Map; + +import static io.dapr.it.testcontainers.DaprContainerConstants.IMAGE_TAG; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(properties = { + "spring.config.import[0]=dapr:config:" + DaprCloudConfigIT.CONFIG_STORE_NAME + + "/" + DaprCloudConfigIT.CONFIG_MULTI_NAME + "?type=doc&doc-type=yaml", + "spring.config.import[1]=dapr:config:" + DaprCloudConfigIT.CONFIG_STORE_NAME + + "/" + DaprCloudConfigIT.CONFIG_SINGLE_NAME + "?type=value", +}) +@ContextConfiguration(classes = TestDaprCloudConfigConfiguration.class) +@ExtendWith(SpringExtension.class) +@Testcontainers +@Tag("testcontainers") +public class DaprCloudConfigIT { + public static final String CONFIG_STORE_NAME = "democonfigconf"; + public static final String CONFIG_MULTI_NAME = "multivalue-yaml"; + public static final String CONFIG_SINGLE_NAME = "dapr.spring.demo-config-config.singlevalue"; + + private static final Map STORE_PROPERTY = generateStoreProperty(); + + private static final Network DAPR_NETWORK = Network.newNetwork(); + + private static final RedisContainer REDIS_CONTAINER = new RedisContainer( + RedisContainer.DEFAULT_IMAGE_NAME.withTag(RedisContainer.DEFAULT_TAG)) { + @Override + protected void containerIsStarted(InspectContainerResponse containerInfo) { + super.containerIsStarted(containerInfo); + + String address = getHost(); + Integer port = getMappedPort(6379); + + Logger logger = LoggerFactory.getLogger(DaprCloudConfigIT.class); + // Put values using Jedis + try (Jedis jedis = new Jedis(address, port)) { + logger.info("Putting Dapr Cloud config to {}:{}", address, port); + jedis.set(DaprCloudConfigIT.CONFIG_MULTI_NAME, DaprConfigurationStores.YAML_CONFIG); + jedis.set(DaprCloudConfigIT.CONFIG_SINGLE_NAME, "testvalue"); + } + } + } + .withNetworkAliases("redis") + .withCommand() + .withNetwork(DAPR_NETWORK); + + @Container + @ServiceConnection + private static final DaprContainer DAPR_CONTAINER = new DaprContainer(IMAGE_TAG) + .withAppName("secret-store-dapr-app") + .withNetwork(DAPR_NETWORK) + .withComponent(new Component(CONFIG_STORE_NAME, "configuration.redis", "v1", STORE_PROPERTY)) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) + .dependsOn(REDIS_CONTAINER); + + private static Map generateStoreProperty() { + return Map.of("redisHost", "redis:6379", + "redisPassword", ""); + } + + @Value("${dapr.spring.demo-config-config.singlevalue}") + String valueConfig; + + @Value("${dapr.spring.demo-config-config.multivalue.v3}") + String yamlConfig; + + @Test + public void configTest() { + assertEquals("testvalue", valueConfig); + assertEquals("cloud", yamlConfig); + } + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprConfigurationStores.java b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprConfigurationStores.java new file mode 100644 index 0000000000..788904a2ce --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprConfigurationStores.java @@ -0,0 +1,5 @@ +package io.dapr.it.spring.cloudconfig; + +public class DaprConfigurationStores { + public static final String YAML_CONFIG = "dapr:\\n spring:\\n demo-config-config:\\n multivalue:\\n v3: cloud"; +} diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStoreIT.java b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStoreIT.java new file mode 100644 index 0000000000..69da223fa8 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStoreIT.java @@ -0,0 +1,91 @@ +package io.dapr.it.spring.cloudconfig; + +import io.dapr.testcontainers.Component; +import io.dapr.testcontainers.DaprContainer; +import io.dapr.testcontainers.DaprLogLevel; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.testcontainers.containers.Network; +import org.testcontainers.images.builder.Transferable; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.Map; + +import static io.dapr.it.testcontainers.DaprContainerConstants.IMAGE_TAG; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(properties = { + "spring.config.import[0]=dapr:secret:" + DaprSecretStoreIT.SECRET_STORE_NAME + + "/" + DaprSecretStoreIT.SECRET_MULTI_NAME + "?type=doc", + "spring.config.import[1]=dapr:secret:" + DaprSecretStoreIT.SECRET_STORE_NAME + + "/" + DaprSecretStoreIT.SECRET_SINGLE_NAME + "?type=value", + "spring.config.import[2]=dapr:secret:" + DaprSecretStoreIT.SECRET_STORE_NAME_MULTI + + "?type=value", +}) +@ContextConfiguration(classes = TestDaprCloudConfigConfiguration.class) +@ExtendWith(SpringExtension.class) +@Testcontainers +@Tag("testcontainers") +public class DaprSecretStoreIT { + public static final String SECRET_STORE_NAME = "democonfig"; + public static final String SECRET_MULTI_NAME = "multivalue-properties"; + public static final String SECRET_SINGLE_NAME = "dapr.spring.demo-config-secret.singlevalue"; + + public static final String SECRET_STORE_NAME_MULTI = "democonfigMultivalued"; + + private static final Map SINGLE_VALUE_PROPERTY = generateSingleValueProperty(); + private static final Map MULTI_VALUE_PROPERTY = generateMultiValueProperty(); + + private static final Network DAPR_NETWORK = Network.newNetwork(); + + @Container + @ServiceConnection + private static final DaprContainer DAPR_CONTAINER = new DaprContainer(IMAGE_TAG) + .withAppName("secret-store-dapr-app") + .withComponent(new Component(SECRET_STORE_NAME, "secretstores.local.file", "v1", SINGLE_VALUE_PROPERTY)) + .withComponent(new Component(SECRET_STORE_NAME_MULTI, "secretstores.local.file", "v1", MULTI_VALUE_PROPERTY)) + .withNetwork(DAPR_NETWORK) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) + .withCopyToContainer(Transferable.of(DaprSecretStores.SINGLE_VALUED_SECRET), "/dapr-secrets/singlevalued.json") + .withCopyToContainer(Transferable.of(DaprSecretStores.MULTI_VALUED_SECRET), "/dapr-secrets/multivalued.json"); + + private static Map generateSingleValueProperty() { + return Map.of("secretsFile", "/dapr-secrets/singlevalued.json", + "multiValued", "false"); + } + + private static Map generateMultiValueProperty() { + return Map.of("secretsFile", "/dapr-secrets/multivalued.json", + "nestedSeparator", ".", + "multiValued", "true"); + } + + @Value("${dapr.spring.demo-config-secret.singlevalue}") + String singleValue; + + @Value("${dapr.spring.demo-config-secret.multivalue.v1}") + String multiV1; + + @Value("${dapr.spring.demo-config-secret.multivalue.v2}") + String multiV2; + + @Value("${dapr.spring.demo-config-secret.multivalue.v4}") + String multiV4; + + @Test + public void testSecretStore() { + assertEquals("testvalue", singleValue); + assertEquals("spring", multiV1); + assertEquals("dapr", multiV2); + assertEquals("config", multiV4); + } + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStores.java b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStores.java new file mode 100644 index 0000000000..c8fc31fdb1 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStores.java @@ -0,0 +1,23 @@ +package io.dapr.it.spring.cloudconfig; + +public class DaprSecretStores { + public static final String SINGLE_VALUED_SECRET = "{\n" + + " \"dapr.spring.democonfigsecret.singlevalue\": \"testvalue\",\n" + + " \"multivalue-properties\": \"dapr.spring.demo-config-secret.multivalue.v1=spring\\ndapr.spring.demo-config-secret.multivalue.v2=dapr\",\n" + + " \"multivalue-yaml\": \"dapr:\\n spring:\\n demo-config-secret:\\n multivalue:\\n v3: cloud\"\n" + + "}"; + + public static final String MULTI_VALUED_SECRET = "{\n" + + " \"value1\": {\n" + + " \"dapr\": {\n" + + " \"spring\": {\n" + + " \"demo-config-secret\": {\n" + + " \"multivalue\": {\n" + + " \"v4\": \"config\"\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; +} diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/TestDaprCloudConfigConfiguration.java b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/TestDaprCloudConfigConfiguration.java new file mode 100644 index 0000000000..62fb063752 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/TestDaprCloudConfigConfiguration.java @@ -0,0 +1,11 @@ +package io.dapr.it.spring.cloudconfig; + +import io.dapr.spring.boot.autoconfigure.client.DaprClientAutoConfiguration; +import io.dapr.spring.boot.cloudconfig.autoconfigure.DaprCloudConfigAutoConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import({DaprClientAutoConfiguration.class, DaprCloudConfigAutoConfiguration.class}) +public class TestDaprCloudConfigConfiguration { +} From 653bb126178c74c4900b28945c7f0808e4854840 Mon Sep 17 00:00:00 2001 From: lony2003 Date: Sat, 8 Mar 2025 22:26:29 +0800 Subject: [PATCH 11/22] test(springboot cloudconfig): fix integration test for secret and config store (try1) Signed-off-by: lony2003 --- .../it/spring/cloudconfig/DaprCloudConfigIT.java | 12 +++++++++++- .../spring/cloudconfig/DaprConfigurationStores.java | 6 +++++- .../it/spring/cloudconfig/DaprSecretStoreIT.java | 12 ++++++++++++ .../dapr/it/spring/cloudconfig/DaprSecretStores.java | 2 +- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprCloudConfigIT.java b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprCloudConfigIT.java index 16aceb0b53..a8cb4662a2 100644 --- a/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprCloudConfigIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprCloudConfigIT.java @@ -5,6 +5,7 @@ import io.dapr.testcontainers.Component; import io.dapr.testcontainers.DaprContainer; import io.dapr.testcontainers.DaprLogLevel; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -14,12 +15,15 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.testcontainers.containers.Network; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import redis.clients.jedis.Jedis; +import java.util.List; import java.util.Map; import static io.dapr.it.testcontainers.DaprContainerConstants.IMAGE_TAG; @@ -30,6 +34,8 @@ + "/" + DaprCloudConfigIT.CONFIG_MULTI_NAME + "?type=doc&doc-type=yaml", "spring.config.import[1]=dapr:config:" + DaprCloudConfigIT.CONFIG_STORE_NAME + "/" + DaprCloudConfigIT.CONFIG_SINGLE_NAME + "?type=value", + "dapr.cloudconfig.wait-sidecar-enabled=true", + "dapr.cloudconfig.wait-sidecar-retries=5", }) @ContextConfiguration(classes = TestDaprCloudConfigConfiguration.class) @ExtendWith(SpringExtension.class) @@ -69,13 +75,17 @@ protected void containerIsStarted(InspectContainerResponse containerInfo) { @Container @ServiceConnection private static final DaprContainer DAPR_CONTAINER = new DaprContainer(IMAGE_TAG) - .withAppName("secret-store-dapr-app") + .withAppName("configuration-dapr-app") .withNetwork(DAPR_NETWORK) .withComponent(new Component(CONFIG_STORE_NAME, "configuration.redis", "v1", STORE_PROPERTY)) .withDaprLogLevel(DaprLogLevel.DEBUG) .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) .dependsOn(REDIS_CONTAINER); + static { + DAPR_CONTAINER.setPortBindings(List.of("3500:3500", "50001:50001")); + } + private static Map generateStoreProperty() { return Map.of("redisHost", "redis:6379", "redisPassword", ""); diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprConfigurationStores.java b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprConfigurationStores.java index 788904a2ce..9c25dc4761 100644 --- a/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprConfigurationStores.java +++ b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprConfigurationStores.java @@ -1,5 +1,9 @@ package io.dapr.it.spring.cloudconfig; public class DaprConfigurationStores { - public static final String YAML_CONFIG = "dapr:\\n spring:\\n demo-config-config:\\n multivalue:\\n v3: cloud"; + public static final String YAML_CONFIG = "dapr:\n" + + " spring:\n" + + " demo-config-config:\n" + + " multivalue:\n" + + " v3: cloud"; } diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStoreIT.java b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStoreIT.java index 69da223fa8..d3930022a7 100644 --- a/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStoreIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStoreIT.java @@ -3,19 +3,25 @@ import io.dapr.testcontainers.Component; import io.dapr.testcontainers.DaprContainer; import io.dapr.testcontainers.DaprLogLevel; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.testcontainers.containers.Network; import org.testcontainers.images.builder.Transferable; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; +import java.util.List; import java.util.Map; import static io.dapr.it.testcontainers.DaprContainerConstants.IMAGE_TAG; @@ -28,6 +34,8 @@ + "/" + DaprSecretStoreIT.SECRET_SINGLE_NAME + "?type=value", "spring.config.import[2]=dapr:secret:" + DaprSecretStoreIT.SECRET_STORE_NAME_MULTI + "?type=value", + "dapr.cloudconfig.wait-sidecar-enabled=true", + "dapr.cloudconfig.wait-sidecar-retries=5", }) @ContextConfiguration(classes = TestDaprCloudConfigConfiguration.class) @ExtendWith(SpringExtension.class) @@ -57,6 +65,10 @@ public class DaprSecretStoreIT { .withCopyToContainer(Transferable.of(DaprSecretStores.SINGLE_VALUED_SECRET), "/dapr-secrets/singlevalued.json") .withCopyToContainer(Transferable.of(DaprSecretStores.MULTI_VALUED_SECRET), "/dapr-secrets/multivalued.json"); + static { + DAPR_CONTAINER.setPortBindings(List.of("3500:3500", "50001:50001")); + } + private static Map generateSingleValueProperty() { return Map.of("secretsFile", "/dapr-secrets/singlevalued.json", "multiValued", "false"); diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStores.java b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStores.java index c8fc31fdb1..67601b1baf 100644 --- a/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStores.java +++ b/sdk-tests/src/test/java/io/dapr/it/spring/cloudconfig/DaprSecretStores.java @@ -2,7 +2,7 @@ public class DaprSecretStores { public static final String SINGLE_VALUED_SECRET = "{\n" + - " \"dapr.spring.democonfigsecret.singlevalue\": \"testvalue\",\n" + + " \"dapr.spring.demo-config-secret.singlevalue\": \"testvalue\",\n" + " \"multivalue-properties\": \"dapr.spring.demo-config-secret.multivalue.v1=spring\\ndapr.spring.demo-config-secret.multivalue.v2=dapr\",\n" + " \"multivalue-yaml\": \"dapr:\\n spring:\\n demo-config-secret:\\n multivalue:\\n v3: cloud\"\n" + "}"; From 1169c54d1a4a88153421575adfed7d1d3fffe1a2 Mon Sep 17 00:00:00 2001 From: lony2003 Date: Sun, 9 Mar 2025 01:04:45 +0800 Subject: [PATCH 12/22] docs(springboot cloudconfig): Write examples and docs for springboot cloudconfig Signed-off-by: lony2003 --- .../en/java-sdk-docs/spring-boot/_index.md | 140 ++++++++++++++++ sdk-tests/components/secret.json | 2 +- sdk-tests/pom.xml | 1 + spring-boot-examples/README.md | 5 +- .../components/redisconfigstore.yaml | 12 ++ .../components/secret-spring/multivalued.json | 13 ++ .../secret-spring/singlevalued.json | 5 + .../secretstore-springboot-multivalued.yaml | 14 ++ .../secretstore-springboot-singlevalued.yaml | 12 ++ .../cloud-config-demo/pom.xml | 90 ++++++++++ .../cloudconfig/CloudConfigApplication.java | 27 +++ .../cloudconfig/config/MultipleConfig.java | 43 +++++ .../cloudconfig/config/SingleConfig.java | 32 ++++ .../src/main/resources/application.yaml | 12 ++ .../cloudconfig/CloudConfigTests.java | 154 ++++++++++++++++++ .../cloudconfig/DaprConfigurationStores.java | 9 + .../cloudconfig/DaprSecretStores.java | 23 +++ .../cloudconfig/DaprTestContainersConfig.java | 38 +++++ .../TestCloudConfigApplication.java | 31 ++++ spring-boot-examples/pom.xml | 1 + 20 files changed, 662 insertions(+), 2 deletions(-) create mode 100644 spring-boot-examples/cloud-config-demo/components/redisconfigstore.yaml create mode 100644 spring-boot-examples/cloud-config-demo/components/secret-spring/multivalued.json create mode 100644 spring-boot-examples/cloud-config-demo/components/secret-spring/singlevalued.json create mode 100644 spring-boot-examples/cloud-config-demo/components/secretstore-springboot-multivalued.yaml create mode 100644 spring-boot-examples/cloud-config-demo/components/secretstore-springboot-singlevalued.yaml create mode 100644 spring-boot-examples/cloud-config-demo/pom.xml create mode 100644 spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/CloudConfigApplication.java create mode 100644 spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/MultipleConfig.java create mode 100644 spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/SingleConfig.java create mode 100644 spring-boot-examples/cloud-config-demo/src/main/resources/application.yaml create mode 100644 spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java create mode 100644 spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprConfigurationStores.java create mode 100644 spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprSecretStores.java create mode 100644 spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprTestContainersConfig.java create mode 100644 spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/TestCloudConfigApplication.java diff --git a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md index 9819f8ef81..5a1079d151 100644 --- a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md +++ b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md @@ -323,6 +323,146 @@ daprWorkflowClient.raiseEvent(instanceId, "MyEvenet", event); Check the [Dapr Workflow documentation](https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-overview/) for more information about how to work with Dapr Workflows. +## Using Dapr Cloud Config with Spring Boot + +To enable dapr cloud config, you should add following properties in your application's config (properties for example): + +```properties +# default enable is true, don't need to specify +dapr.cloudconfig.enabled = true +spring.config.import[0] = +spring.config.import[1] = +spring.config.import[2] = +#... keep going if you want to import more configs +``` + +There are other config of the dapr cloud config, listed below: + +```properties +#enable dapr cloud config or not (default = true). +dapr.cloudconfig.enabled=true +#timeout for getting dapr config (include wait for dapr sidecar) (default = 2000). +dapr.cloudconfig.timeout=2000 +#whether enable dapr client wait for sidecar, if no response, will throw IOException (default = false). +dapr.cloudconfig.wait-sidecar-enabled=false +#retries of dapr client wait for sidecar (default = 3). +dapr.cloudconfig.wait-sidecar-retries=3 +``` + +In Dapr Cloud Config component, we support two ways to imprt config: Secret Store API and Configuration API. + +Both of them have their schemas, listed below. + +### Cloud Config Import Schemas + +#### Secret Store Component + +##### url structure + +`dapr:secret:[/][?]` + +###### paramters + +| parameter | description | default | available | +|--------------------|--------------------|--------------------|--------------------| +| type | value type | `value` | `value`/`doc`| +| doc-type | type of doc | `properties` | `yaml`/`properties`/`or any file extensions you want`| + +- when type = `value`, if `secret-name` is specified, will treat secret as the value of property, and `secret-name` as the key of property; if none `secret-name` is specified, will get bulk secret and treat every value of secret as the value of property, and every key of secret as the key of property. +- when type = `doc`, if `secret-name` is specified, will treat secret as a bunch of property, and load it with property or yaml loader; if none `secret-name` is specified, will get bulk secret and and treat every value of secret as bunches of property, and load them with property or yaml loader. +- secret store with multiValud = true must specify nestedSeparator = ".", and using type = `doc` is not recommanded + +##### demo + +###### multiValued = false: + +####### store content(file secret store as example) + +```json +{ + "dapr.spring.demo-config-secret.singlevalue": "testvalue", + "multivalue-properties": "dapr.spring.demo-config-secret.multivalue.v1=spring\ndapr.spring.demo-config-secret.multivalue.v2=dapr", + "multivalue-yaml": "dapr:\n spring:\n demo-config-secret:\n multivalue:\n v3: cloud" +} +``` + +####### valid demo url + +- `dapr:secret:democonfig/multivalue-properties?type=doc&doc-type=properties` +- `dapr:secret:democonfig/multivalue-yaml?type=doc&doc-type=yaml` +- `dapr:secret:democonfig/dapr.spring.demo-config.singlevalue?type=value` +- `dapr:secret:democonfig?type=value` +- `dapr:secret:democonfig?type=doc` + +###### multiValued = true, nestedSeparator = ".": + +####### store content(file secret store as example) + +```json +{ + "value1": { + "dapr": { + "spring": { + "demo-config-secret": { + "multivalue": { + "v4": "config" + } + } + } + } + } +} +``` + +will be read as + +```json +{ + "value1": { + "dapr.spring.demo-config-secret.multivalue.v4": "config" + } +} +``` + +####### valid demo url +- `dapr:secret:demo-config-multi/value1?type=value` +- `dapr:secret:demo-config-multi?type=value` + +#### Configuration Component + +##### url structure + +`dapr:config:[/][?]` + +###### paramters + +| parameter | description | default | available | +|--------------------|--------------------|--------------------|--------------------| +| type | value type | `value` | `doc`/`value` | +| doc-type | type of doc | `properties` | `yaml`/`properties`/`or any file extensions you want`| +| subscribe | subscribe this configuration | `false` | `true`/`false` | + +- when subscribe = `true`, will subscribe update for the configuration. +- when type = `value`, if `key` is specified, will treat config value as the value of property, and `key` as the key of property; if none `key` is specified, will get all key and value in the `config-name` and treat every config value as the value of property, and every `key` as the key of property. +- when type = `doc`, if `key` is specified, will treat config value as a bunch of property, and load it with property or yaml loader; if none `key` is specified, will get all key and value in the `config-name` and treat every config value as bunches of property, and load them with property or yaml loader. + +##### Demo + +###### store content(table as example) + +| key | value | +|--------------------|--------------------| +| dapr.spring.demo-config-config.singlevalue | testvalue | +| multivalue-properties | `dapr.spring.demo-config-config.multivalue.v1=spring\ndapr.spring.demo-config-config.multivalue.v2=dapr` | +| multivalue-yaml | `dapr:\n spring:\n demo-config-config:\n multivalue:\n v3: cloud` | + +###### valid demo url + +- `dapr:config:democonfigconf/dapr.spring.demo-config-config.singlevalue?type=value` +- `dapr:config:democonfigconf/multivalue-properties?type=doc&doc-type=properties` +- `dapr:config:democonfigconf/multivalue-yaml?type=doc&doc-type=yaml` +- `dapr:config:democonfigconf?type=doc` +- `dapr:config:democonfigconf?type=value` ## Next steps diff --git a/sdk-tests/components/secret.json b/sdk-tests/components/secret.json index 9eeb959bb4..bd604a6a0f 100644 --- a/sdk-tests/components/secret.json +++ b/sdk-tests/components/secret.json @@ -1 +1 @@ -{"94022f35-25ab-4ffa-a4a0-9b45b602b50c":{"name":"Jon Doe"},"e1f219a2-2689-4689-a18d-372bdf989b05":{"year":"2020","title":"The Metrics IV"}} \ No newline at end of file +{"589f54ec-d0b7-40b5-92d9-6ab7ddef3c4f":{"name":"Jon Doe"},"969595f8-859c-4f7d-ae6a-864704f94a10":{"year":"2020","title":"The Metrics IV"}} \ No newline at end of file diff --git a/sdk-tests/pom.xml b/sdk-tests/pom.xml index 0cb9f17897..50df90e7ba 100644 --- a/sdk-tests/pom.xml +++ b/sdk-tests/pom.xml @@ -73,6 +73,7 @@ redis.clients jedis 5.2.0 + test io.grpc diff --git a/spring-boot-examples/README.md b/spring-boot-examples/README.md index 3cc88610de..d24f723e64 100644 --- a/spring-boot-examples/README.md +++ b/spring-boot-examples/README.md @@ -1,12 +1,15 @@ # Dapr Spring Boot and Testcontainers integration Example -This example consists of two applications: +This example consists of three applications: - Producer App: - Publish messages using a Spring Messaging approach - Store and retrieve information using Spring Data CrudRepository - Implements a Workflow with Dapr Workflows - Consumer App: - Subscribe to messages +- Cloud Config Demo: + - Import and use configs + - Can not run in Kubernetes currently, as lack of redis pre-fill data supported ## Running these examples from source code diff --git a/spring-boot-examples/cloud-config-demo/components/redisconfigstore.yaml b/spring-boot-examples/cloud-config-demo/components/redisconfigstore.yaml new file mode 100644 index 0000000000..6b1f3f799f --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/components/redisconfigstore.yaml @@ -0,0 +1,12 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: democonfigconf +spec: + type: configuration.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" diff --git a/spring-boot-examples/cloud-config-demo/components/secret-spring/multivalued.json b/spring-boot-examples/cloud-config-demo/components/secret-spring/multivalued.json new file mode 100644 index 0000000000..8cfa56e9ac --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/components/secret-spring/multivalued.json @@ -0,0 +1,13 @@ +{ + "value1": { + "dapr": { + "spring": { + "demo-config-secret": { + "multivalue": { + "v4": "config" + } + } + } + } + } +} \ No newline at end of file diff --git a/spring-boot-examples/cloud-config-demo/components/secret-spring/singlevalued.json b/spring-boot-examples/cloud-config-demo/components/secret-spring/singlevalued.json new file mode 100644 index 0000000000..6e9fd591e5 --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/components/secret-spring/singlevalued.json @@ -0,0 +1,5 @@ +{ + "dapr.spring.demo-config-secret.singlevalue": "testvalue", + "multivalue-properties": "dapr.spring.demo-config-secret.multivalue.v1=spring\ndapr.spring.demo-config-secret.multivalue.v2=dapr", + "multivalue-yaml": "dapr:\n spring:\n demo-config-secret:\n multivalue:\n v3: cloud" +} \ No newline at end of file diff --git a/spring-boot-examples/cloud-config-demo/components/secretstore-springboot-multivalued.yaml b/spring-boot-examples/cloud-config-demo/components/secretstore-springboot-multivalued.yaml new file mode 100644 index 0000000000..24ac13d589 --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/components/secretstore-springboot-multivalued.yaml @@ -0,0 +1,14 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: democonfigMultivalued +spec: + type: secretstores.local.file + version: v1 + metadata: + - name: secretsFile + value: "./components/secret-spring/multivalued.json" + - name: nestedSeparator + value: "." + - name: multiValued + value: "true" diff --git a/spring-boot-examples/cloud-config-demo/components/secretstore-springboot-singlevalued.yaml b/spring-boot-examples/cloud-config-demo/components/secretstore-springboot-singlevalued.yaml new file mode 100644 index 0000000000..17a8580f54 --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/components/secretstore-springboot-singlevalued.yaml @@ -0,0 +1,12 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: democonfig +spec: + type: secretstores.local.file + version: v1 + metadata: + - name: secretsFile + value: "./components/secret-spring/singlevalued.json" + - name: multiValued + value: "false" diff --git a/spring-boot-examples/cloud-config-demo/pom.xml b/spring-boot-examples/cloud-config-demo/pom.xml new file mode 100644 index 0000000000..65f58fa31a --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/pom.xml @@ -0,0 +1,90 @@ + + + 4.0.0 + + + io.dapr + spring-boot-examples + 0.15.0-SNAPSHOT + + + cloud-config-demo + cloud-config-demo + Spring Boot, Testcontainers and Dapr Integration Examples :: Cloud Config Demo + + + + + + org.springframework.boot + spring-boot-dependencies + ${springboot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-web + + + io.dapr.spring + dapr-spring-boot-starter + ${dapr.sdk.alpha.version} + + + io.dapr.spring + dapr-spring-boot-starter-test + ${dapr.sdk.alpha.version} + test + + + org.testcontainers + junit-jupiter + test + + + redis.clients + jedis + 5.2.0 + test + + + com.redis + testcontainers-redis + 2.2.4 + test + + + io.rest-assured + rest-assured + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-site-plugin + 3.12.1 + + true + + + + + diff --git a/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/CloudConfigApplication.java b/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/CloudConfigApplication.java new file mode 100644 index 0000000000..d3fa3f2de2 --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/CloudConfigApplication.java @@ -0,0 +1,27 @@ +/* + * Copyright 2025 The Dapr 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.dapr.springboot.examples.cloudconfig; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +public class CloudConfigApplication { + + public static void main(String[] args) { + SpringApplication.run(CloudConfigApplication.class, args); + } + +} diff --git a/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/MultipleConfig.java b/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/MultipleConfig.java new file mode 100644 index 0000000000..f6b2cb040d --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/MultipleConfig.java @@ -0,0 +1,43 @@ +package io.dapr.springboot.examples.cloudconfig.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class MultipleConfig { + + //should be spring + @Value("${dapr.spring.demo-config-secret.multivalue.v1}") + private String multipleSecretConfigV1; + //should be dapr + @Value("${dapr.spring.demo-config-secret.multivalue.v2}") + private String multipleSecretConfigV2; + + //should be cloud + @Value("${dapr.spring.demo-config-config.multivalue.v3}") + private String multipleConfigurationConfigV3; + + public String getMultipleSecretConfigV1() { + return multipleSecretConfigV1; + } + + public void setMultipleSecretConfigV1(String multipleSecretConfigV1) { + this.multipleSecretConfigV1 = multipleSecretConfigV1; + } + + public String getMultipleSecretConfigV2() { + return multipleSecretConfigV2; + } + + public void setMultipleSecretConfigV2(String multipleSecretConfigV2) { + this.multipleSecretConfigV2 = multipleSecretConfigV2; + } + + public String getMultipleConfigurationConfigV3() { + return multipleConfigurationConfigV3; + } + + public void setMultipleConfigurationConfigV3(String multipleConfigurationConfigV3) { + this.multipleConfigurationConfigV3 = multipleConfigurationConfigV3; + } +} diff --git a/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/SingleConfig.java b/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/SingleConfig.java new file mode 100644 index 0000000000..53a9af960e --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/SingleConfig.java @@ -0,0 +1,32 @@ +package io.dapr.springboot.examples.cloudconfig.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class SingleConfig { + + // should be testvalue + @Value("${dapr.spring.demo-config-secret.singlevalue}") + private String singleValueSecret; + + // should be testvalue + @Value("${dapr.spring.demo-config-config.singlevalue}") + private String singleValueConfig; + + public String getSingleValueSecret() { + return singleValueSecret; + } + + public void setSingleValueSecret(String singleValueSecret) { + this.singleValueSecret = singleValueSecret; + } + + public String getSingleValueConfig() { + return singleValueConfig; + } + + public void setSingleValueConfig(String singleValueConfig) { + this.singleValueConfig = singleValueConfig; + } +} diff --git a/spring-boot-examples/cloud-config-demo/src/main/resources/application.yaml b/spring-boot-examples/cloud-config-demo/src/main/resources/application.yaml new file mode 100644 index 0000000000..2785be400d --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/src/main/resources/application.yaml @@ -0,0 +1,12 @@ +spring: + application: + name: cloud-config-demo + config: + import: + - dapr:secret:democonfig/multivalue-properties?type=doc + - dapr:secret:democonfig/dapr.spring.demo-config-secret.singlevalue?type=value + #- dapr:secret:democonfigmulti/value1?type=value + - dapr:config:democonfigconf/dapr.spring.demo-config-config.singlevalue?type=value + - dapr:config:democonfigconf/multivalue-yaml?type=doc&doc-type=yaml + + diff --git a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java new file mode 100644 index 0000000000..cdcd8c12cd --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java @@ -0,0 +1,154 @@ +/* + * Copyright 2025 The Dapr 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.dapr.springboot.examples.cloudconfig; + +import com.github.dockerjava.api.command.InspectContainerResponse; +import com.redis.testcontainers.RedisContainer; +import io.dapr.spring.boot.cloudconfig.autoconfigure.DaprCloudConfigAutoConfiguration; +import io.dapr.springboot.DaprAutoConfiguration; +import io.dapr.springboot.examples.cloudconfig.config.MultipleConfig; +import io.dapr.springboot.examples.cloudconfig.config.SingleConfig; +import io.dapr.testcontainers.Component; +import io.dapr.testcontainers.DaprContainer; +import io.dapr.testcontainers.DaprLogLevel; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.Network; +import org.testcontainers.images.builder.Transferable; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import redis.clients.jedis.Jedis; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static io.restassured.RestAssured.given; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(classes = {TestCloudConfigApplication.class, DaprTestContainersConfig.class, + DaprAutoConfiguration.class, DaprCloudConfigAutoConfiguration.class}, + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@Testcontainers +@Tag("testcontainers") +class CloudConfigTests { + + public static final String CONFIG_STORE_NAME = "democonfigconf"; + public static final String CONFIG_MULTI_NAME = "multivalue-yaml"; + public static final String CONFIG_SINGLE_NAME = "dapr.spring.demo-config-config.singlevalue"; + + public static final String SECRET_STORE_NAME = "democonfig"; + public static final String SECRET_MULTI_NAME = "multivalue-properties"; + public static final String SECRET_SINGLE_NAME = "dapr.spring.demo-config-secret.singlevalue"; + + public static final String SECRET_STORE_NAME_MULTI = "democonfigMultivalued"; + + private static final Map SINGLE_VALUE_PROPERTY = generateSingleValueProperty(); + private static final Map MULTI_VALUE_PROPERTY = generateMultiValueProperty(); + + + private static final Map STORE_PROPERTY = generateStoreProperty(); + + private static final Network DAPR_NETWORK = Network.newNetwork(); + + @Container + private static final RedisContainer REDIS_CONTAINER = new RedisContainer( + RedisContainer.DEFAULT_IMAGE_NAME.withTag(RedisContainer.DEFAULT_TAG)) { + @Override + protected void containerIsStarted(InspectContainerResponse containerInfo) { + super.containerIsStarted(containerInfo); + + String address = getHost(); + Integer port = getMappedPort(6379); + + Logger logger = LoggerFactory.getLogger(CloudConfigTests.class); + // Put values using Jedis + try (Jedis jedis = new Jedis(address, port)) { + logger.info("Putting Dapr Cloud config to {}:{}", address, port); + jedis.set(CloudConfigTests.CONFIG_MULTI_NAME, DaprConfigurationStores.YAML_CONFIG); + jedis.set(CloudConfigTests.CONFIG_SINGLE_NAME, "testvalue"); + } + } + } + .withNetworkAliases("redis") + .withCommand() + .withNetwork(DAPR_NETWORK); + + @Container + @ServiceConnection + private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.14.4") + .withAppName("configuration-dapr-app") + .withNetwork(DAPR_NETWORK) + .withComponent(new Component(CONFIG_STORE_NAME, "configuration.redis", "v1", STORE_PROPERTY)) + .withComponent(new Component(SECRET_STORE_NAME, "secretstores.local.file", "v1", SINGLE_VALUE_PROPERTY)) + .withComponent(new Component(SECRET_STORE_NAME_MULTI, "secretstores.local.file", "v1", MULTI_VALUE_PROPERTY)) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) + .dependsOn(REDIS_CONTAINER) + .withCopyToContainer(Transferable.of(DaprSecretStores.SINGLE_VALUED_SECRET), "/dapr-secrets/singlevalued.json") + .withCopyToContainer(Transferable.of(DaprSecretStores.MULTI_VALUED_SECRET), "/dapr-secrets/multivalued.json"); + + static { + DAPR_CONTAINER.setPortBindings(List.of("3500:3500", "50001:50001")); + } + + private static Map generateStoreProperty() { + return Map.of("redisHost", "redis:6379", + "redisPassword", ""); + } + + private static Map generateSingleValueProperty() { + return Map.of("secretsFile", "/dapr-secrets/singlevalued.json", + "multiValued", "false"); + } + + private static Map generateMultiValueProperty() { + return Map.of("secretsFile", "/dapr-secrets/multivalued.json", + "nestedSeparator", ".", + "multiValued", "true"); + } + + @Autowired + MultipleConfig multipleConfig; + + @Autowired + SingleConfig singleConfig; + + @DynamicPropertySource + static void dynamicProperties(DynamicPropertyRegistry registry) { + registry.add("dapr.client.http-port", DAPR_CONTAINER::getHttpPort); + registry.add("dapr.client.grpc-port", DAPR_CONTAINER::getGrpcPort); + } + + @Test + void testCloudConfig() { + assertEquals("testvalue", singleConfig.getSingleValueSecret()); + assertEquals("spring", multipleConfig.getMultipleSecretConfigV1()); + assertEquals("dapr", multipleConfig.getMultipleSecretConfigV2()); + + assertEquals("testvalue", singleConfig.getSingleValueConfig()); + + assertEquals("cloud", multipleConfig.getMultipleConfigurationConfigV3()); + } + +} diff --git a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprConfigurationStores.java b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprConfigurationStores.java new file mode 100644 index 0000000000..87911de3e1 --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprConfigurationStores.java @@ -0,0 +1,9 @@ +package io.dapr.springboot.examples.cloudconfig; + +public class DaprConfigurationStores { + public static final String YAML_CONFIG = "dapr:\n" + + " spring:\n" + + " demo-config-config:\n" + + " multivalue:\n" + + " v3: cloud"; +} diff --git a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprSecretStores.java b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprSecretStores.java new file mode 100644 index 0000000000..ef05862cb1 --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprSecretStores.java @@ -0,0 +1,23 @@ +package io.dapr.springboot.examples.cloudconfig; + +public class DaprSecretStores { + public static final String SINGLE_VALUED_SECRET = "{\n" + + " \"dapr.spring.demo-config-secret.singlevalue\": \"testvalue\",\n" + + " \"multivalue-properties\": \"dapr.spring.demo-config-secret.multivalue.v1=spring\\ndapr.spring.demo-config-secret.multivalue.v2=dapr\",\n" + + " \"multivalue-yaml\": \"dapr:\\n spring:\\n demo-config-secret:\\n multivalue:\\n v3: cloud\"\n" + + "}"; + + public static final String MULTI_VALUED_SECRET = "{\n" + + " \"value1\": {\n" + + " \"dapr\": {\n" + + " \"spring\": {\n" + + " \"demo-config-secret\": {\n" + + " \"multivalue\": {\n" + + " \"v4\": \"config\"\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; +} diff --git a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprTestContainersConfig.java b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprTestContainersConfig.java new file mode 100644 index 0000000000..4a770895ea --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprTestContainersConfig.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 The Dapr 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.dapr.springboot.examples.cloudconfig; + +import com.github.dockerjava.api.command.InspectContainerResponse; +import com.redis.testcontainers.RedisContainer; +import io.dapr.testcontainers.Component; +import io.dapr.testcontainers.DaprContainer; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.Network; +import org.testcontainers.images.builder.Transferable; +import redis.clients.jedis.Jedis; + +import java.util.List; +import java.util.Map; + +@TestConfiguration(proxyBeanMethods = false) +public class DaprTestContainersConfig { + +} diff --git a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/TestCloudConfigApplication.java b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/TestCloudConfigApplication.java new file mode 100644 index 0000000000..d8514a356a --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/TestCloudConfigApplication.java @@ -0,0 +1,31 @@ +/* + * Copyright 2025 The Dapr 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.dapr.springboot.examples.cloudconfig; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +public class TestCloudConfigApplication { + + public static void main(String[] args) { + + SpringApplication.from(CloudConfigApplication::main) + .with(DaprTestContainersConfig.class) + .run(args); + org.testcontainers.Testcontainers.exposeHostPorts(8080); + } + +} diff --git a/spring-boot-examples/pom.xml b/spring-boot-examples/pom.xml index 75a32364f7..6667ec0cab 100644 --- a/spring-boot-examples/pom.xml +++ b/spring-boot-examples/pom.xml @@ -21,6 +21,7 @@ producer-app consumer-app + cloud-config-demo From f5fab348c3d3ddcde1f47f7c5ec77efb11037c97 Mon Sep 17 00:00:00 2001 From: lony2003 Date: Mon, 10 Mar 2025 20:59:54 +0800 Subject: [PATCH 13/22] style(springboot cloudconfig): Clean comments Signed-off-by: lony2003 --- dapr-spring/dapr-spring-cloudconfig/pom.xml | 12 ------------ .../src/test/resources/application.yaml | 1 - 2 files changed, 13 deletions(-) diff --git a/dapr-spring/dapr-spring-cloudconfig/pom.xml b/dapr-spring/dapr-spring-cloudconfig/pom.xml index 35c584a112..c77a803032 100644 --- a/dapr-spring/dapr-spring-cloudconfig/pom.xml +++ b/dapr-spring/dapr-spring-cloudconfig/pom.xml @@ -22,18 +22,6 @@ compile - - - - - - - - - - - - io.dapr.spring dapr-spring-data diff --git a/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml b/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml index b84f1ed04e..473a5c308b 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml +++ b/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml @@ -5,7 +5,6 @@ spring: import: - dapr:secret:democonfig/multivalue-properties?type=doc - dapr:secret:democonfig/dapr.spring.democonfigsecret.singlevalue?type=value - #- dapr:secret:democonfigmulti/value1?type=value - dapr:config:democonfigconf/dapr.spring.democonfigconfig.singlevalue?type=value - dapr:config:democonfigconf/multivalue-yaml?type=doc&doc-type=yaml From 852d4591c82e575aea596f756c1eee06419a3370 Mon Sep 17 00:00:00 2001 From: lony2003 Date: Mon, 10 Mar 2025 21:51:36 +0800 Subject: [PATCH 14/22] docs(springboot cloudconfig): Fix typo of import Signed-off-by: lony2003 --- daprdocs/content/en/java-sdk-docs/spring-boot/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md index 5a1079d151..70dddccbdd 100644 --- a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md +++ b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md @@ -349,7 +349,7 @@ dapr.cloudconfig.wait-sidecar-enabled=false dapr.cloudconfig.wait-sidecar-retries=3 ``` -In Dapr Cloud Config component, we support two ways to imprt config: Secret Store API and Configuration API. +In Dapr Cloud Config component, we support two ways to import config: Secret Store API and Configuration API. Both of them have their schemas, listed below. From 29fa755d87632bac0d7a10712d5fb125fd66ae4f Mon Sep 17 00:00:00 2001 From: lony2003 Date: Thu, 27 Mar 2025 13:20:50 +0800 Subject: [PATCH 15/22] test(springboot cloudconfig): Fix test coverage add a test of Dapr Secret getBulkSecret method Signed-off-by: lony2003 --- .../io/dapr/spring/boot/cloudconfig/CloudConfigTests.java | 5 ++++- .../src/test/resources/application.yaml | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTests.java b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTests.java index cbb1891a76..d290ff3633 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTests.java +++ b/dapr-spring/dapr-spring-cloudconfig/src/test/java/io/dapr/spring/boot/cloudconfig/CloudConfigTests.java @@ -35,6 +35,10 @@ public class CloudConfigTests { singleValueProperties.put("dapr.spring.democonfigsecret.singlevalue", "testvalue"); Mockito.when(daprClient.getSecret(Mockito.eq("democonfig"), Mockito.eq("dapr.spring.democonfigsecret.singlevalue"))).thenReturn(Mono.just(singleValueProperties)); + Map> bulkProperties = new HashMap<>(); + bulkProperties.put("dapr.spring.democonfigsecret.singlevalue", singleValueProperties); + Mockito.when(daprClient.getBulkSecret(Mockito.eq("democonfig"))).thenReturn(Mono.just(bulkProperties)); + Map singleValueConfigurationItems = new HashMap<>(); singleValueConfigurationItems.put("dapr.spring.democonfigconfig.singlevalue", new ConfigurationItem("dapr.spring.democonfigconfig.singlevalue", "testvalue", "")); Mockito.when(daprClient.getConfiguration(Mockito.refEq(new GetConfigurationRequest("democonfigconf", List.of("dapr.spring.democonfigconfig.singlevalue")), "metadata"))).thenReturn(Mono.just(singleValueConfigurationItems)); @@ -49,7 +53,6 @@ public class CloudConfigTests { } catch (Exception ignore) { ignore.printStackTrace(); - } } diff --git a/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml b/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml index 473a5c308b..890ef8a20c 100644 --- a/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml +++ b/dapr-spring/dapr-spring-cloudconfig/src/test/resources/application.yaml @@ -4,6 +4,7 @@ spring: config: import: - dapr:secret:democonfig/multivalue-properties?type=doc + - dapr:secret:democonfig?type=doc - dapr:secret:democonfig/dapr.spring.democonfigsecret.singlevalue?type=value - dapr:config:democonfigconf/dapr.spring.democonfigconfig.singlevalue?type=value - dapr:config:democonfigconf/multivalue-yaml?type=doc&doc-type=yaml From 74e36d402677d780a82f9bb41c6bcf52c4fdf89d Mon Sep 17 00:00:00 2001 From: lony2003 Date: Sun, 30 Mar 2025 00:25:32 +0800 Subject: [PATCH 16/22] docs(cloud-config-demo): enhence Kubernetes cluster usages Signed-off-by: lony2003 --- spring-boot-examples/README.md | 24 +++++++++- .../cloudconfig/config/MultipleConfig.java | 11 ----- .../cloudconfig/config/SingleConfig.java | 12 ----- .../src/main/resources/application.yaml | 15 +++++-- .../cloudconfig/CloudConfigTests.java | 4 -- spring-boot-examples/kubernetes/README.md | 45 +++++++++++++++++++ .../kubernates-secrets-multivalue.yaml | 18 ++++++++ .../kubernetes/secretstore.yaml | 7 +++ 8 files changed, 104 insertions(+), 32 deletions(-) create mode 100644 spring-boot-examples/kubernetes/secrets/kubernates-secrets-multivalue.yaml create mode 100644 spring-boot-examples/kubernetes/secretstore.yaml diff --git a/spring-boot-examples/README.md b/spring-boot-examples/README.md index d24f723e64..307e8de88b 100644 --- a/spring-boot-examples/README.md +++ b/spring-boot-examples/README.md @@ -9,7 +9,6 @@ This example consists of three applications: - Subscribe to messages - Cloud Config Demo: - Import and use configs - - Can not run in Kubernetes currently, as lack of redis pre-fill data supported ## Running these examples from source code @@ -71,6 +70,29 @@ cd consumer-app/ The `consumer-app` starts in port `8081` by default. +To run `cloud-config-demo`, you should run in a terminal (`cloud-config-demo` doesn't depends on two applications above): + + + +```sh +cd cloud-config-demo/ +../../mvnw -Dspring-boot.run.arguments="--reuse=true" spring-boot:test-run +``` + + + +It will work and gain secrets from secret store. you can also uncomment the lines in application.yaml to enable more configuration imports. + ## Interacting with the applications Now that both applications are up you can place an order by sending a POST request to `:8080/orders/` diff --git a/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/MultipleConfig.java b/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/MultipleConfig.java index f6b2cb040d..442ebb9d56 100644 --- a/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/MultipleConfig.java +++ b/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/MultipleConfig.java @@ -13,9 +13,6 @@ public class MultipleConfig { @Value("${dapr.spring.demo-config-secret.multivalue.v2}") private String multipleSecretConfigV2; - //should be cloud - @Value("${dapr.spring.demo-config-config.multivalue.v3}") - private String multipleConfigurationConfigV3; public String getMultipleSecretConfigV1() { return multipleSecretConfigV1; @@ -32,12 +29,4 @@ public String getMultipleSecretConfigV2() { public void setMultipleSecretConfigV2(String multipleSecretConfigV2) { this.multipleSecretConfigV2 = multipleSecretConfigV2; } - - public String getMultipleConfigurationConfigV3() { - return multipleConfigurationConfigV3; - } - - public void setMultipleConfigurationConfigV3(String multipleConfigurationConfigV3) { - this.multipleConfigurationConfigV3 = multipleConfigurationConfigV3; - } } diff --git a/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/SingleConfig.java b/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/SingleConfig.java index 53a9af960e..e3cfc68d3f 100644 --- a/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/SingleConfig.java +++ b/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/config/SingleConfig.java @@ -10,10 +10,6 @@ public class SingleConfig { @Value("${dapr.spring.demo-config-secret.singlevalue}") private String singleValueSecret; - // should be testvalue - @Value("${dapr.spring.demo-config-config.singlevalue}") - private String singleValueConfig; - public String getSingleValueSecret() { return singleValueSecret; } @@ -21,12 +17,4 @@ public String getSingleValueSecret() { public void setSingleValueSecret(String singleValueSecret) { this.singleValueSecret = singleValueSecret; } - - public String getSingleValueConfig() { - return singleValueConfig; - } - - public void setSingleValueConfig(String singleValueConfig) { - this.singleValueConfig = singleValueConfig; - } } diff --git a/spring-boot-examples/cloud-config-demo/src/main/resources/application.yaml b/spring-boot-examples/cloud-config-demo/src/main/resources/application.yaml index 2785be400d..976966239d 100644 --- a/spring-boot-examples/cloud-config-demo/src/main/resources/application.yaml +++ b/spring-boot-examples/cloud-config-demo/src/main/resources/application.yaml @@ -5,8 +5,15 @@ spring: import: - dapr:secret:democonfig/multivalue-properties?type=doc - dapr:secret:democonfig/dapr.spring.demo-config-secret.singlevalue?type=value - #- dapr:secret:democonfigmulti/value1?type=value - - dapr:config:democonfigconf/dapr.spring.demo-config-config.singlevalue?type=value - - dapr:config:democonfigconf/multivalue-yaml?type=doc&doc-type=yaml - +# Following line works by default even if no secret store components defined in dapr running +# inside kubernetes cluster +# To maintain the compatibility of local version, we defined a secret store to dapr kubernetes, +# so following lines are commented. +# - dapr:secret:kubernetes/multivalue-properties?type=doc +# - dapr:secret:kubernetes/dapr.spring.demo-config-secret.singlevalue?type=value +# Following line includes dapr configuration schema of Cloud Config +# Unfortunately current dapr runtime doesn't support Kubernetes ConfigMap for configuration, +# so to maintain the compatibility of Kubernetes, following lines are commented. +# - dapr:config:democonfigconf/dapr.spring.demo-config-config.singlevalue?type=value +# - dapr:config:democonfigconf/multivalue-yaml?type=doc&doc-type=yaml diff --git a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java index cdcd8c12cd..e29f2f38c4 100644 --- a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java +++ b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java @@ -145,10 +145,6 @@ void testCloudConfig() { assertEquals("testvalue", singleConfig.getSingleValueSecret()); assertEquals("spring", multipleConfig.getMultipleSecretConfigV1()); assertEquals("dapr", multipleConfig.getMultipleSecretConfigV2()); - - assertEquals("testvalue", singleConfig.getSingleValueConfig()); - - assertEquals("cloud", multipleConfig.getMultipleConfigurationConfigV3()); } } diff --git a/spring-boot-examples/kubernetes/README.md b/spring-boot-examples/kubernetes/README.md index 3bb5da421e..c36d5ee7c6 100644 --- a/spring-boot-examples/kubernetes/README.md +++ b/spring-boot-examples/kubernetes/README.md @@ -3,6 +3,10 @@ To run this example on Kubernetes, you can use any Kubernetes distribution. We install Dapr on a Kubernetes cluster and then we will deploy both the `producer-app` and `consumer-app`. +You can also deploy `cloud-config-demo` if you like, but actually it doesn't have any feature. + +> Remind that only Kubernetes Secret works on dapr secret, Kubernetes ConfigMap doesn't support in dapr runtime, and there is no easy way to fill data to redis running in cluster before container started automatically, so configuration schema are commented by default. + ## Creating a cluster and installing Dapr If you don't have any Kubernetes cluster you can use Kubernetes KIND to create a local cluster. We will create a cluster @@ -35,6 +39,32 @@ helm upgrade --install dapr dapr/dapr \ --create-namespace \ --wait ``` +__Optional: pre-configure data needed by `cloud-config-demo`__ + +If you want to run `cloud-config-demo` on your cluster, you should apply the secrets file from `secrets/kubernates-secrets-multivalue.yaml`, or you can just run following command: + +```bash +cat < Date: Sun, 30 Mar 2025 20:50:35 +0800 Subject: [PATCH 17/22] docs(cloud-config-demo): enhence Kubernetes cluster usages Signed-off-by: lony2003 --- .../examples/cloudconfig/DemoController.java | 21 +++++++++ .../kubernetes/cloud-config-demo.yaml | 45 +++++++++++++++++++ .../kubernetes/secretstore.yaml | 1 + 3 files changed, 67 insertions(+) create mode 100644 spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/DemoController.java create mode 100644 spring-boot-examples/kubernetes/cloud-config-demo.yaml diff --git a/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/DemoController.java b/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/DemoController.java new file mode 100644 index 0000000000..68d8a12945 --- /dev/null +++ b/spring-boot-examples/cloud-config-demo/src/main/java/io/dapr/springboot/examples/cloudconfig/DemoController.java @@ -0,0 +1,21 @@ +package io.dapr.springboot.examples.cloudconfig; + +import io.dapr.springboot.examples.cloudconfig.config.SingleConfig; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class DemoController { + + private final SingleConfig singleConfig; + + public DemoController(SingleConfig singleConfig) { + this.singleConfig = singleConfig; + } + + @GetMapping("/config") + public String getConfig() { + return singleConfig.getSingleValueSecret(); + } + +} diff --git a/spring-boot-examples/kubernetes/cloud-config-demo.yaml b/spring-boot-examples/kubernetes/cloud-config-demo.yaml new file mode 100644 index 0000000000..099e7f44f5 --- /dev/null +++ b/spring-boot-examples/kubernetes/cloud-config-demo.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: cloud-config-demo + name: cloud-config-demo +spec: + type: NodePort + ports: + - name: "cloud-config-demo" + port: 8080 + targetPort: 8080 + nodePort: 31002 + selector: + app: cloud-config-demo + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: cloud-config-demo + name: cloud-config-demo +spec: + replicas: 1 + selector: + matchLabels: + app: cloud-config-demo + template: + metadata: + annotations: + dapr.io/app-id: cloud-config-demo + dapr.io/app-port: "8080" + dapr.io/enabled: "true" + labels: + app: cloud-config-demo + spec: + containers: + - image: localhost:5001/sb-cloud-config-demo + name: cloud-config-demo + imagePullPolicy: Always + ports: + - containerPort: 8080 + name: cloud-config diff --git a/spring-boot-examples/kubernetes/secretstore.yaml b/spring-boot-examples/kubernetes/secretstore.yaml index 1cffacad12..4c84e1f8e4 100644 --- a/spring-boot-examples/kubernetes/secretstore.yaml +++ b/spring-boot-examples/kubernetes/secretstore.yaml @@ -5,3 +5,4 @@ metadata: spec: type: secretstores.kubernetes version: v1 + metadata: [] From ffc43d3a339de52a0297a7e5ee0d329323f971b2 Mon Sep 17 00:00:00 2001 From: lony2003 Date: Sun, 30 Mar 2025 20:50:35 +0800 Subject: [PATCH 18/22] docs(cloud-config-demo): enhence Kubernetes cluster usages Signed-off-by: lony2003 --- spring-boot-examples/kubernetes/README.md | 2 +- .../kubernetes/secrets/kubernates-secrets-multivalue.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-boot-examples/kubernetes/README.md b/spring-boot-examples/kubernetes/README.md index c36d5ee7c6..97b032123d 100644 --- a/spring-boot-examples/kubernetes/README.md +++ b/spring-boot-examples/kubernetes/README.md @@ -62,7 +62,7 @@ metadata: name: dapr.spring.demo-config-secret.singlevalue type: Opaque stringData: - value: "dapr.spring.demo-config-secret.singlevalue=testvalue" + dapr.spring.demo-config-secret.singlevalue: "testvalue" EOF ``` diff --git a/spring-boot-examples/kubernetes/secrets/kubernates-secrets-multivalue.yaml b/spring-boot-examples/kubernetes/secrets/kubernates-secrets-multivalue.yaml index 25ae136f6f..74c1598a3c 100644 --- a/spring-boot-examples/kubernetes/secrets/kubernates-secrets-multivalue.yaml +++ b/spring-boot-examples/kubernetes/secrets/kubernates-secrets-multivalue.yaml @@ -15,4 +15,4 @@ metadata: name: dapr.spring.demo-config-secret.singlevalue type: Opaque stringData: - value: "dapr.spring.demo-config-secret.singlevalue=testvalue" \ No newline at end of file + dapr.spring.demo-config-secret.singlevalue: "testvalue" \ No newline at end of file From 186b1bbb06e42a97cd46429ecccceb4b8653bdc2 Mon Sep 17 00:00:00 2001 From: lony2003 Date: Wed, 2 Apr 2025 21:49:47 +0800 Subject: [PATCH 19/22] docs(springboot cloudconfig): A fix of example docs. Fix the example doc to make cloud config demo works both local and kubernetes, and pass the mm doc validate Signed-off-by: lony2003 --- spring-boot-examples/README.md | 26 ++++- .../src/main/resources/application.yaml | 3 + .../cloudconfig/CloudConfigTests.java | 37 ++++-- .../cloudconfig/DaprTestContainersConfig.java | 106 ++++++++++++++++++ .../TestCloudConfigApplication.java | 6 +- spring-boot-examples/kubernetes/README.md | 12 +- .../kubernetes/cloud-config-demo.yaml | 8 +- 7 files changed, 178 insertions(+), 20 deletions(-) diff --git a/spring-boot-examples/README.md b/spring-boot-examples/README.md index 307e8de88b..c3e7426024 100644 --- a/spring-boot-examples/README.md +++ b/spring-boot-examples/README.md @@ -76,7 +76,7 @@ name: Run Demo Producer Service match_order: none output_match_mode: substring expected_stdout_lines: -- 'Started ProducerApplication' +- 'Started CloudConfigApplication' background: true expected_return_code: 143 sleep: 30 @@ -91,6 +91,8 @@ cd cloud-config-demo/ +The `cloud-config-demo` starts in port `8082` by default. + It will work and gain secrets from secret store. you can also uncomment the lines in application.yaml to enable more configuration imports. ## Interacting with the applications @@ -196,6 +198,28 @@ Customer: salaboy follow-up done. Congratulations the customer: salaboy is happy! ``` +You can check the config in CloudConfig app, just run: + + + + +```sh +curl -X GET localhost:8082/config +``` + + + +You will get `testvalue` in terminal stdout. + ## Running on Kubernetes You can run the same example on a Kubernetes cluster. [Check the Kubernetes tutorial here](kubernetes/README.md). diff --git a/spring-boot-examples/cloud-config-demo/src/main/resources/application.yaml b/spring-boot-examples/cloud-config-demo/src/main/resources/application.yaml index 976966239d..a6a734c3dc 100644 --- a/spring-boot-examples/cloud-config-demo/src/main/resources/application.yaml +++ b/spring-boot-examples/cloud-config-demo/src/main/resources/application.yaml @@ -17,3 +17,6 @@ spring: # so to maintain the compatibility of Kubernetes, following lines are commented. # - dapr:config:democonfigconf/dapr.spring.demo-config-config.singlevalue?type=value # - dapr:config:democonfigconf/multivalue-yaml?type=doc&doc-type=yaml +server: + port: 8082 + diff --git a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java index e29f2f38c4..71b3227525 100644 --- a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java +++ b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java @@ -15,13 +15,14 @@ import com.github.dockerjava.api.command.InspectContainerResponse; import com.redis.testcontainers.RedisContainer; -import io.dapr.spring.boot.cloudconfig.autoconfigure.DaprCloudConfigAutoConfiguration; -import io.dapr.springboot.DaprAutoConfiguration; import io.dapr.springboot.examples.cloudconfig.config.MultipleConfig; import io.dapr.springboot.examples.cloudconfig.config.SingleConfig; import io.dapr.testcontainers.Component; import io.dapr.testcontainers.DaprContainer; import io.dapr.testcontainers.DaprLogLevel; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -32,6 +33,7 @@ import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.images.builder.Transferable; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -46,8 +48,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; -@SpringBootTest(classes = {TestCloudConfigApplication.class, DaprTestContainersConfig.class, - DaprAutoConfiguration.class, DaprCloudConfigAutoConfiguration.class}, +@SpringBootTest(classes = {TestCloudConfigApplication.class, DaprTestContainersConfig.class,}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @Testcontainers @Tag("testcontainers") @@ -128,18 +129,18 @@ private static Map generateMultiValueProperty() { "multiValued", "true"); } + @BeforeEach + void setUp() { + RestAssured.baseURI = "http://localhost:" + 8082; + org.testcontainers.Testcontainers.exposeHostPorts(8082); + } + @Autowired MultipleConfig multipleConfig; @Autowired SingleConfig singleConfig; - @DynamicPropertySource - static void dynamicProperties(DynamicPropertyRegistry registry) { - registry.add("dapr.client.http-port", DAPR_CONTAINER::getHttpPort); - registry.add("dapr.client.grpc-port", DAPR_CONTAINER::getGrpcPort); - } - @Test void testCloudConfig() { assertEquals("testvalue", singleConfig.getSingleValueSecret()); @@ -147,4 +148,20 @@ void testCloudConfig() { assertEquals("dapr", multipleConfig.getMultipleSecretConfigV2()); } + @Test + void testController() { + given().contentType(ContentType.JSON) + .when() + .get("/config") + .then() + .statusCode(200).body(is("testvalue")); + } + + @Test + void fillCoverage() { + new SingleConfig().setSingleValueSecret("testvalue"); + new MultipleConfig().setMultipleSecretConfigV1("spring"); + new MultipleConfig().setMultipleSecretConfigV2("dapr"); + } + } diff --git a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprTestContainersConfig.java b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprTestContainersConfig.java index 4a770895ea..fab18ecb2d 100644 --- a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprTestContainersConfig.java +++ b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprTestContainersConfig.java @@ -17,6 +17,7 @@ import com.redis.testcontainers.RedisContainer; import io.dapr.testcontainers.Component; import io.dapr.testcontainers.DaprContainer; +import io.dapr.testcontainers.DaprLogLevel; import org.junit.runner.Description; import org.junit.runners.model.Statement; import org.slf4j.Logger; @@ -24,15 +25,120 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; import org.testcontainers.DockerClientFactory; import org.testcontainers.containers.Network; import org.testcontainers.images.builder.Transferable; +import org.testcontainers.junit.jupiter.Container; import redis.clients.jedis.Jedis; +import java.util.HashMap; import java.util.List; import java.util.Map; @TestConfiguration(proxyBeanMethods = false) public class DaprTestContainersConfig { + public static final String CONFIG_STORE_NAME = "democonfigconf"; + public static final String CONFIG_MULTI_NAME = "multivalue-yaml"; + public static final String CONFIG_SINGLE_NAME = "dapr.spring.demo-config-config.singlevalue"; + public static final String SECRET_STORE_NAME = "democonfig"; + public static final String SECRET_MULTI_NAME = "multivalue-properties"; + public static final String SECRET_SINGLE_NAME = "dapr.spring.demo-config-secret.singlevalue"; + + public static final String SECRET_STORE_NAME_MULTI = "democonfigMultivalued"; + + private static final Map SINGLE_VALUE_PROPERTY = generateSingleValueProperty(); + private static final Map MULTI_VALUE_PROPERTY = generateMultiValueProperty(); + + + private static final Map STORE_PROPERTY = generateStoreProperty(); + + private static final Network DAPR_NETWORK = getDaprNetwork(); + + private static Map generateStoreProperty() { + return Map.of("redisHost", "redis:6379", + "redisPassword", ""); + } + + private static Map generateSingleValueProperty() { + return Map.of("secretsFile", "/dapr-secrets/singlevalued.json", + "multiValued", "false"); + } + + private static Map generateMultiValueProperty() { + return Map.of("secretsFile", "/dapr-secrets/multivalued.json", + "nestedSeparator", ".", + "multiValued", "true"); + } + + public static Network getDaprNetwork() { + Network defaultDaprNetwork = new Network() { + @Override + public String getId() { + return "dapr-network"; + } + + @Override + public void close() { + + } + + @Override + public Statement apply(Statement base, Description description) { + return null; + } + }; + + List networks = DockerClientFactory.instance().client().listNetworksCmd() + .withNameFilter("dapr-network").exec(); + if (networks.isEmpty()) { + Network.builder().createNetworkCmdModifier(cmd -> cmd.withName("dapr-network")).build().getId(); + return defaultDaprNetwork; + } else { + return defaultDaprNetwork; + } + + } + + @Container + private static final RedisContainer REDIS_CONTAINER = new RedisContainer( + RedisContainer.DEFAULT_IMAGE_NAME.withTag(RedisContainer.DEFAULT_TAG)) { + @Override + protected void containerIsStarted(InspectContainerResponse containerInfo) { + super.containerIsStarted(containerInfo); + + String address = getHost(); + Integer port = getMappedPort(6379); + + Logger logger = LoggerFactory.getLogger(CloudConfigTests.class); + // Put values using Jedis + try (Jedis jedis = new Jedis(address, port)) { + logger.info("Putting Dapr Cloud config to {}:{}", address, port); + jedis.set(CloudConfigTests.CONFIG_MULTI_NAME, DaprConfigurationStores.YAML_CONFIG); + jedis.set(CloudConfigTests.CONFIG_SINGLE_NAME, "testvalue"); + } + } + } + .withNetworkAliases("redis") + .withCommand() + .withNetwork(DAPR_NETWORK); + + @Container + @ServiceConnection + public static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.14.4") + .withAppName("configuration-dapr-app") + .withNetwork(DAPR_NETWORK) + .withComponent(new Component(CONFIG_STORE_NAME, "configuration.redis", "v1", STORE_PROPERTY)) + .withComponent(new Component(SECRET_STORE_NAME, "secretstores.local.file", "v1", SINGLE_VALUE_PROPERTY)) + .withComponent(new Component(SECRET_STORE_NAME_MULTI, "secretstores.local.file", "v1", MULTI_VALUE_PROPERTY)) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) + .dependsOn(REDIS_CONTAINER) + .withCopyToContainer(Transferable.of(DaprSecretStores.SINGLE_VALUED_SECRET), "/dapr-secrets/singlevalued.json") + .withCopyToContainer(Transferable.of(DaprSecretStores.MULTI_VALUED_SECRET), "/dapr-secrets/multivalued.json"); + + static { + DAPR_CONTAINER.setPortBindings(List.of("3500:3500", "50001:50001")); + } } diff --git a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/TestCloudConfigApplication.java b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/TestCloudConfigApplication.java index d8514a356a..5871e6e633 100644 --- a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/TestCloudConfigApplication.java +++ b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/TestCloudConfigApplication.java @@ -20,12 +20,16 @@ @SpringBootApplication public class TestCloudConfigApplication { + static { + DaprTestContainersConfig.DAPR_CONTAINER.start(); + } + public static void main(String[] args) { SpringApplication.from(CloudConfigApplication::main) .with(DaprTestContainersConfig.class) .run(args); - org.testcontainers.Testcontainers.exposeHostPorts(8080); + org.testcontainers.Testcontainers.exposeHostPorts(8082); } } diff --git a/spring-boot-examples/kubernetes/README.md b/spring-boot-examples/kubernetes/README.md index 97b032123d..2dea0c3504 100644 --- a/spring-boot-examples/kubernetes/README.md +++ b/spring-boot-examples/kubernetes/README.md @@ -1,10 +1,10 @@ # Running this example on Kubernetes To run this example on Kubernetes, you can use any Kubernetes distribution. -We install Dapr on a Kubernetes cluster and then we will deploy both the `producer-app` and `consumer-app`. - -You can also deploy `cloud-config-demo` if you like, but actually it doesn't have any feature. +We install Dapr on a Kubernetes cluster and then we will deploy the `producer-app`, `consumer-app` and `cloud-config-demo`. +> ___A Note for `cloud-config-demo`:___ +> > Remind that only Kubernetes Secret works on dapr secret, Kubernetes ConfigMap doesn't support in dapr runtime, and there is no easy way to fill data to redis running in cluster before container started automatically, so configuration schema are commented by default. ## Creating a cluster and installing Dapr @@ -152,7 +152,7 @@ Next you need to use `kubectl port-forward` to be able to send requests to the a kubectl port-forward svc/producer-app 8080:8080 ``` -In a different terminals you can check the logs of the `producer-app` and `consumer-app`: +In a different terminals you can check the logs of the `producer-app`, `consumer-app` and `cloud-config-demo`: ```bash kubectl logs -f producer-app- @@ -162,5 +162,9 @@ and ```bash kubectl logs -f consumer-app- ``` +and +```bash +kubectl logs -f cloud-config-demo- +``` diff --git a/spring-boot-examples/kubernetes/cloud-config-demo.yaml b/spring-boot-examples/kubernetes/cloud-config-demo.yaml index 099e7f44f5..04c80b5aae 100644 --- a/spring-boot-examples/kubernetes/cloud-config-demo.yaml +++ b/spring-boot-examples/kubernetes/cloud-config-demo.yaml @@ -8,8 +8,8 @@ spec: type: NodePort ports: - name: "cloud-config-demo" - port: 8080 - targetPort: 8080 + port: 8082 + targetPort: 8082 nodePort: 31002 selector: app: cloud-config-demo @@ -31,7 +31,7 @@ spec: metadata: annotations: dapr.io/app-id: cloud-config-demo - dapr.io/app-port: "8080" + dapr.io/app-port: "8082" dapr.io/enabled: "true" labels: app: cloud-config-demo @@ -41,5 +41,5 @@ spec: name: cloud-config-demo imagePullPolicy: Always ports: - - containerPort: 8080 + - containerPort: 8082 name: cloud-config From 15fefb14855c1626072f782c3eea8b825f3bdfe1 Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Fri, 3 Feb 2023 04:30:22 +0800 Subject: [PATCH 20/22] Update springboot to latest minor.patch version. (#826) (cherry picked from commit 31a47f55212ad0331562d6b308b938d111fe60ae) Signed-off-by: lony2003 --- sdk-tests/components/secret.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-tests/components/secret.json b/sdk-tests/components/secret.json index bd604a6a0f..9e26dfeeb6 100644 --- a/sdk-tests/components/secret.json +++ b/sdk-tests/components/secret.json @@ -1 +1 @@ -{"589f54ec-d0b7-40b5-92d9-6ab7ddef3c4f":{"name":"Jon Doe"},"969595f8-859c-4f7d-ae6a-864704f94a10":{"year":"2020","title":"The Metrics IV"}} \ No newline at end of file +{} \ No newline at end of file From a31ce67b9da56516fd0e2aab5ac0d3287e2a414e Mon Sep 17 00:00:00 2001 From: lony2003 Date: Wed, 2 Apr 2025 22:12:48 +0800 Subject: [PATCH 21/22] docs(springboot cloudconfig): A fix of example docs. Fix the example doc to make cloud config demo works both local and kubernetes Signed-off-by: lony2003 --- .../cloudconfig/CloudConfigTests.java | 73 +------------------ .../cloudconfig/DaprTestContainersConfig.java | 35 +-------- 2 files changed, 4 insertions(+), 104 deletions(-) diff --git a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java index 71b3227525..e0fd862d1e 100644 --- a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java +++ b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/CloudConfigTests.java @@ -54,79 +54,8 @@ @Tag("testcontainers") class CloudConfigTests { - public static final String CONFIG_STORE_NAME = "democonfigconf"; - public static final String CONFIG_MULTI_NAME = "multivalue-yaml"; - public static final String CONFIG_SINGLE_NAME = "dapr.spring.demo-config-config.singlevalue"; - - public static final String SECRET_STORE_NAME = "democonfig"; - public static final String SECRET_MULTI_NAME = "multivalue-properties"; - public static final String SECRET_SINGLE_NAME = "dapr.spring.demo-config-secret.singlevalue"; - - public static final String SECRET_STORE_NAME_MULTI = "democonfigMultivalued"; - - private static final Map SINGLE_VALUE_PROPERTY = generateSingleValueProperty(); - private static final Map MULTI_VALUE_PROPERTY = generateMultiValueProperty(); - - - private static final Map STORE_PROPERTY = generateStoreProperty(); - - private static final Network DAPR_NETWORK = Network.newNetwork(); - - @Container - private static final RedisContainer REDIS_CONTAINER = new RedisContainer( - RedisContainer.DEFAULT_IMAGE_NAME.withTag(RedisContainer.DEFAULT_TAG)) { - @Override - protected void containerIsStarted(InspectContainerResponse containerInfo) { - super.containerIsStarted(containerInfo); - - String address = getHost(); - Integer port = getMappedPort(6379); - - Logger logger = LoggerFactory.getLogger(CloudConfigTests.class); - // Put values using Jedis - try (Jedis jedis = new Jedis(address, port)) { - logger.info("Putting Dapr Cloud config to {}:{}", address, port); - jedis.set(CloudConfigTests.CONFIG_MULTI_NAME, DaprConfigurationStores.YAML_CONFIG); - jedis.set(CloudConfigTests.CONFIG_SINGLE_NAME, "testvalue"); - } - } - } - .withNetworkAliases("redis") - .withCommand() - .withNetwork(DAPR_NETWORK); - - @Container - @ServiceConnection - private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.14.4") - .withAppName("configuration-dapr-app") - .withNetwork(DAPR_NETWORK) - .withComponent(new Component(CONFIG_STORE_NAME, "configuration.redis", "v1", STORE_PROPERTY)) - .withComponent(new Component(SECRET_STORE_NAME, "secretstores.local.file", "v1", SINGLE_VALUE_PROPERTY)) - .withComponent(new Component(SECRET_STORE_NAME_MULTI, "secretstores.local.file", "v1", MULTI_VALUE_PROPERTY)) - .withDaprLogLevel(DaprLogLevel.DEBUG) - .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) - .dependsOn(REDIS_CONTAINER) - .withCopyToContainer(Transferable.of(DaprSecretStores.SINGLE_VALUED_SECRET), "/dapr-secrets/singlevalued.json") - .withCopyToContainer(Transferable.of(DaprSecretStores.MULTI_VALUED_SECRET), "/dapr-secrets/multivalued.json"); - static { - DAPR_CONTAINER.setPortBindings(List.of("3500:3500", "50001:50001")); - } - - private static Map generateStoreProperty() { - return Map.of("redisHost", "redis:6379", - "redisPassword", ""); - } - - private static Map generateSingleValueProperty() { - return Map.of("secretsFile", "/dapr-secrets/singlevalued.json", - "multiValued", "false"); - } - - private static Map generateMultiValueProperty() { - return Map.of("secretsFile", "/dapr-secrets/multivalued.json", - "nestedSeparator", ".", - "multiValued", "true"); + DaprTestContainersConfig.DAPR_CONTAINER.start(); } @BeforeEach diff --git a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprTestContainersConfig.java b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprTestContainersConfig.java index fab18ecb2d..67ced02c56 100644 --- a/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprTestContainersConfig.java +++ b/spring-boot-examples/cloud-config-demo/src/test/java/io/dapr/springboot/examples/cloudconfig/DaprTestContainersConfig.java @@ -54,7 +54,7 @@ public class DaprTestContainersConfig { private static final Map STORE_PROPERTY = generateStoreProperty(); - private static final Network DAPR_NETWORK = getDaprNetwork(); + private static final Network DAPR_NETWORK = Network.newNetwork(); private static Map generateStoreProperty() { return Map.of("redisHost", "redis:6379", @@ -72,35 +72,6 @@ private static Map generateMultiValueProperty() { "multiValued", "true"); } - public static Network getDaprNetwork() { - Network defaultDaprNetwork = new Network() { - @Override - public String getId() { - return "dapr-network"; - } - - @Override - public void close() { - - } - - @Override - public Statement apply(Statement base, Description description) { - return null; - } - }; - - List networks = DockerClientFactory.instance().client().listNetworksCmd() - .withNameFilter("dapr-network").exec(); - if (networks.isEmpty()) { - Network.builder().createNetworkCmdModifier(cmd -> cmd.withName("dapr-network")).build().getId(); - return defaultDaprNetwork; - } else { - return defaultDaprNetwork; - } - - } - @Container private static final RedisContainer REDIS_CONTAINER = new RedisContainer( RedisContainer.DEFAULT_IMAGE_NAME.withTag(RedisContainer.DEFAULT_TAG)) { @@ -115,8 +86,8 @@ protected void containerIsStarted(InspectContainerResponse containerInfo) { // Put values using Jedis try (Jedis jedis = new Jedis(address, port)) { logger.info("Putting Dapr Cloud config to {}:{}", address, port); - jedis.set(CloudConfigTests.CONFIG_MULTI_NAME, DaprConfigurationStores.YAML_CONFIG); - jedis.set(CloudConfigTests.CONFIG_SINGLE_NAME, "testvalue"); + jedis.set(DaprTestContainersConfig.CONFIG_MULTI_NAME, DaprConfigurationStores.YAML_CONFIG); + jedis.set(DaprTestContainersConfig.CONFIG_SINGLE_NAME, "testvalue"); } } } From 70d4eea6061b26a4cac9b74f6c7697636feee53a Mon Sep 17 00:00:00 2001 From: lony2003 Date: Thu, 3 Apr 2025 14:43:22 +0800 Subject: [PATCH 22/22] docs(springboot cloudconfig): A fix of example docs. Signed-off-by: lony2003 --- spring-boot-examples/README.md | 4 ++-- spring-boot-examples/kubernetes/README.md | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/spring-boot-examples/README.md b/spring-boot-examples/README.md index c3e7426024..1696b8bc60 100644 --- a/spring-boot-examples/README.md +++ b/spring-boot-examples/README.md @@ -29,7 +29,7 @@ expected_stdout_lines: background: true expected_return_code: 143 sleep: 30 -timeout_seconds: 45 +timeout_seconds: 75 --> @@ -72,7 +72,7 @@ The `consumer-app` starts in port `8081` by default. To run `cloud-config-demo`, you should run in a terminal (`cloud-config-demo` doesn't depends on two applications above):