Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Features Added

- Added support for Snapshot References in App Configuration stores. Snapshot reference settings are now automatically resolved, loading the referenced snapshot's configuration settings as properties.

### Breaking Changes

### Bugs Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.azure.data.appconfiguration.models.SettingSelector;
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.FEATURE_FLAG_CONTENT_TYPE;
import com.azure.spring.cloud.appconfiguration.config.implementation.configuration.WatchedConfigurationSettings;

/**
* Azure App Configuration PropertySource unique per Store Label(Profile) combo.
Expand All @@ -44,16 +45,23 @@ class AppConfigurationApplicationSettingPropertySource extends AppConfigurationP

private final List<String> tagsFilter;

protected List<ConfigurationSetting> featureFlagsList = new ArrayList<>();

private static final String SNAPSHOT_REF_CONTENT_TYPE = "application/json; profile=\"https://azconfig.io/mime-profiles/snapshot-ref\"; charset=utf-8";

protected final FeatureFlagClient featureFlagClient;

AppConfigurationApplicationSettingPropertySource(String name, AppConfigurationReplicaClient replicaClient,
AppConfigurationKeyVaultClientFactory keyVaultClientFactory, String keyFilter, String[] labelFilters,
List<String> tagsFilter) {
List<String> tagsFilter, FeatureFlagClient featureFlagClient) {
// The context alone does not uniquely define a PropertySource, append storeName
// and label to uniquely define a PropertySource
super(name + getLabelName(labelFilters), replicaClient);
this.keyVaultClientFactory = keyVaultClientFactory;
this.keyFilter = keyFilter;
this.labelFilters = labelFilters;
this.tagsFilter = tagsFilter;
this.featureFlagClient = featureFlagClient;
}

/**
Expand All @@ -65,7 +73,8 @@ class AppConfigurationApplicationSettingPropertySource extends AppConfigurationP
* @throws InvalidConfigurationPropertyValueException thrown if fails to parse Json content type
*/
@Override
public void initProperties(List<String> keyPrefixTrimValues, Context context) throws InvalidConfigurationPropertyValueException {
public void initProperties(List<String> keyPrefixTrimValues, Context context)
throws InvalidConfigurationPropertyValueException {

replicaClient.getTracingInfo().resetAiConfigurationTracing();

Expand All @@ -82,14 +91,21 @@ public void initProperties(List<String> keyPrefixTrimValues, Context context) th
}

// * for wildcard match
processConfigurationSettings(replicaClient.listSettings(settingSelector, context), settingSelector.getKeyFilter(),
keyPrefixTrimValues);
processConfigurationSettings(replicaClient.listSettings(settingSelector, context),
settingSelector.getKeyFilter(),
keyPrefixTrimValues, context);
}
}

