Skip to content

Commit 9216437

Browse files
authored
Merge pull request #604 from Azure/rossgrambo/merge-main-to-preview
Merge main to preview with resolved conflicts
2 parents 51d4ad7 + 0619626 commit 9216437

File tree

15 files changed

+118
-121
lines changed

15 files changed

+118
-121
lines changed

src/Microsoft.Azure.AppConfiguration.AspNetCore/Microsoft.Azure.AppConfiguration.AspNetCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<!-- Nuget Package Version Settings -->
2222

2323
<PropertyGroup>
24-
<OfficialVersion>8.0.0-preview.3</OfficialVersion>
24+
<OfficialVersion>8.0.0</OfficialVersion>
2525
</PropertyGroup>
2626

2727
<PropertyGroup Condition="'$(CDP_PATCH_NUMBER)'!='' AND '$(CDP_BUILD_TYPE)'=='Official'">

src/Microsoft.Azure.AppConfiguration.Functions.Worker/Microsoft.Azure.AppConfiguration.Functions.Worker.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<!-- Nuget Package Version Settings -->
2525

2626
<PropertyGroup>
27-
<OfficialVersion>8.0.0-preview.3</OfficialVersion>
27+
<OfficialVersion>8.0.0</OfficialVersion>
2828
</PropertyGroup>
2929

3030
<PropertyGroup Condition="'$(CDP_PATCH_NUMBER)'!='' AND '$(CDP_BUILD_TYPE)'=='Official'">

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationKeyVaultOptions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,17 @@ namespace Microsoft.Extensions.Configuration.AzureAppConfiguration
1414
/// </summary>
1515
public class AzureAppConfigurationKeyVaultOptions
1616
{
17+
// 6 retries is the highest number that will make the total retry time comfortably fall under the default startup timeout of 100 seconds.
18+
// This allows the provider to throw a KeyVaultReferenceException with all relevant information and halt startup instead of timing out.
19+
private const int KeyVaultMaxRetries = 6;
20+
1721
internal TokenCredential Credential;
22+
internal SecretClientOptions ClientOptions = new SecretClientOptions
23+
{
24+
Retry = {
25+
MaxRetries = KeyVaultMaxRetries
26+
}
27+
};
1828
internal List<SecretClient> SecretClients = new List<SecretClient>();
1929
internal Func<Uri, ValueTask<string>> SecretResolver;
2030
internal Dictionary<string, TimeSpan> SecretRefreshIntervals = new Dictionary<string, TimeSpan>();
@@ -31,6 +41,17 @@ public AzureAppConfigurationKeyVaultOptions SetCredential(TokenCredential creden
3141
return this;
3242
}
3343

44+
/// <summary>
45+
/// Configures the client options used when connecting to key vaults that have no registered <see cref="SecretClient"/>.
46+
/// The client options will not affect <see cref="SecretClient"/> instances registered via <see cref="Register(SecretClient)"/>.
47+
/// </summary>
48+
/// <param name="configure">A callback used to configure secret client options.</param>
49+
public AzureAppConfigurationKeyVaultOptions ConfigureClientOptions(Action<SecretClientOptions> configure)
50+
{
51+
configure?.Invoke(ClientOptions);
52+
return this;
53+
}
54+
3455
/// <summary>
3556
/// Registers the specified <see cref="SecretClient"/> instance to use to resolve key vault references for secrets from associated key vault.
3657
/// </summary>

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,15 @@ await CallWithRequestTracing(
390390
// Invalidate the cached Key Vault secret (if any) for this ConfigurationSetting
391391
foreach (IKeyValueAdapter adapter in _options.Adapters)
392392
{
393-
adapter.OnChangeDetected(change.Current);
393+
// If the current setting is null, try to pass the previous setting instead
394+
if (change.Current != null)
395+
{
396+
adapter.OnChangeDetected(change.Current);
397+
}
398+
else if (change.Previous != null)
399+
{
400+
adapter.OnChangeDetected(change.Previous);
401+
}
394402
}
395403
}
396404
}
@@ -671,17 +679,6 @@ private async Task<bool> TryInitializeAsync(IEnumerable<ConfigurationClient> cli
671679

672680
throw;
673681
}
674-
catch (KeyVaultReferenceException exception)
675-
{
676-
if (IsFailOverable(exception))
677-
{
678-
startupExceptions.Add(exception);
679-
680-
return false;
681-
}
682-
683-
throw;
684-
}
685682
catch (AggregateException exception)
686683
{
687684
if (exception.InnerExceptions?.Any(e => e is OperationCanceledException) ?? false)
@@ -1065,15 +1062,6 @@ private async Task<T> ExecuteWithFailOverPolicyAsync<T>(
10651062
throw;
10661063
}
10671064
}
1068-
catch (KeyVaultReferenceException kvre)
1069-
{
1070-
if (!IsFailOverable(kvre) || !clientEnumerator.MoveNext())
1071-
{
1072-
backoffAllClients = true;
1073-
1074-
throw;
1075-
}
1076-
}
10771065
catch (AggregateException ae)
10781066
{
10791067
if (!IsFailOverable(ae) || !clientEnumerator.MoveNext())
@@ -1145,7 +1133,9 @@ private bool IsFailOverable(RequestFailedException rfe)
11451133
{
11461134
if (rfe.Status == HttpStatusCodes.TooManyRequests ||
11471135
rfe.Status == (int)HttpStatusCode.RequestTimeout ||
1148-
rfe.Status >= (int)HttpStatusCode.InternalServerError)
1136+
rfe.Status >= (int)HttpStatusCode.InternalServerError ||
1137+
rfe.Status == (int)HttpStatusCode.Forbidden ||
1138+
rfe.Status == (int)HttpStatusCode.Unauthorized)
11491139
{
11501140
return true;
11511141
}
@@ -1167,20 +1157,6 @@ innerException is SocketException ||
11671157
innerException is IOException;
11681158
}
11691159

