diff --git a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationBootstrapConfiguration.java b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationBootstrapConfiguration.java index 0c35324ee..4ebedb33b 100644 --- a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationBootstrapConfiguration.java +++ b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationBootstrapConfiguration.java @@ -6,13 +6,12 @@ package com.microsoft.azure.spring.cloud.config; import java.util.List; +import java.util.Optional; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -67,31 +66,50 @@ public CloseableHttpClient closeableHttpClient() { @Bean public AppConfigurationPropertySourceLocator sourceLocator(AppConfigurationProperties properties, - AppConfigurationProviderProperties appProperties, ClientStore clients, ApplicationContext context) { + AppConfigurationProviderProperties appProperties, ClientStore clients, ApplicationContext context, + Optional keyVaultCredentialProviderOptional, + Optional keyVaultClientProviderOptional) { + KeyVaultCredentialProvider keyVaultCredentialProvider = null; - try { - keyVaultCredentialProvider = context.getBean(KeyVaultCredentialProvider.class); - } catch (NoUniqueBeanDefinitionException e) { - throw new RuntimeException("Failed to find unique KeyVaultCredentialProvider Bean for authentication.", e); - } catch (NoSuchBeanDefinitionException e) { + SecretClientBuilderSetup keyVaultClientProvider = null; + + if (!keyVaultCredentialProviderOptional.isPresent()) { LOGGER.debug("No KeyVaultCredentialProvider found."); + } else { + keyVaultCredentialProvider = keyVaultCredentialProviderOptional.get(); } + + if (!keyVaultClientProviderOptional.isPresent()) { + LOGGER.debug("No KeyVaultCredentialProvider found."); + } else { + keyVaultClientProvider = keyVaultClientProviderOptional.get(); + } + return new AppConfigurationPropertySourceLocator(properties, appProperties, clients, - keyVaultCredentialProvider); + keyVaultCredentialProvider, keyVaultClientProvider); } @Bean public ClientStore buildClientStores(AppConfigurationProperties properties, - AppConfigurationProviderProperties appProperties, ConnectionPool pool, ApplicationContext context) { + AppConfigurationProviderProperties appProperties, ConnectionPool pool, ApplicationContext context, + Optional tokenCredentialProviderOptional, + Optional clientProviderOptional) { + AppConfigurationCredentialProvider tokenCredentialProvider = null; - try { - tokenCredentialProvider = context.getBean(AppConfigurationCredentialProvider.class); - } catch (NoUniqueBeanDefinitionException e) { - throw new RuntimeException( - "Failed to find unique AppConfigurationCredentialProvider Bean for authentication.", e); - } catch (NoSuchBeanDefinitionException e) { + ConfigurationClientBuilderSetup clientProvider = null; + + if (!tokenCredentialProviderOptional.isPresent()) { LOGGER.debug("No AppConfigurationCredentialProvider found."); + } else { + tokenCredentialProvider = tokenCredentialProviderOptional.get(); + } + + if (!clientProviderOptional.isPresent()) { + LOGGER.debug("No AppConfigurationClientProvider found."); + } else { + clientProvider = clientProviderOptional.get(); } - return new ClientStore(appProperties, pool, tokenCredentialProvider); + + return new ClientStore(appProperties, pool, tokenCredentialProvider, clientProvider); } } diff --git a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySource.java b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySource.java index fc670b015..79bdd30f1 100644 --- a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySource.java +++ b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySource.java @@ -58,13 +58,16 @@ public class AppConfigurationPropertySource extends EnumerablePropertySource(); this.clients = clients; this.keyVaultCredentialProvider = keyVaultCredentialProvider; + this.keyVaultClientProvider = keyVaultClientProvider; } @Override @@ -108,10 +112,7 @@ public Object getProperty(String name) { FeatureSet initProperties(FeatureSet featureSet) throws IOException { String storeName = configStore.getEndpoint(); Date date = new Date(); - SettingSelector settingSelector = new SettingSelector(); - if (!label.equals("%00")) { - settingSelector.setLabelFilter(label); - } + SettingSelector settingSelector = new SettingSelector().setLabelFilter(label); // * for wildcard match settingSelector.setKeyFilter(context + "*"); @@ -172,7 +173,8 @@ private String getKeyVaultEntry(String value) { // Check if we already have a client for this key vault, if not we will make // one if (!keyVaultClients.containsKey(uri.getHost())) { - KeyVaultClient client = new KeyVaultClient(appConfigurationProperties, uri, keyVaultCredentialProvider); + KeyVaultClient client = new KeyVaultClient(appConfigurationProperties, uri, keyVaultCredentialProvider, + keyVaultClientProvider); keyVaultClients.put(uri.getHost(), client); } KeyVaultSecret secret = keyVaultClients.get(uri.getHost()).getSecret(uri, appProperties.getMaxRetryTime()); diff --git a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceLocator.java b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceLocator.java index 68af3cd3b..7b84bf409 100644 --- a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceLocator.java +++ b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceLocator.java @@ -58,18 +58,21 @@ public class AppConfigurationPropertySourceLocator implements PropertySourceLoca private ClientStore clients; private KeyVaultCredentialProvider keyVaultCredentialProvider; + + private SecretClientBuilderSetup keyVaultClientProvider; private static Boolean startup = true; public AppConfigurationPropertySourceLocator(AppConfigurationProperties properties, AppConfigurationProviderProperties appProperties, ClientStore clients, - KeyVaultCredentialProvider keyVaultCredentialProvider) { + KeyVaultCredentialProvider keyVaultCredentialProvider, SecretClientBuilderSetup keyVaultClientProvider) { this.properties = properties; this.appProperties = appProperties; this.profileSeparator = properties.getProfileSeparator(); this.configStores = properties.getStores(); this.clients = clients; this.keyVaultCredentialProvider = keyVaultCredentialProvider; + this.keyVaultClientProvider = keyVaultClientProvider; } @Override @@ -137,28 +140,35 @@ private void addPropertySource(CompositePropertySource composite, ConfigStore st // There is only one Feature Set for all AppConfigurationPropertySources FeatureSet featureSet = new FeatureSet(); + List sourceList = new ArrayList(); + // Reverse in order to add Profile specific properties earlier, and last profile // comes first Collections.reverse(contexts); for (String sourceContext : contexts) { try { - List sourceList = create(sourceContext, store, storeContextsMap, - initFeatures, featureSet); - sourceList.forEach(composite::addPropertySource); + sourceList.addAll(create(sourceContext, store, storeContextsMap, initFeatures, featureSet)); + LOGGER.debug("PropertySource context [{}] is added.", sourceContext); } catch (Exception e) { if (store.isFailFast() || !startup) { LOGGER.error( "Fail fast is set and there was an error reading configuration from Azure App " - + "Configuration Service for " + sourceContext); + + "Configuration store " + store.getEndpoint() + + ". The configuration starting with " + sourceContext + " failed to load."); ReflectionUtils.rethrowRuntimeException(e); } else { - LOGGER.warn("Unable to load configuration from Azure AppConfiguration Service for " + sourceContext, + LOGGER.warn( + "Unable to load configuration from Azure AppConfiguration store " + store.getEndpoint() + + ". The configurations starting with " + sourceContext + "failed to load.", e); StateHolder.setLoadState(store.getEndpoint(), false); } + // If anything breaks we skip out on loading the rest of the store. + return; } } + sourceList.forEach(composite::addPropertySource); } private List generateContexts(String applicationName, List profiles, ConfigStore configStore) { @@ -207,7 +217,7 @@ private List create(String context, ConfigStore for (String label : store.getLabels()) { putStoreContext(store.getEndpoint(), context, storeContextsMap); AppConfigurationPropertySource propertySource = new AppConfigurationPropertySource(context, store, - label, properties, clients, appProperties, keyVaultCredentialProvider); + label, properties, clients, appProperties, keyVaultCredentialProvider, keyVaultClientProvider); propertySource.initProperties(featureSet); if (initFeatures) { @@ -220,23 +230,22 @@ private List create(String context, ConfigStore String watchedKeyNames = clients.watchedKeyNames(store, storeContextsMap); SettingSelector settingSelector = new SettingSelector().setKeyFilter(watchedKeyNames).setLabelFilter("*"); - List configurationRevisions = clients.listSettingRevisons(settingSelector, + ConfigurationSetting configurationRevision = clients.getRevison(settingSelector, store.getEndpoint()); settingSelector = new SettingSelector().setKeyFilter(FEATURE_STORE_WATCH_KEY).setLabelFilter("*"); - List featureRevisions = clients.listSettingRevisons(settingSelector, + ConfigurationSetting featureRevision = clients.getRevison(settingSelector, store.getEndpoint()); - if (configurationRevisions != null && !configurationRevisions.isEmpty()) { - StateHolder.setEtagState(store.getEndpoint() + CONFIGURATION_SUFFIX, - configurationRevisions.get(0)); + if (configurationRevision != null) { + StateHolder.setEtagState(store.getEndpoint() + CONFIGURATION_SUFFIX, configurationRevision); } else { StateHolder.setEtagState(store.getEndpoint() + CONFIGURATION_SUFFIX, new ConfigurationSetting()); } - if (featureRevisions != null && !featureRevisions.isEmpty()) { - StateHolder.setEtagState(store.getEndpoint() + FEATURE_SUFFIX, featureRevisions.get(0)); + if (featureRevision != null) { + StateHolder.setEtagState(store.getEndpoint() + FEATURE_SUFFIX, featureRevision); } else { StateHolder.setEtagState(store.getEndpoint() + FEATURE_SUFFIX, new ConfigurationSetting()); } diff --git a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationProviderProperties.java b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationProviderProperties.java index 400b88bfc..ba1eef9d5 100644 --- a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationProviderProperties.java +++ b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationProviderProperties.java @@ -28,7 +28,7 @@ public class AppConfigurationProviderProperties { private String version; @NotNull - @Value("${maxRetries:12}") + @Value("${maxRetries:2}") private int maxRetries; @NotNull diff --git a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationRefresh.java b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationRefresh.java index 3d58b5351..fc7c8456e 100644 --- a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationRefresh.java +++ b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AppConfigurationRefresh.java @@ -102,6 +102,9 @@ private boolean refreshStores() { // Refresh Feature Flags willRefresh = refresh(configStore, FEATURE_SUFFIX, FEATURE_STORE_WATCH_KEY) ? true : willRefresh; + } else { + LOGGER.debug("Skipping refresh check for " + configStore.getEndpoint() + + ". The store failed to load on startup."); } } // Resetting last Checked date to now. @@ -110,6 +113,11 @@ private boolean refreshStores() { if (willRefresh) { // Only one refresh Event needs to be call to update all of the // stores, not one for each. + if (eventDataInfo.equals("*")) { + LOGGER.info("Configuration Refresh event triggered by store modification."); + } else { + LOGGER.info("Configuration Refresh Event triggered by " + eventDataInfo); + } RefreshEventData eventData = new RefreshEventData(eventDataInfo); publisher.publishEvent(new RefreshEvent(this, eventData, eventData.getMessage())); } @@ -134,18 +142,19 @@ private boolean refresh(ConfigStore store, String storeSuffix, String watchedKey SettingSelector settingSelector = new SettingSelector().setKeyFilter(watchedKeyNames) .setLabelFilter("*"); - List items = clientStore.listSettingRevisons(settingSelector, store.getEndpoint()); + ConfigurationSetting revision = clientStore.getRevison(settingSelector, store.getEndpoint()); String etag = null; // If there is no result, etag will be considered empty. // A refresh will trigger once the selector returns a value. - if (items != null && !items.isEmpty()) { - etag = items.get(0).getETag(); + if (revision != null) { + etag = revision.getETag(); } if (StateHolder.getEtagState(storeNameWithSuffix) == null) { // On startup there was no Configurations, but now there is. if (etag != null) { + LOGGER.info("The store " + store.getEndpoint() + " had no keys on startup, but now has keys to load."); return true; } return false; diff --git a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/ConfigurationClientBuilderSetup.java b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/ConfigurationClientBuilderSetup.java new file mode 100644 index 000000000..aa3bb752b --- /dev/null +++ b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/ConfigurationClientBuilderSetup.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for + * license information. + */ +package com.microsoft.azure.spring.cloud.config; + + +import com.azure.data.appconfiguration.ConfigurationClientBuilder; + +public interface ConfigurationClientBuilderSetup { + + public void setup(ConfigurationClientBuilder builder, String endpoint); + +} diff --git a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/SecretClientBuilderSetup.java b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/SecretClientBuilderSetup.java new file mode 100644 index 000000000..2d8c73db7 --- /dev/null +++ b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/SecretClientBuilderSetup.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for + * license information. + */ +package com.microsoft.azure.spring.cloud.config; + +import com.azure.security.keyvault.secrets.SecretClientBuilder; + +public interface SecretClientBuilderSetup { + + public void setup(SecretClientBuilder builder, String uri); + +} diff --git a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/stores/ClientStore.java b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/stores/ClientStore.java index 5df862d44..ce27fb879 100644 --- a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/stores/ClientStore.java +++ b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/stores/ClientStore.java @@ -12,6 +12,8 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; @@ -23,13 +25,16 @@ import com.azure.data.appconfiguration.models.ConfigurationSetting; import com.azure.data.appconfiguration.models.SettingSelector; import com.azure.identity.ManagedIdentityCredentialBuilder; +import com.microsoft.azure.spring.cloud.config.ConfigurationClientBuilderSetup; import com.microsoft.azure.spring.cloud.config.AppConfigurationCredentialProvider; import com.microsoft.azure.spring.cloud.config.AppConfigurationProviderProperties; +import com.microsoft.azure.spring.cloud.config.AppConfigurationRefresh; import com.microsoft.azure.spring.cloud.config.pipline.policies.BaseAppConfigurationPolicy; import com.microsoft.azure.spring.cloud.config.resource.Connection; import com.microsoft.azure.spring.cloud.config.resource.ConnectionPool; public class ClientStore { + private static final Logger LOGGER = LoggerFactory.getLogger(ClientStore.class); private AppConfigurationProviderProperties appProperties; @@ -37,11 +42,15 @@ public class ClientStore { private AppConfigurationCredentialProvider tokenCredentialProvider; - public ClientStore(AppConfigurationProviderProperties appProperties, - ConnectionPool pool, AppConfigurationCredentialProvider tokenCredentialProvider) { + private ConfigurationClientBuilderSetup clientProvider; + + public ClientStore(AppConfigurationProviderProperties appProperties, ConnectionPool pool, + AppConfigurationCredentialProvider tokenCredentialProvider, + ConfigurationClientBuilderSetup clientProvider) { this.appProperties = appProperties; this.pool = pool; this.tokenCredentialProvider = tokenCredentialProvider; + this.clientProvider = clientProvider; } private ConfigurationAsyncClient buildClient(String store) throws IllegalArgumentException { @@ -72,28 +81,41 @@ private ConfigurationAsyncClient buildClient(String store) throws IllegalArgumen if (tokenCredential != null) { // User Provided Token Credential + LOGGER.debug("Connecting to " + endpoint + " using AppConfigurationCredentialProvider."); builder.credential(tokenCredential); } else if ((connection.getClientId() != null && StringUtils.isNotEmpty(connection.getClientId())) && connection.getEndpoint() != null) { // User Assigned Identity - Client ID through configuration file. + LOGGER.debug("Connecting to " + endpoint + " using Client ID from configuration file."); ManagedIdentityCredentialBuilder micBuilder = new ManagedIdentityCredentialBuilder() .clientId(connection.getClientId()); builder.credential(micBuilder.build()); } else if (StringUtils.isNotEmpty(connection.getConnectionString())) { // Connection String + LOGGER.debug("Connecting to " + endpoint + " using Connecting String."); builder.connectionString(connection.getConnectionString()); } else if (connection.getEndpoint() != null) { - // System Assigned Identity. Needs to be checked last as all of the above should have a Endpoint. + // System Assigned Identity. Needs to be checked last as all of the above + // should have a Endpoint. + LOGGER.debug("Connecting to " + endpoint + + " using Azure System Assigned Identity or Azure User Assigned Identity."); ManagedIdentityCredentialBuilder micBuilder = new ManagedIdentityCredentialBuilder(); builder.credential(micBuilder.build()); } else { throw new IllegalArgumentException("No Configuration method was set for connecting to App Configuration"); } - return builder.endpoint(endpoint).buildAsyncClient(); + + builder.endpoint(endpoint); + + if (clientProvider != null) { + clientProvider.setup(builder, endpoint); + } + + return builder.buildAsyncClient(); } /** - * Gets a list of Configuration Settings from the revisions given config store that + * Gets the latest Configuration Setting from the revisions given config store that * match the Setting Selector criteria. * * @param settingSelector Information on which setting to pull. i.e. number of @@ -101,9 +123,9 @@ private ConfigurationAsyncClient buildClient(String store) throws IllegalArgumen * @param storeName Name of the App Configuration store to query against. * @return List of Configuration Settings. */ - public final List listSettingRevisons(SettingSelector settingSelector, String storeName) { + public final ConfigurationSetting getRevison(SettingSelector settingSelector, String storeName) { ConfigurationAsyncClient client = buildClient(storeName); - return client.listRevisions(settingSelector).collectList().block(); + return client.listRevisions(settingSelector).blockFirst(); } /** diff --git a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/stores/ConfigStore.java b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/stores/ConfigStore.java index c3b818784..b41d46df4 100644 --- a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/stores/ConfigStore.java +++ b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/stores/ConfigStore.java @@ -25,7 +25,8 @@ import com.microsoft.azure.spring.cloud.config.resource.Connection; public class ConfigStore { - private static final String[] EMPTY_LABEL_ONLY = {"\0"}; + private static final String EMPTY_LABEL = "\0"; + private static final String[] EMPTY_LABEL_ARRAY = {EMPTY_LABEL}; private String endpoint; // Config store endpoint @Nullable @@ -131,21 +132,32 @@ private boolean watchedKeyValid(String watchedKey) { */ public String[] getLabels() { if (!StringUtils.hasText(this.getLabel())) { - return EMPTY_LABEL_ONLY; + return EMPTY_LABEL_ARRAY; } + // The use of trim makes label= dev,prod and label= dev, prod equal. List labels = Arrays.stream(this.getLabel().split(LABEL_SEPARATOR)) - .filter(StringUtils::hasText) - .map(String::trim) + .map(label -> mapLabel(label)) .distinct() .collect(Collectors.toList()); - + + if (this.getLabel().endsWith(",")) { + labels.add(EMPTY_LABEL); + } + Collections.reverse(labels); if (labels.isEmpty()) { - return EMPTY_LABEL_ONLY; + return EMPTY_LABEL_ARRAY; } else { String[] t = new String[labels.size()]; return labels.toArray(t); } } + + private String mapLabel(String label) { + if (label == null || label.equals("")) { + return EMPTY_LABEL; + } + return label.trim(); + } } diff --git a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/stores/KeyVaultClient.java b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/stores/KeyVaultClient.java index 3957735ec..da6fa46b1 100644 --- a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/stores/KeyVaultClient.java +++ b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/stores/KeyVaultClient.java @@ -17,6 +17,7 @@ import com.azure.security.keyvault.secrets.models.KeyVaultSecret; import com.microsoft.azure.spring.cloud.config.AppConfigurationProperties; import com.microsoft.azure.spring.cloud.config.KeyVaultCredentialProvider; +import com.microsoft.azure.spring.cloud.config.SecretClientBuilderSetup; import com.microsoft.azure.spring.cloud.config.resource.AppConfigManagedIdentityProperties; public class KeyVaultClient { @@ -25,6 +26,8 @@ public class KeyVaultClient { private AppConfigurationProperties properties; + private SecretClientBuilderSetup keyVaultClientProvider; + private URI uri; private TokenCredential tokenCredential; @@ -38,17 +41,20 @@ public class KeyVaultClient { * @param properties Azure Configuration Managed Identity credentials */ public KeyVaultClient(AppConfigurationProperties properties, URI uri, - KeyVaultCredentialProvider tokenCredentialProvider) { + KeyVaultCredentialProvider tokenCredentialProvider, SecretClientBuilderSetup keyVaultClientProvider) { this.properties = properties; this.uri = uri; if (tokenCredentialProvider != null) { this.tokenCredential = tokenCredentialProvider.getKeyVaultCredential("https://" + uri.getHost()); } + this.keyVaultClientProvider = keyVaultClientProvider; } KeyVaultClient build() { SecretClientBuilder builder = getBuilder(); AppConfigManagedIdentityProperties msiProps = properties.getManagedIdentity(); + String fullUri = "https://" + uri.getHost(); + if (tokenCredential != null && msiProps != null) { throw new IllegalArgumentException("More than 1 Conncetion method was set for connecting to Key Vault."); } @@ -63,7 +69,14 @@ KeyVaultClient build() { // System Assigned Identity. builder.credential(new ManagedIdentityCredentialBuilder().build()); } - secretClient = builder.vaultUrl("https://" + uri.getHost()).buildAsyncClient(); + builder.vaultUrl(fullUri); + + if (keyVaultClientProvider != null) { + keyVaultClientProvider.setup(builder, fullUri); + } + + secretClient = builder.buildAsyncClient(); + return this; } diff --git a/spring-cloud-azure-appconfiguration-config/src/main/resources/appConfiguration.yaml b/spring-cloud-azure-appconfiguration-config/src/main/resources/appConfiguration.yaml index 2c0ca67b1..a08248e4d 100644 --- a/spring-cloud-azure-appconfiguration-config/src/main/resources/appConfiguration.yaml +++ b/spring-cloud-azure-appconfiguration-config/src/main/resources/appConfiguration.yaml @@ -2,8 +2,7 @@ spring: cloud: appconfiguration: version: 1.0 - maxRetries: 12 + maxRetries: 2 maxRetryTime: 60 preKillTime: 5 - keyVaultWaitTime: 5 diff --git a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceKeyVaultTest.java b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceKeyVaultTest.java index a38a9b3e2..e911f23f3 100644 --- a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceKeyVaultTest.java +++ b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceKeyVaultTest.java @@ -133,7 +133,7 @@ public void setup() { ArrayList contexts = new ArrayList(); contexts.add("/application/*"); propertySource = new AppConfigurationPropertySource(TEST_CONTEXT, testStore, "\0", - appConfigurationProperties, clientStoreMock, appProperties, tokenCredentialProvider); + appConfigurationProperties, clientStoreMock, appProperties, tokenCredentialProvider, null); testItems = new ArrayList(); testItems.add(item1); diff --git a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceLocatorTest.java b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceLocatorTest.java index 1ba3a4854..9a0061914 100644 --- a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceLocatorTest.java +++ b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceLocatorTest.java @@ -12,17 +12,11 @@ import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_CONN_STRING_2; import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_CONTEXT; import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_KEY_1; -import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_KEY_2; -import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_KEY_3; import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_LABEL_1; -import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_LABEL_2; -import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_LABEL_3; import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_STORE_NAME; import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_STORE_NAME_1; import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_STORE_NAME_2; import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_VALUE_1; -import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_VALUE_2; -import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_VALUE_3; import static com.microsoft.azure.spring.cloud.config.TestUtils.createItem; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertTrue; @@ -40,7 +34,6 @@ import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -70,8 +63,6 @@ public class AppConfigurationPropertySourceLocatorTest { private static final String PREFIX = "/config"; - public static final List FEATURE_ITEMS = new ArrayList<>(); - private static final ConfigurationSetting featureItem = createItem(".appconfig.featureflag/", "Alpha", FEATURE_VALUE, FEATURE_LABEL, FEATURE_FLAG_CONTENT_TYPE); @@ -83,7 +74,7 @@ public class AppConfigurationPropertySourceLocatorTest { @Mock private ClientStore clientStoreMock; - + @Mock private ConfigStore configStore; @@ -113,7 +104,7 @@ public class AppConfigurationPropertySourceLocatorTest { @Mock private Iterator configStoreIterator; - + @Mock private AppConfigurationProviderProperties appPropertiesMock; @@ -125,25 +116,12 @@ public class AppConfigurationPropertySourceLocatorTest { private AppConfigurationProviderProperties appProperties; private KeyVaultCredentialProvider tokenCredentialProvider = null; - - private static final String EMPTY_CONTENT_TYPE = ""; - - public List testItems = new ArrayList<>(); - - private static final ConfigurationSetting item1 = createItem(TEST_CONTEXT, TEST_KEY_1, TEST_VALUE_1, TEST_LABEL_1, - EMPTY_CONTENT_TYPE); - private static final ConfigurationSetting item2 = createItem(TEST_CONTEXT, TEST_KEY_2, TEST_VALUE_2, TEST_LABEL_2, - EMPTY_CONTENT_TYPE); + private static final String EMPTY_CONTENT_TYPE = ""; - private static final ConfigurationSetting item3 = createItem(TEST_CONTEXT, TEST_KEY_3, TEST_VALUE_3, TEST_LABEL_3, + private static final ConfigurationSetting item1 = createItem(TEST_CONTEXT, TEST_KEY_1, TEST_VALUE_1, TEST_LABEL_1, EMPTY_CONTENT_TYPE); - @BeforeClass - public static void init() { - FEATURE_ITEMS.add(featureItem); - } - @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -172,11 +150,6 @@ public void setup() { appProperties.setVersion("1.0"); appProperties.setMaxRetries(12); appProperties.setMaxRetryTime(0); - - testItems = new ArrayList(); - testItems.add(item1); - testItems.add(item2); - testItems.add(item3); } @After @@ -185,7 +158,7 @@ public void cleanup() Field field = AppConfigurationPropertySourceLocator.class.getDeclaredField("startup"); field.setAccessible(true); field.set(null, true); - StateHolder.setLoadState(TEST_STORE_NAME, false); + StateHolder.setLoadState(TEST_STORE_NAME, false); } @Test @@ -196,7 +169,7 @@ public void compositeSourceIsCreated() { when(properties.getDefaultContext()).thenReturn("application"); locator = new AppConfigurationPropertySourceLocator(properties, appProperties, clientStoreMock, - tokenCredentialProvider); + tokenCredentialProvider, null); PropertySource source = locator.locate(environment); assertThat(source).isInstanceOf(CompositePropertySource.class); @@ -210,18 +183,18 @@ public void compositeSourceIsCreated() { assertThat(sources.size()).isEqualTo(6); assertThat(sources.stream().map(s -> s.getName()).toArray()).containsExactly(expectedSourceNames); } - + @Test public void revisionsCheck() { String[] labels = new String[1]; labels[0] = "\0"; when(configStoreMock.getLabels()).thenReturn(labels); when(properties.getDefaultContext()).thenReturn("application"); - when(clientStoreMock.listSettingRevisons(Mockito.any(), Mockito.anyString())).thenReturn(testItems) - .thenReturn(FEATURE_ITEMS); + when(clientStoreMock.getRevison(Mockito.any(), Mockito.anyString())).thenReturn(item1) + .thenReturn(featureItem); locator = new AppConfigurationPropertySourceLocator(properties, appProperties, clientStoreMock, - tokenCredentialProvider); + tokenCredentialProvider, null); PropertySource source = locator.locate(environment); assertThat(source).isInstanceOf(CompositePropertySource.class); @@ -244,7 +217,7 @@ public void compositeSourceIsCreatedForPrefixedConfig() { when(configStoreMock.getPrefix()).thenReturn(PREFIX); when(properties.getDefaultContext()).thenReturn("application"); locator = new AppConfigurationPropertySourceLocator(properties, appProperties, clientStoreMock, - tokenCredentialProvider); + tokenCredentialProvider, null); PropertySource source = locator.locate(environment); @@ -273,7 +246,7 @@ public void nullApplicationNameCreateDefaultContextOnly() { when(properties.getName()).thenReturn(null); locator = new AppConfigurationPropertySourceLocator(properties, appProperties, - clientStoreMock, tokenCredentialProvider); + clientStoreMock, tokenCredentialProvider, null); PropertySource source = locator.locate(environment); assertThat(source).isInstanceOf(CompositePropertySource.class); @@ -296,7 +269,7 @@ public void emptyApplicationNameCreateDefaultContextOnly() { when(properties.getName()).thenReturn(""); when(properties.getDefaultContext()).thenReturn("application"); locator = new AppConfigurationPropertySourceLocator(properties, appProperties, - clientStoreMock, tokenCredentialProvider); + clientStoreMock, tokenCredentialProvider, null); PropertySource source = locator.locate(environment); assertThat(source).isInstanceOf(CompositePropertySource.class); @@ -317,7 +290,7 @@ public void defaultFailFastThrowException() throws IOException { when(properties.getDefaultContext()).thenReturn("application"); locator = new AppConfigurationPropertySourceLocator(properties, appProperties, - clientStoreMock, tokenCredentialProvider); + clientStoreMock, tokenCredentialProvider, null); when(clientStoreMock.listSettings(Mockito.any(), Mockito.anyString())).thenThrow(new RuntimeException()); locator.locate(environment); @@ -330,15 +303,15 @@ public void refreshThrowException() throws IOException, NoSuchFieldException, Se Field field = AppConfigurationPropertySourceLocator.class.getDeclaredField("startup"); field.setAccessible(true); field.set(null, false); - StateHolder.setLoadState(TEST_STORE_NAME, true); + StateHolder.setLoadState(TEST_STORE_NAME, true); expected.expect(NullPointerException.class); - + when(environment.getActiveProfiles()).thenReturn(new String[] {}); when(environment.getProperty("spring.application.name")).thenReturn(null); locator = new AppConfigurationPropertySourceLocator(properties, appProperties, - clientStoreMock, tokenCredentialProvider); + clientStoreMock, tokenCredentialProvider, null); locator.locate(environment); } @@ -347,7 +320,7 @@ public void refreshThrowException() throws IOException, NoSuchFieldException, Se public void notFailFastShouldPass() throws IOException { when(configStoreMock.isFailFast()).thenReturn(false); locator = new AppConfigurationPropertySourceLocator(properties, appProperties, - clientStoreMock, tokenCredentialProvider); + clientStoreMock, tokenCredentialProvider, null); when(configStoreMock.isFailFast()).thenReturn(false); when(clientStoreMock.listSettings(Mockito.any(), Mockito.anyString())).thenThrow(new RuntimeException()); @@ -355,7 +328,9 @@ public void notFailFastShouldPass() throws IOException { PropertySource source = locator.locate(environment); assertThat(source).isInstanceOf(CompositePropertySource.class); - verify(configStoreMock, times(3)).isFailFast(); + + // Once a store fails it should stop attempting to load + verify(configStoreMock, times(1)).isFailFast(); } @Test @@ -371,7 +346,7 @@ public void multiplePropertySourcesExistForMultiStores() { TestUtils.addStore(properties, TEST_STORE_NAME_2, TEST_CONN_STRING_2); locator = new AppConfigurationPropertySourceLocator(properties, appProperties, - clientStoreMock, tokenCredentialProvider); + clientStoreMock, tokenCredentialProvider, null); PropertySource source = locator.locate(environment); assertThat(source).isInstanceOf(CompositePropertySource.class); @@ -382,7 +357,7 @@ public void multiplePropertySourcesExistForMultiStores() { assertThat(sources.size()).isEqualTo(2); assertThat(sources.stream().map(s -> s.getName()).toArray()).containsExactly(expectedSourceNames); } - + @Test public void awaitOnError() throws Exception { List configStores = new ArrayList(); @@ -404,7 +379,7 @@ public void awaitOnError() throws Exception { when(appPropertiesMock.getStartDate()).thenReturn(new Date()); locator = new AppConfigurationPropertySourceLocator(properties, appPropertiesMock, clientStoreMock, - tokenCredentialProvider); + tokenCredentialProvider, null); boolean threwException = false; try { diff --git a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceTest.java b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceTest.java index eafb03856..07f0be3a1 100644 --- a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceTest.java +++ b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationPropertySourceTest.java @@ -36,7 +36,6 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import org.apache.commons.lang3.ArrayUtils; import org.junit.Before; @@ -160,7 +159,7 @@ public void setup() { ArrayList contexts = new ArrayList(); contexts.add("/application/*"); propertySource = new AppConfigurationPropertySource(TEST_CONTEXT, configStore, "\0", - appConfigurationProperties, clientStoreMock, appProperties, tokenCredentialProvider); + appConfigurationProperties, clientStoreMock, appProperties, tokenCredentialProvider, null); testItems = new ArrayList(); testItems.add(item1); @@ -179,7 +178,7 @@ public void setup() { public void testPropCanBeInitAndQueried() throws IOException { when(clientStoreMock.listSettings(Mockito.any(), Mockito.anyString())).thenReturn(testItems) .thenReturn(FEATURE_ITEMS); - + FeatureSet featureSet = new FeatureSet(); try { propertySource.initProperties(featureSet); diff --git a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationRefreshTest.java b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationRefreshTest.java index dbd71f518..1d32c7eef 100644 --- a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationRefreshTest.java +++ b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AppConfigurationRefreshTest.java @@ -102,12 +102,12 @@ public void cleanupMethod() { @Test public void nonUpdatedEtagShouldntPublishEvent() throws Exception { - StateHolder.setEtagState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, initialResponse().get(0)); - StateHolder.setEtagState(TEST_STORE_NAME + FEATURE_SUFFIX, initialResponse().get(0)); + StateHolder.setEtagState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, initialResponse()); + StateHolder.setEtagState(TEST_STORE_NAME + FEATURE_SUFFIX, initialResponse()); configRefresh.setApplicationEventPublisher(eventPublisher); - when(clientStoreMock.listSettingRevisons(Mockito.any(), Mockito.anyString())).thenReturn(initialResponse()); + when(clientStoreMock.getRevison(Mockito.any(), Mockito.anyString())).thenReturn(initialResponse()); configRefresh.refreshConfigurations().get(); verify(eventPublisher, times(0)).publishEvent(any(RefreshEvent.class)); @@ -115,24 +115,24 @@ public void nonUpdatedEtagShouldntPublishEvent() throws Exception { @Test public void updatedEtagShouldPublishEvent() throws Exception { - StateHolder.setEtagState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, initialResponse().get(0)); - StateHolder.setEtagState(TEST_STORE_NAME + FEATURE_SUFFIX, initialResponse().get(0)); + StateHolder.setEtagState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, initialResponse()); + StateHolder.setEtagState(TEST_STORE_NAME + FEATURE_SUFFIX, initialResponse()); - when(clientStoreMock.listSettingRevisons(Mockito.any(), Mockito.anyString())).thenReturn(initialResponse()); + when(clientStoreMock.getRevison(Mockito.any(), Mockito.anyString())).thenReturn(initialResponse()); configRefresh.setApplicationEventPublisher(eventPublisher); // The first time an action happens it can't update assertFalse(configRefresh.refreshConfigurations().get()); verify(eventPublisher, times(0)).publishEvent(any(RefreshEvent.class)); - when(clientStoreMock.listSettingRevisons(Mockito.any(), Mockito.anyString())).thenReturn(updatedResponse()); + when(clientStoreMock.getRevison(Mockito.any(), Mockito.anyString())).thenReturn(updatedResponse()); // If there is a change it should update assertTrue(configRefresh.refreshConfigurations().get()); verify(eventPublisher, times(1)).publishEvent(any(RefreshEvent.class)); - StateHolder.setEtagState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, updatedResponse().get(0)); - StateHolder.setEtagState(TEST_STORE_NAME + FEATURE_SUFFIX, updatedResponse().get(0)); + StateHolder.setEtagState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, updatedResponse()); + StateHolder.setEtagState(TEST_STORE_NAME + FEATURE_SUFFIX, updatedResponse()); HashMap map = new HashMap(); map.put("store1_configuration", "fake-etag-updated"); @@ -150,11 +150,11 @@ public void updatedEtagShouldPublishEvent() throws Exception { @Test public void noEtagReturned() throws Exception { - StateHolder.setEtagState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, initialResponse().get(0)); - StateHolder.setEtagState(TEST_STORE_NAME + FEATURE_SUFFIX, initialResponse().get(0)); + StateHolder.setEtagState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, initialResponse()); + StateHolder.setEtagState(TEST_STORE_NAME + FEATURE_SUFFIX, initialResponse()); - when(clientStoreMock.listSettingRevisons(Mockito.any(), Mockito.anyString())) - .thenReturn(new ArrayList()); + when(clientStoreMock.getRevison(Mockito.any(), Mockito.anyString())) + .thenReturn(null); configRefresh.setApplicationEventPublisher(eventPublisher); // The first time an action happens it can't update @@ -164,10 +164,10 @@ public void noEtagReturned() throws Exception { @Test public void nullItemsReturned() throws Exception { - StateHolder.setEtagState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, initialResponse().get(0)); - StateHolder.setEtagState(TEST_STORE_NAME + FEATURE_SUFFIX, initialResponse().get(0)); + StateHolder.setEtagState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, initialResponse()); + StateHolder.setEtagState(TEST_STORE_NAME + FEATURE_SUFFIX, initialResponse()); - when(clientStoreMock.listSettingRevisons(Mockito.any(), Mockito.anyString())).thenReturn(null); + when(clientStoreMock.getRevison(Mockito.any(), Mockito.anyString())).thenReturn(null); configRefresh.setApplicationEventPublisher(eventPublisher); // The first time an action happens it can't update @@ -189,7 +189,7 @@ public void noInitialStateNoEtag() throws Exception { AppConfigurationRefresh configRefreshLost = new AppConfigurationRefresh(propertiesLost, contextsMap, clientStoreMock); StateHolder.setLoadState(TEST_STORE_NAME + "_LOST", true); - when(clientStoreMock.listSettingRevisons(Mockito.any(), Mockito.anyString())).thenReturn(null); + when(clientStoreMock.getRevison(Mockito.any(), Mockito.anyString())).thenReturn(null); configRefreshLost.setApplicationEventPublisher(eventPublisher); // The first time an action happens it can't update @@ -212,7 +212,7 @@ public void noInitialStateHasEtag() throws Exception { clientStoreMock); StateHolder.setLoadState(TEST_STORE_NAME + "_LOST", true); - when(clientStoreMock.listSettingRevisons(Mockito.any(), Mockito.anyString())).thenReturn(updatedResponse()); + when(clientStoreMock.getRevison(Mockito.any(), Mockito.anyString())).thenReturn(updatedResponse()); configRefreshLost.setApplicationEventPublisher(eventPublisher); // The first time an action happens it can't update @@ -222,8 +222,8 @@ public void noInitialStateHasEtag() throws Exception { @Test public void notRefreshTime() throws Exception { - StateHolder.setEtagState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, initialResponse().get(0)); - StateHolder.setEtagState(TEST_STORE_NAME + FEATURE_SUFFIX, initialResponse().get(0)); + StateHolder.setEtagState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, initialResponse()); + StateHolder.setEtagState(TEST_STORE_NAME + FEATURE_SUFFIX, initialResponse()); properties.setCacheExpiration(Duration.ofMinutes(60)); AppConfigurationRefresh watchLargeDelay = new AppConfigurationRefresh(properties, contextsMap, clientStoreMock); @@ -235,18 +235,12 @@ public void notRefreshTime() throws Exception { verify(eventPublisher, times(0)).publishEvent(any(RefreshEvent.class)); } - private List initialResponse() { - ConfigurationSetting item = new ConfigurationSetting(); - item.setETag("fake-etag"); - - return Arrays.asList(item); + private ConfigurationSetting initialResponse() { + return new ConfigurationSetting().setETag("fake-etag"); } - private List updatedResponse() { - ConfigurationSetting item = new ConfigurationSetting(); - item.setETag("fake-etag-updated"); - - return Arrays.asList(item); + private ConfigurationSetting updatedResponse() { + return new ConfigurationSetting().setETag("fake-etag-updated"); } } diff --git a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/stores/ClientStoreTest.java b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/stores/ClientStoreTest.java index fdf5f1c79..2a21e23d3 100644 --- a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/stores/ClientStoreTest.java +++ b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/stores/ClientStoreTest.java @@ -84,7 +84,7 @@ public void connectWithConnectionString() throws IOException { SettingSelector selector = new SettingSelector(); - clientStore = new ClientStore(appProperties, pool, null); + clientStore = new ClientStore(appProperties, pool, null, null); ClientStore test = Mockito.spy(clientStore); Mockito.doReturn(builderMock).when(test).getBuilder(); @@ -114,7 +114,7 @@ public TokenCredential getAppConfigCredential(String uri) { } }; - clientStore = new ClientStore(appProperties, pool, provider); + clientStore = new ClientStore(appProperties, pool, provider, null); ClientStore test = Mockito.spy(clientStore); Mockito.doReturn(builderMock).when(test).getBuilder(); @@ -144,7 +144,7 @@ public TokenCredential getAppConfigCredential(String uri) { } }; - clientStore = new ClientStore(appProperties, pool, provider); + clientStore = new ClientStore(appProperties, pool, provider, null); ClientStore test = Mockito.spy(clientStore); Mockito.doReturn(builderMock).when(test).getBuilder(); @@ -168,7 +168,7 @@ public TokenCredential getAppConfigCredential(String uri) { } }; - clientStore = new ClientStore(appProperties, pool, provider); + clientStore = new ClientStore(appProperties, pool, provider, null); ClientStore test = Mockito.spy(clientStore); Mockito.doReturn(builderMock).when(test).getBuilder(); @@ -180,7 +180,7 @@ public TokenCredential getAppConfigCredential(String uri) { @Test public void watchedKeyNamesWildcardTest() { - clientStore = new ClientStore(appProperties, pool, null); + clientStore = new ClientStore(appProperties, pool, null, null); ConfigStore store = new ConfigStore(); HashMap> storeContextsMap = new HashMap>(); diff --git a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/stores/ConfigStoreTest.java b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/stores/ConfigStoreTest.java index b02566321..eb83bf0cb 100644 --- a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/stores/ConfigStoreTest.java +++ b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/stores/ConfigStoreTest.java @@ -5,6 +5,7 @@ */ package com.microsoft.azure.spring.cloud.config.stores; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import org.junit.Test; @@ -26,5 +27,21 @@ public void invalidEndpoint() { configStore.validateAndInit(); fail(); } + + @Test + public void getLabelsTest() { + ConfigStore configStore = new ConfigStore(); + assertEquals(configStore.getLabels()[0], "\0"); + + configStore.setLabel("dev"); + assertEquals(configStore.getLabels()[0], "dev"); + + configStore.setLabel("dev,test"); + assertEquals(configStore.getLabels()[0], "test"); + assertEquals(configStore.getLabels()[1], "dev"); + + configStore.setLabel(","); + assertEquals(configStore.getLabels()[0], "\0"); + } } diff --git a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/stores/KeyVaultClientTest.java b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/stores/KeyVaultClientTest.java index 87d6b05cd..4122d5808 100644 --- a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/stores/KeyVaultClientTest.java +++ b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/stores/KeyVaultClientTest.java @@ -74,7 +74,7 @@ public TokenCredential getKeyVaultCredential(String uri) { } }; - clientStore = new KeyVaultClient(azureProperties, new URI(keyVaultUri), provider); + clientStore = new KeyVaultClient(azureProperties, new URI(keyVaultUri), provider, null); KeyVaultClient test = Mockito.spy(clientStore); Mockito.doReturn(builderMock).when(test).getBuilder(); @@ -100,7 +100,7 @@ public TokenCredential getKeyVaultCredential(String uri) { } }; - clientStore = new KeyVaultClient(azureProperties, new URI(keyVaultUri), provider); + clientStore = new KeyVaultClient(azureProperties, new URI(keyVaultUri), provider, null); KeyVaultClient test = Mockito.spy(clientStore); Mockito.doReturn(builderMock).when(test).getBuilder(); @@ -128,7 +128,7 @@ public void configClientIdAuth() throws IOException, URISyntaxException { String keyVaultUri = "https://keyvault.vault.azure.net/secrets/mySecret"; - clientStore = new KeyVaultClient(azureProperties, new URI(keyVaultUri), null); + clientStore = new KeyVaultClient(azureProperties, new URI(keyVaultUri), null, null); KeyVaultClient test = Mockito.spy(clientStore); Mockito.doReturn(builderMock).when(test).getBuilder(); diff --git a/spring-cloud-azure-starters/spring-cloud-starter-azure-appconfiguration-config/README.md b/spring-cloud-azure-starters/spring-cloud-starter-azure-appconfiguration-config/README.md index 5f9d52e36..82a62ce6a 100644 --- a/spring-cloud-azure-starters/spring-cloud-starter-azure-appconfiguration-config/README.md +++ b/spring-cloud-azure-starters/spring-cloud-starter-azure-appconfiguration-config/README.md @@ -191,6 +191,38 @@ public class MyCredentials implements AppConfigCredentialProvider, KeyVaultCrede } ``` +### Modifying Connection Client + +In some situations the client connection needs to be modified, such as adding proxy configurations. Using the AppConfigurationClientProvider and/or KeyVaultClientProvider. By implementing either of these classes and providing and generating a @Bean of them will enable client modification following [App Configuration SDK](https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/appconfiguration/azure-data-appconfiguration#key-concepts) and [Key Vault SDK](https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/keyvault/azure-security-keyvault-secrets#key-concepts). + +```java +public class MyClients implements AppConfigurationClientProvider, KeyVaultClientProvider { + + @Override + public SecretClientBuilder modifyKeyVaultClient(SecretClientBuilder keyVaultClientBuilder) { + return keyVaultClientBuilder.httpClient(buildHttpClient()); + } + + @Override + public ConfigurationClientBuilder modifyConfigurationClient(ConfigurationClientBuilder configurationClientBuilder) { + return configurationClientBuilder.httpClient(buildHttpClient()); + } + + private HttpClient buildHttpClient() { + String hostname = System.getProperty("https.proxyHosts"); + String portString = System.getProperty("https.proxyPort"); + int port = Integer.valueOf(portString); + + ProxyOptions proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP, + new InetSocketAddress(hostname, port)); + return new NettyAsyncHttpClientBuilder() + .proxy(proxyOptions) + .build(); + } + +} +``` + [azure]: https://azure.microsoft.com [azure_active_directory]: https://azure.microsoft.com/services/active-directory/