protected void processConfigurationSettings(List<ConfigurationSetting> settings, String keyFilter,
List<String> keyPrefixTrimValues)
List<String> keyPrefixTrimValues, Context context)
throws InvalidConfigurationPropertyValueException {
// Reset per-label state so flags from a previous label aren't re-processed.
featureFlagsList.clear();

// First resolve snapshot references
settings = resolveSnapshotReferences(settings, context);

for (ConfigurationSetting setting : settings) {
replicaClient.getTracingInfo().updateAiConfigurationTracing(setting.getContentType());
if (keyPrefixTrimValues == null && StringUtils.hasText(keyFilter)) {
Expand All @@ -110,6 +126,30 @@ protected void processConfigurationSettings(List<ConfigurationSetting> settings,
properties.put(key, setting.getValue());
}
}

WatchedConfigurationSettings featureFlags = new WatchedConfigurationSettings(null, featureFlagsList);
featureFlagClient.processFeatureFlags(featureFlags, replicaClient.getEndpoint());
}

private List<ConfigurationSetting> resolveSnapshotReferences(List<ConfigurationSetting> settings, Context context) {
List<ConfigurationSetting> resolvedSettings = new ArrayList<>();
for (ConfigurationSetting setting : settings) {
if (SNAPSHOT_REF_CONTENT_TYPE.equals(setting.getContentType())) {
// Handle snapshot reference
replicaClient.getTracingInfo().setUsesSnapshotReference();
List<ConfigurationSetting> snapshotSettings = replicaClient.listSettingSnapshot(setting.getValue(),
context);
resolvedSettings.addAll(snapshotSettings);
Comment thread
mrm9084 marked this conversation as resolved.
} else if (setting instanceof FeatureFlagConfigurationSetting) {
// We need to strip feature flags as we only support feature flags from snapshots, and if they are in a
// snapshot reference we won't be able to resolve them.
LOGGER.warn("Feature Flag {} with key {} is being ignored as it is not from a snapshot reference.",
setting.getLabel(), setting.getKey());
} else {
resolvedSettings.add(setting);
}
}
return resolvedSettings;
}

/**
Expand All @@ -119,7 +159,7 @@ protected void processConfigurationSettings(List<ConfigurationSetting> settings,
* @param secretReference {"uri": "&lt;your-vault-url&gt;/secret/&lt;secret&gt;/&lt;version&gt;"}
* @throws InvalidConfigurationPropertyValueException
*/
private void handleKeyVaultReference(String key, SecretReferenceConfigurationSetting secretReference)
protected void handleKeyVaultReference(String key, SecretReferenceConfigurationSetting secretReference)
throws InvalidConfigurationPropertyValueException {
// Parsing Key Vault Reference for URI
try {
Expand All @@ -138,10 +178,11 @@ private void handleKeyVaultReference(String key, SecretReferenceConfigurationSet

void handleFeatureFlag(String key, FeatureFlagConfigurationSetting setting, List<String> trimStrings)
throws InvalidConfigurationPropertyValueException {
// Feature Flags aren't loaded as configuration, but are loaded as feature flags when loading a snapshot.
// Feature Flags are only part of this if they come from a snapshot
featureFlagsList.add(setting);
}

private void handleJson(ConfigurationSetting setting, List<String> keyPrefixTrimValues)
protected void handleJson(ConfigurationSetting setting, List<String> keyPrefixTrimValues)
throws InvalidConfigurationPropertyValueException {
Map<String, Object> jsonSettings = JsonConfigurationParser.parseJsonSetting(setting);
for (Entry<String, Object> jsonSetting : jsonSettings.entrySet()) {
Expand All @@ -150,7 +191,7 @@ private void handleJson(ConfigurationSetting setting, List<String> keyPrefixTrim
}
}

private String trimKey(String key, List<String> trimStrings) {
protected String trimKey(String key, List<String> trimStrings) {
key = key.trim();
if (trimStrings != null) {
for (String trim : trimStrings) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,9 @@ public class AppConfigurationConstants {
* Constant for tracing AI Chat Completion configuration usage.
*/
public static final String AI_CHAT_COMPLETION_FEATURE = "AICC";

/**
* Constant for tracing snapshot reference usage.
*/
public static final String SNAPSHOT_REFERENCE_TAG = "SnapshotRef";
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.appconfiguration.config.implementation;

import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.util.StringUtils;

import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
import com.azure.data.appconfiguration.models.SecretReferenceConfigurationSetting;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.FEATURE_FLAG_CONTENT_TYPE;
import com.azure.spring.cloud.appconfiguration.config.implementation.configuration.WatchedConfigurationSettings;

/**
Expand All @@ -23,18 +25,13 @@ final class AppConfigurationSnapshotPropertySource extends AppConfigurationAppli

private final String snapshotName;

private final FeatureFlagClient featureFlagClient;

private List<ConfigurationSetting> featureFlagsList = new ArrayList<>();

AppConfigurationSnapshotPropertySource(String name, AppConfigurationReplicaClient replicaClient,
AppConfigurationKeyVaultClientFactory keyVaultClientFactory, String snapshotName,
FeatureFlagClient featureFlagClient) {
// The context alone does not uniquely define a PropertySource, append storeName
// and label to uniquely define a PropertySource
super(name, replicaClient, keyVaultClientFactory, null, null, null);
super(name, replicaClient, keyVaultClientFactory, null, null, null, featureFlagClient);
this.snapshotName = snapshotName;
this.featureFlagClient = featureFlagClient;
}

/**
Expand All @@ -43,12 +40,29 @@ final class AppConfigurationSnapshotPropertySource extends AppConfigurationAppli
* </p>
*
* @param trim prefix to trim
* @param isRefresh true if a refresh triggered the loading of the Snapshot.
* @param context request context propagated to the App Configuration client.
* @throws InvalidConfigurationPropertyValueException thrown if fails to parse Json content type
*/
@Override
public void initProperties(List<String> trim, Context context) throws InvalidConfigurationPropertyValueException {
replicaClient.getTracingInfo().resetAiConfigurationTracing();
processConfigurationSettings(replicaClient.listSettingSnapshot(snapshotName, context), null, trim);
List<ConfigurationSetting> settings = replicaClient.listSettingSnapshot(snapshotName, context);

for (ConfigurationSetting setting : settings) {
String key = trimKey(setting.getKey(), trim);

Comment on lines +51 to +53
if (setting instanceof SecretReferenceConfigurationSetting) {
handleKeyVaultReference(key, (SecretReferenceConfigurationSetting) setting);
} else if (setting instanceof FeatureFlagConfigurationSetting
&& FEATURE_FLAG_CONTENT_TYPE.equals(setting.getContentType())) {
handleFeatureFlag(key, (FeatureFlagConfigurationSetting) setting, trim);
} else if (StringUtils.hasText(setting.getContentType())
&& JsonConfigurationParser.isJsonContentType(setting.getContentType())) {
handleJson(setting, trim);
} else {
properties.put(key, setting.getValue());
}
}

WatchedConfigurationSettings featureFlags = new WatchedConfigurationSettings(null, featureFlagsList);
featureFlagClient.processFeatureFlags(featureFlags, replicaClient.getEndpoint());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ private List<AppConfigurationPropertySource> createSettings(AppConfigurationRepl
propertySource = new AppConfigurationApplicationSettingPropertySource(
selectedKeys.getKeyFilter() + resource.getEndpoint() + "/", client, keyVaultClientFactory,
selectedKeys.getKeyFilter(), selectedKeys.getLabelFilter(profiles),
selectedKeys.getTagsFilter());
selectedKeys.getTagsFilter(), featureFlagClient);
}
propertySource.initProperties(resource.getTrimKeyPrefix(), requestContext);
sourceList.add(propertySource);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.KEY_VAULT_CONFIGURED_TRACING;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.LOAD_BALANCING_FEATURE;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.PUSH_REFRESH;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.SNAPSHOT_REFERENCE_TAG;
import com.azure.spring.cloud.appconfiguration.config.implementation.HostType;
import com.azure.spring.cloud.appconfiguration.config.implementation.JsonConfigurationParser;
import com.azure.spring.cloud.appconfiguration.config.implementation.RequestTracingConstants;
Expand All @@ -35,6 +36,8 @@ public class TracingInfo {

private boolean usesAiccConfiguration = false;

private boolean usesSnapshotReference = false;

private boolean isFailoverRequest = false;

public TracingInfo(boolean isKeyVaultConfigured, int replicaCount, Configuration configuration) {
Expand All @@ -58,6 +61,13 @@ public void setFailoverRequest() {
this.isFailoverRequest = true;
}

/**
* Marks snapshot references as used.
*/
public void setUsesSnapshotReference() {
this.usesSnapshotReference = true;
}

/**
* Resets AI configuration tracing flags.
*/
Expand Down Expand Up @@ -199,6 +209,12 @@ private String createFeaturesString() {
}
sb.append(AI_CHAT_COMPLETION_FEATURE);
}
if (usesSnapshotReference) {
if (sb.length() > 0) {
sb.append(DELIMITER);
}
sb.append(SNAPSHOT_REFERENCE_TAG);
}
return sb.toString();
}

Expand Down
Loading