1170-
private bool IsFailOverable(KeyVaultReferenceException kvre)
1171-
{
1172-
if (kvre.InnerException is RequestFailedException rfe && IsFailOverable(rfe))
1173-
{
1174-
return true;
1175-
}
1176-
else if (kvre.InnerException is AggregateException ae && IsFailOverable(ae))
1177-
{
1178-
return true;
1179-
}
1180-
1181-
return false;
1182-
}
1183-
11841160
private async Task<Dictionary<string, ConfigurationSetting>> MapConfigurationSettings(Dictionary<string, ConfigurationSetting> data)
11851161
{
11861162
Dictionary<string, ConfigurationSetting> mappedData = new Dictionary<string, ConfigurationSetting>(StringComparer.OrdinalIgnoreCase);

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureKeyVaultReference/AzureKeyVaultKeyValueAdapter.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,15 @@ public void OnChangeDetected(ConfigurationSetting setting = null)
8484
}
8585
else
8686
{
87-
_secretProvider.RemoveSecretFromCache(setting.Key);
87+
if (CanProcess(setting))
88+
{
89+
string secretRefUri = ParseSecretReferenceUri(setting);
90+
91+
if (!string.IsNullOrEmpty(secretRefUri) && Uri.TryCreate(secretRefUri, UriKind.Absolute, out Uri secretUri) && KeyVaultSecretIdentifier.TryCreate(secretUri, out KeyVaultSecretIdentifier secretIdentifier))
92+
{
93+
_secretProvider.RemoveSecretFromCache(secretIdentifier.SourceId);
94+
}
95+
}
8896
}
8997
}
9098

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureKeyVaultReference/AzureKeyVaultSecretProvider.cs

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ internal class AzureKeyVaultSecretProvider
1515
{
1616
private readonly AzureAppConfigurationKeyVaultOptions _keyVaultOptions;
1717
private readonly IDictionary<string, SecretClient> _secretClients;
18-
private readonly Dictionary<string, CachedKeyVaultSecret> _cachedKeyVaultSecrets;
19-
private string _nextRefreshKey;
18+
private readonly Dictionary<Uri, CachedKeyVaultSecret> _cachedKeyVaultSecrets;
19+
private Uri _nextRefreshSourceId;
2020
private DateTimeOffset? _nextRefreshTime;
2121

2222
public AzureKeyVaultSecretProvider(AzureAppConfigurationKeyVaultOptions keyVaultOptions = null)
2323
{
2424
_keyVaultOptions = keyVaultOptions ?? new AzureAppConfigurationKeyVaultOptions();
25-
_cachedKeyVaultSecrets = new Dictionary<string, CachedKeyVaultSecret>(StringComparer.OrdinalIgnoreCase);
25+
_cachedKeyVaultSecrets = new Dictionary<Uri, CachedKeyVaultSecret>();
2626
_secretClients = new Dictionary<string, SecretClient>(StringComparer.OrdinalIgnoreCase);
2727

2828
if (_keyVaultOptions.SecretClients != null)
@@ -39,7 +39,7 @@ public async Task<string> GetSecretValue(KeyVaultSecretIdentifier secretIdentifi
3939
{
4040
string secretValue = null;
4141

42-
if (_cachedKeyVaultSecrets.TryGetValue(key, out CachedKeyVaultSecret cachedSecret) &&
42+
if (_cachedKeyVaultSecrets.TryGetValue(secretIdentifier.SourceId, out CachedKeyVaultSecret cachedSecret) &&
4343
(!cachedSecret.RefreshAt.HasValue || DateTimeOffset.UtcNow < cachedSecret.RefreshAt.Value))
4444
{
4545
return cachedSecret.SecretValue;
@@ -68,12 +68,12 @@ public async Task<string> GetSecretValue(KeyVaultSecretIdentifier secretIdentifi
6868
secretValue = await _keyVaultOptions.SecretResolver(secretIdentifier.SourceId).ConfigureAwait(false);
6969
}
7070

71-
cachedSecret = new CachedKeyVaultSecret(secretValue);
71+
cachedSecret = new CachedKeyVaultSecret(secretValue, secretIdentifier.SourceId);
7272
success = true;
7373
}
7474
finally
7575
{
76-
SetSecretInCache(key, cachedSecret, success);
76+
SetSecretInCache(secretIdentifier.SourceId, key, cachedSecret, success);
7777
}
7878

7979
return secretValue;
@@ -86,16 +86,34 @@ public bool ShouldRefreshKeyVaultSecrets()
8686

8787
public void ClearCache()
8888
{
89-
_cachedKeyVaultSecrets.Clear();
90-
_nextRefreshKey = null;
91-
_nextRefreshTime = null;
89+
var sourceIdsToRemove = new List<Uri>();
90+
91+
var utcNow = DateTimeOffset.UtcNow;
92+
93+
foreach (KeyValuePair<Uri, CachedKeyVaultSecret> secret in _cachedKeyVaultSecrets)
94+
{
95+
if (secret.Value.LastRefreshTime + RefreshConstants.MinimumSecretRefreshInterval < utcNow)
96+
{
97+
sourceIdsToRemove.Add(secret.Key);
98+
}
99+
}
100+
101+
foreach (Uri sourceId in sourceIdsToRemove)
102+
{
103+
_cachedKeyVaultSecrets.Remove(sourceId);
104+
}
105+
106+
if (_cachedKeyVaultSecrets.Any())
107+
{
108+
UpdateNextRefreshableSecretFromCache();
109+
}
92110
}
93111

94-
public void RemoveSecretFromCache(string key)
112+
public void RemoveSecretFromCache(Uri sourceId)
95113
{
96-
_cachedKeyVaultSecrets.Remove(key);
114+
_cachedKeyVaultSecrets.Remove(sourceId);
97115

98-
if (key == _nextRefreshKey)
116+
if (sourceId == _nextRefreshSourceId)
99117
{
100118
UpdateNextRefreshableSecretFromCache();
101119
}
@@ -115,44 +133,49 @@ private SecretClient GetSecretClient(Uri secretUri)
115133
return null;
116134
}
117135

118-
client = new SecretClient(new Uri(secretUri.GetLeftPart(UriPartial.Authority)), _keyVaultOptions.Credential);
136+
client = new SecretClient(
137+
new Uri(secretUri.GetLeftPart(UriPartial.Authority)),
138+
_keyVaultOptions.Credential,
139+
_keyVaultOptions.ClientOptions);
140+
119141
_secretClients.Add(keyVaultId, client);
142+
120143
return client;
121144
}
122145

123-
private void SetSecretInCache(string key, CachedKeyVaultSecret cachedSecret, bool success = true)
146+
private void SetSecretInCache(Uri sourceId, string key, CachedKeyVaultSecret cachedSecret, bool success = true)
124147
{
125148
if (cachedSecret == null)
126149
{
127150
cachedSecret = new CachedKeyVaultSecret();
128151
}
129152

130153
UpdateCacheExpirationTimeForSecret(key, cachedSecret, success);
131-
_cachedKeyVaultSecrets[key] = cachedSecret;
154+
_cachedKeyVaultSecrets[sourceId] = cachedSecret;
132155

133-
if (key == _nextRefreshKey)
156+
if (sourceId == _nextRefreshSourceId)
134157
{
135158
UpdateNextRefreshableSecretFromCache();
136159
}
137160
else if ((cachedSecret.RefreshAt.HasValue && _nextRefreshTime.HasValue && cachedSecret.RefreshAt.Value < _nextRefreshTime.Value)
138161
|| (cachedSecret.RefreshAt.HasValue && !_nextRefreshTime.HasValue))
139162
{
140-
_nextRefreshKey = key;
163+
_nextRefreshSourceId = sourceId;
141164
_nextRefreshTime = cachedSecret.RefreshAt.Value;
142165
}
143166
}
144167

145168
private void UpdateNextRefreshableSecretFromCache()
146169
{
147-
_nextRefreshKey = null;
170+
_nextRefreshSourceId = null;
148171
_nextRefreshTime = DateTimeOffset.MaxValue;
149172

150-
foreach (KeyValuePair<string, CachedKeyVaultSecret> secret in _cachedKeyVaultSecrets)
173+
foreach (KeyValuePair<Uri, CachedKeyVaultSecret> secret in _cachedKeyVaultSecrets)
151174
{
152175
if (secret.Value.RefreshAt.HasValue && secret.Value.RefreshAt.Value < _nextRefreshTime)
153176
{
154177
_nextRefreshTime = secret.Value.RefreshAt;
155-
_nextRefreshKey = secret.Key;
178+
_nextRefreshSourceId = secret.Key;
156179
}
157180
}
158181

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureKeyVaultReference/CachedKeyVaultSecret.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,23 @@ internal class CachedKeyVaultSecret
2222
/// </summary>
2323
public int RefreshAttempts { get; set; }
2424

25-
public CachedKeyVaultSecret(string secretValue = null, DateTimeOffset? refreshAt = null, int refreshAttempts = 0)
25+
/// <summary>
26+
/// The last time this secret was reloaded from Key Vault.
27+
/// </summary>
28+
public DateTimeOffset LastRefreshTime { get; set; }
29+
30+
/// <summary>
31+
/// The source <see cref="Uri"/> for this secret.
32+
/// </summary>
33+
public Uri SourceId { get; }
34+
35+
public CachedKeyVaultSecret(string secretValue = null, Uri sourceId = null, DateTimeOffset? refreshAt = null, int refreshAttempts = 0)
2636
{
2737
SecretValue = secretValue;
2838
RefreshAt = refreshAt;
39+
LastRefreshTime = DateTimeOffset.UtcNow;
2940
RefreshAttempts = refreshAttempts;
41+
SourceId = sourceId;
3042
}
3143
}
3244
}

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Constants/RefreshConstants.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ internal class RefreshConstants
1616
public static readonly TimeSpan MinimumFeatureFlagRefreshInterval = TimeSpan.FromSeconds(1);
1717

1818
// Key Vault secrets
19-
public static readonly TimeSpan MinimumSecretRefreshInterval = TimeSpan.FromSeconds(1);
19+
public static readonly TimeSpan MinimumSecretRefreshInterval = TimeSpan.FromMinutes(1);
2020

2121
// Backoff during refresh failures
2222
public static readonly TimeSpan DefaultMinBackoff = TimeSpan.FromSeconds(30);

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/ConfigurationClientExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public static async Task<KeyValueChange> GetKeyValueChange(this ConfigurationCli
3636
return new KeyValueChange
3737
{
3838
ChangeType = KeyValueChangeType.Modified,
39+
Previous = setting,
3940
Current = response.Value,
4041
Key = setting.Key,
4142
Label = setting.Label
@@ -47,6 +48,7 @@ public static async Task<KeyValueChange> GetKeyValueChange(this ConfigurationCli
4748
return new KeyValueChange
4849
{
4950
ChangeType = KeyValueChangeType.Deleted,
51+
Previous = setting,
5052
Current = null,
5153
Key = setting.Key,
5254
Label = setting.Label
@@ -56,6 +58,7 @@ public static async Task<KeyValueChange> GetKeyValueChange(this ConfigurationCli
5658
return new KeyValueChange
5759
{
5860
ChangeType = KeyValueChangeType.None,
61+
Previous = setting,
5962
Current = setting,
6063
Key = setting.Key,
6164
Label = setting.Label
@@ -158,6 +161,7 @@ await TracingUtils.CallWithRequestTracing(options.RequestTracingEnabled, Request
158161
ChangeType = KeyValueChangeType.Modified,
159162
Key = setting.Key,
160163
Label = options.Label.NormalizeNull(),
164+
Previous = null,
161165
Current = setting
162166
});
163167
string key = setting.Key.Substring(FeatureManagementConstants.FeatureFlagMarker.Length);
@@ -176,6 +180,7 @@ await TracingUtils.CallWithRequestTracing(options.RequestTracingEnabled, Request
176180
ChangeType = KeyValueChangeType.Deleted,
177181
Key = kvp.Key,
178182
Label = options.Label.NormalizeNull(),
183+
Previous = null,
179184
Current = null
180185
});
181186
string key = kvp.Key.Substring(FeatureManagementConstants.FeatureFlagMarker.Length);

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/KeyValueChange.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,7 @@ internal struct KeyValueChange
2121
public string Label { get; set; }
2222

2323
public ConfigurationSetting Current { get; set; }
24+
25+
public ConfigurationSetting Previous { get; set; }
2426
}
2527
}

0 commit comments

Comments
 (0)