Skip to content

Commit

Permalink
Spring Snapshots (#35726)
Browse files Browse the repository at this point in the history
* Snapshots

* Updating from meeting comments

* Adding Prefix Trimming

* Adding Trimming

* Updating Snapshots with tests and trimming fixes

* Updating for feature flags

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Fixing Build

* Fixing spot bugs

* Update pom.xml

* Update pom.xml

* Using Beta1

* Merging constructor

* Re-organizing snapshot property sources

* Updating Snapshot Property Sources

* Fixes Snapshot Property Source Name, Max Retry usage

* Update README.md

* Updates from review

* Updates from review

* Updated FF usage
  • Loading branch information
mrm9084 authored Sep 11, 2023
1 parent e4fe6ab commit 15ffff2
Show file tree
Hide file tree
Showing 20 changed files with 666 additions and 165 deletions.
3 changes: 1 addition & 2 deletions eng/versioning/version_client.txt
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,6 @@ unreleased_com.azure:azure-core;1.42.0-beta.2
# beta_<groupId>:<artifactId>;dependency-version
# note: Released beta versions will not be manipulated with the automatic PR creation code.
beta_com.azure:azure-communication-common;1.3.0-beta.1
beta_com.azure:azure-data-appconfiguration;1.5.0-beta.1
beta_com.azure:azure-core-http-netty;1.14.0-beta.1
beta_com.azure:azure-core;1.42.0-beta.1


Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-data-appconfiguration</artifactId>
<version>1.4.7</version> <!-- {x-version-update;com.azure:azure-data-appconfiguration;dependency} -->
<version>1.5.0-beta.1</version> <!-- {x-version-update;beta_com.azure:azure-data-appconfiguration;dependency} -->
</dependency>
<dependency>
<groupId>com.azure</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.appconfiguration.config.implementation;

import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.FEATURE_FLAG_CONTENT_TYPE;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
Expand All @@ -17,6 +20,7 @@
import org.springframework.util.StringUtils;

import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
import com.azure.data.appconfiguration.models.SecretReferenceConfigurationSetting;
import com.azure.data.appconfiguration.models.SettingSelector;
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
Expand All @@ -26,102 +30,131 @@
* Azure App Configuration PropertySource unique per Store Label(Profile) combo.
*
* <p>
* i.e. If connecting to 2 stores and have 2 labels set 4
* AppConfigurationPropertySources need to be created.
* i.e. If connecting to 2 stores and have 2 labels set 4 AppConfigurationPropertySources need to be created.
* </p>
*/
final class AppConfigurationApplicationSettingPropertySource extends AppConfigurationPropertySource {
class AppConfigurationApplicationSettingPropertySource extends AppConfigurationPropertySource {

private static final Logger LOGGER = LoggerFactory
.getLogger(AppConfigurationApplicationSettingPropertySource.class);
.getLogger(AppConfigurationApplicationSettingPropertySource.class);

private final AppConfigurationKeyVaultClientFactory keyVaultClientFactory;

private final int maxRetryTime;
private final String keyFilter;

private final String[] labelFilters;

AppConfigurationApplicationSettingPropertySource(String originEndpoint, AppConfigurationReplicaClient replicaClient,
AppConfigurationKeyVaultClientFactory keyVaultClientFactory, String keyFilter, String[] labelFilter,
int maxRetryTime) {
AppConfigurationApplicationSettingPropertySource(String name, AppConfigurationReplicaClient replicaClient,
AppConfigurationKeyVaultClientFactory keyVaultClientFactory, String keyFilter, String[] labelFilters) {
// The context alone does not uniquely define a PropertySource, append storeName
// and label to uniquely define a PropertySource
super(originEndpoint, replicaClient, keyFilter, labelFilter);
super(name + getLabelName(labelFilters), replicaClient);
this.keyVaultClientFactory = keyVaultClientFactory;
this.maxRetryTime = maxRetryTime;
this.keyFilter = keyFilter;
this.labelFilters = labelFilters;
}

/**
* <p>
* Gets settings from Azure/Cache to set as configurations. Updates the cache.
* </p>
*
* @param keyPrefixTrimValues prefixs to trim from key values
* @throws JsonProcessingException thrown if fails to parse Json content type
*/
public void initProperties() throws JsonProcessingException {
List<String> labels = Arrays.asList(labelFilter);
public void initProperties(List<String> keyPrefixTrimValues) throws JsonProcessingException {

List<String> labels = Arrays.asList(labelFilters);
// Reverse labels so they have the right priority order.
Collections.reverse(labels);

for (String label : labels) {
SettingSelector settingSelector = new SettingSelector().setKeyFilter(keyFilter + "*")
.setLabelFilter(label);
SettingSelector settingSelector = new SettingSelector().setKeyFilter(keyFilter + "*").setLabelFilter(label);

// * for wildcard match
List<ConfigurationSetting> settings = replicaClient.listSettings(settingSelector);

for (ConfigurationSetting setting : settings) {
String key = setting.getKey().trim().substring(keyFilter.length())
.replace('/', '.');
if (setting instanceof SecretReferenceConfigurationSetting) {
String entry = getKeyVaultEntry((SecretReferenceConfigurationSetting) setting);

// Null in the case of failFast is false, will just skip entry.
if (entry != null) {
properties.put(key, entry);
}
} else if (StringUtils.hasText(setting.getContentType())
&& JsonConfigurationParser.isJsonContentType(setting.getContentType())) {
Map<String, Object> jsonSettings = JsonConfigurationParser.parseJsonSetting(setting);
for (Entry<String, Object> jsonSetting : jsonSettings.entrySet()) {
key = jsonSetting.getKey().trim().substring(keyFilter.length());
properties.put(key, jsonSetting.getValue());
}
} else {
properties.put(key, setting.getValue());
}
}
processConfigurationSettings(replicaClient.listSettings(settingSelector), settingSelector.getKeyFilter(),
keyPrefixTrimValues);
}
}

protected void processConfigurationSettings(List<ConfigurationSetting> settings, String keyFilter,
List<String> keyPrefixTrimValues)
throws JsonProcessingException {
for (ConfigurationSetting setting : settings) {
if (keyPrefixTrimValues == null && StringUtils.hasText(keyFilter)) {
keyPrefixTrimValues = new ArrayList<>();
keyPrefixTrimValues.add(keyFilter.substring(0, keyFilter.length() - 1));
}
String key = trimKey(setting.getKey(), keyPrefixTrimValues);

if (setting instanceof SecretReferenceConfigurationSetting) {
handleKeyVaultReference(key, (SecretReferenceConfigurationSetting) setting);
} else if (setting instanceof FeatureFlagConfigurationSetting
&& FEATURE_FLAG_CONTENT_TYPE.equals(setting.getContentType())) {
handleFeatureFlag(key, (FeatureFlagConfigurationSetting) setting, keyPrefixTrimValues);
} else if (StringUtils.hasText(setting.getContentType())
&& JsonConfigurationParser.isJsonContentType(setting.getContentType())) {
handleJson(setting, keyPrefixTrimValues);
} else {
properties.put(key, setting.getValue());
}
}
}

/**
* Given a Setting's Key Vault Reference stored in the Settings value, it will
* get its entry in Key Vault.
* Given a Setting's Key Vault Reference stored in the Settings value, it will get its entry in Key Vault.
*
* @param secretReference {"uri":
* "&lt;your-vault-url&gt;/secret/&lt;secret&gt;/&lt;version&gt;"}
* @param key Application Setting name
* @param secretReference {"uri": "&lt;your-vault-url&gt;/secret/&lt;secret&gt;/&lt;version&gt;"}
* @return Key Vault Secret Value
*/
private String getKeyVaultEntry(SecretReferenceConfigurationSetting secretReference) {
String secretValue = null;
protected void handleKeyVaultReference(String key, SecretReferenceConfigurationSetting secretReference) {
try {
URI uri = null;
KeyVaultSecret secret = null;

// Parsing Key Vault Reference for URI
try {
uri = new URI(secretReference.getSecretId());
secret = keyVaultClientFactory.getClient("https://" + uri.getHost()).getSecret(uri, maxRetryTime);
URI uri = new URI(secretReference.getSecretId());
secret = keyVaultClientFactory.getClient("https://" + uri.getHost()).getSecret(uri);
} catch (URISyntaxException e) {
LOGGER.error("Error Processing Key Vault Entry URI.");
ReflectionUtils.rethrowRuntimeException(e);
}

if (secret == null) {
throw new IOException("No Key Vault Secret found for Reference.");
}
secretValue = secret.getValue();
properties.put(key, secret.getValue());
} catch (RuntimeException | IOException e) {
LOGGER.error("Error Retrieving Key Vault Entry");
ReflectionUtils.rethrowRuntimeException(e);
}
return secretValue;
}

void handleFeatureFlag(String key, FeatureFlagConfigurationSetting setting, List<String> trimStrings)
throws JsonProcessingException {
handleJson(setting, trimStrings);
}

void handleJson(ConfigurationSetting setting, List<String> keyPrefixTrimValues)
throws JsonProcessingException {
Map<String, Object> jsonSettings = JsonConfigurationParser.parseJsonSetting(setting);
for (Entry<String, Object> jsonSetting : jsonSettings.entrySet()) {
String key = trimKey(jsonSetting.getKey(), keyPrefixTrimValues);
properties.put(key, jsonSetting.getValue());
}
}


protected String trimKey(String key, List<String> trimStrings) {
key = key.trim();
if (trimStrings != null) {
for (String trim : trimStrings) {
if (key.startsWith(trim)) {
return key.replaceFirst("^" + trim, "").replace('/', '.');
}
}
}
return key.replace("/", ".");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toMap;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -48,23 +47,20 @@
* i.e. If connecting to 2 stores and have 2 labels set 4 AppConfigurationPropertySources need to be created.
* </p>
*/
final class AppConfigurationFeatureManagementPropertySource extends AppConfigurationPropertySource {
class AppConfigurationFeatureManagementPropertySource extends AppConfigurationPropertySource {

private static final ObjectMapper CASE_INSENSITIVE_MAPPER = JsonMapper.builder()
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build();

private final List<ConfigurationSetting> featureConfigurationSettings;
private final String keyFilter;

private final String[] labelFilter;

AppConfigurationFeatureManagementPropertySource(String originEndpoint, AppConfigurationReplicaClient replicaClient,
String keyFilter, String[] labelFilter) {
super("FM_" + originEndpoint, replicaClient, keyFilter, labelFilter);
featureConfigurationSettings = new ArrayList<>();
}

private static List<Object> convertToListOrEmptyList(Map<String, Object> parameters, String key) {
List<Object> listObjects = CASE_INSENSITIVE_MAPPER.convertValue(parameters.get(key),
new TypeReference<List<Object>>() {
});
return listObjects == null ? emptyList() : listObjects;
super("FM_" + originEndpoint + "/" + getLabelName(labelFilter), replicaClient);
this.keyFilter = keyFilter;
this.labelFilter = labelFilter;
}

/**
Expand All @@ -79,7 +75,8 @@ private static List<Object> convertToListOrEmptyList(Map<String, Object> paramet
* </p>
*
*/
public void initProperties() {
@Override
public void initProperties(List<String> trim) {
SettingSelector settingSelector = new SettingSelector();

String keyFilter = SELECT_ALL_FEATURE_FLAGS;
Expand All @@ -97,38 +94,41 @@ public void initProperties() {
settingSelector.setLabelFilter(label);

List<ConfigurationSetting> features = replicaClient.listSettings(settingSelector);
TracingInfo tracing = replicaClient.getTracingInfo();

// Reading In Features
for (ConfigurationSetting setting : features) {
if (setting instanceof FeatureFlagConfigurationSetting
&& FEATURE_FLAG_CONTENT_TYPE.equals(setting.getContentType())) {
featureConfigurationSettings.add(setting);
FeatureFlagConfigurationSetting featureFlag = (FeatureFlagConfigurationSetting) setting;

String configName = FEATURE_MANAGEMENT_KEY
+ setting.getKey().trim().substring(FEATURE_FLAG_PREFIX.length());

updateTelemetry(featureFlag, tracing);

properties.put(configName, createFeature(featureFlag));
processFeatureFlag(null, (FeatureFlagConfigurationSetting) setting, null);
}
}
}
}

List<ConfigurationSetting> getFeatureFlagSettings() {
return featureConfigurationSettings;
}

protected void processFeatureFlag(String key, FeatureFlagConfigurationSetting setting, List<String> trimStrings) {
TracingInfo tracing = replicaClient.getTracingInfo();
featureConfigurationSettings.add(setting);
FeatureFlagConfigurationSetting featureFlag = setting;

String configName = FEATURE_MANAGEMENT_KEY + setting.getKey().trim().substring(FEATURE_FLAG_PREFIX.length());

updateTelemetry(featureFlag, tracing);

properties.put(configName, createFeature(featureFlag));
}

/**
* Creates a {@code Feature} from a {@code KeyValueItem}
*
* @param item Used to create Features before being converted to be set into properties.
* @return Feature created from KeyValueItem
*/
@SuppressWarnings("unchecked")
private Object createFeature(FeatureFlagConfigurationSetting item) {
protected static Object createFeature(FeatureFlagConfigurationSetting item) {
String key = getFeatureSimpleName(item);
String requirementType = DEFAULT_REQUIREMENT_TYPE;
try {
Expand Down Expand Up @@ -181,31 +181,37 @@ private Object createFeature(FeatureFlagConfigurationSetting item) {

}
return feature;

}

/**
* Looks at each filter used in a Feature Flag to check what types it is using.
*
* @param featureFlag FeatureFlagConfigurationSetting
* @param tracing The TracingInfo for this store.
*/
private void updateTelemetry(FeatureFlagConfigurationSetting featureFlag, TracingInfo tracing) {
protected static void updateTelemetry(FeatureFlagConfigurationSetting featureFlag, TracingInfo tracing) {
for (FeatureFlagFilter filter : featureFlag.getClientFilters()) {
tracing.getFeatureFlagTracing().updateFeatureFilterTelemetry(filter.getName());
}
}

private String getFeatureSimpleName(ConfigurationSetting setting) {
private static String getFeatureSimpleName(ConfigurationSetting setting) {
return setting.getKey().trim().substring(FEATURE_FLAG_PREFIX.length());
}

private Map<String, Object> mapValuesByIndex(List<Object> users) {

@SuppressWarnings("null")
private static Map<String, Object> mapValuesByIndex(List<Object> users) {
return IntStream.range(0, users.size()).boxed().collect(toMap(String::valueOf, users::get));
}

private void switchKeyValues(Map<String, Object> parameters, String oldKey, String newKey, Object value) {
private static void switchKeyValues(Map<String, Object> parameters, String oldKey, String newKey, Object value) {
parameters.put(newKey, value);
parameters.remove(oldKey);
}

private static List<Object> convertToListOrEmptyList(Map<String, Object> parameters, String key) {
List<Object> listObjects =
CASE_INSENSITIVE_MAPPER.convertValue(parameters.get(key), new TypeReference<List<Object>>() {});
return listObjects == null ? emptyList() : listObjects;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,27 @@ public class AppConfigurationKeyVaultClientFactory {
private final boolean credentialsConfigured;

private final boolean isConfigured;

private final int timeout;

public AppConfigurationKeyVaultClientFactory(SecretClientCustomizer keyVaultClientProvider,
KeyVaultSecretProvider keyVaultSecretProvider, SecretClientBuilderFactory secretClientFactory,
boolean credentialsConfigured) {
boolean credentialsConfigured, int timeout) {
this.keyVaultClientProvider = keyVaultClientProvider;
this.keyVaultSecretProvider = keyVaultSecretProvider;
this.secretClientFactory = secretClientFactory;
keyVaultClients = new HashMap<>();
this.credentialsConfigured = credentialsConfigured;
isConfigured = keyVaultClientProvider != null || credentialsConfigured;
this.timeout = timeout;
}

public AppConfigurationSecretClientManager getClient(String host) {
// Check if we already have a client for this key vault, if not we will make
// one
if (!keyVaultClients.containsKey(host)) {
AppConfigurationSecretClientManager client = new AppConfigurationSecretClientManager(host,
keyVaultClientProvider, keyVaultSecretProvider, secretClientFactory, credentialsConfigured);
keyVaultClientProvider, keyVaultSecretProvider, secretClientFactory, credentialsConfigured, timeout);
keyVaultClients.put(host, client);
}
return keyVaultClients.get(host);
Expand Down
Loading

0 comments on commit 15ffff2

Please sign in to comment.