Skip to content

Commit 7ffe511

Browse files
Merge pull request #652 from Azure/main
Merge main to release/stable/v8
2 parents 17843ce + 65ed480 commit 7ffe511

20 files changed

+852
-623
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.1.1</OfficialVersion>
24+
<OfficialVersion>8.1.2</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.1.1</OfficialVersion>
27+
<OfficialVersion>8.1.2</OfficialVersion>
2828
</PropertyGroup>
2929

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

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//
44
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
55
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.DependencyInjection.Extensions;
67
using System;
78
using System.Collections.Generic;
89
using System.Security;
@@ -99,11 +100,11 @@ public static IServiceCollection AddAzureAppConfiguration(this IServiceCollectio
99100
if (!_isProviderDisabled)
100101
{
101102
services.AddLogging();
102-
services.AddSingleton<IConfigurationRefresherProvider, AzureAppConfigurationRefresherProvider>();
103+
services.TryAddSingleton<IConfigurationRefresherProvider, AzureAppConfigurationRefresherProvider>();
103104
}
104105
else
105106
{
106-
services.AddSingleton<IConfigurationRefresherProvider, EmptyConfigurationRefresherProvider>();
107+
services.TryAddSingleton<IConfigurationRefresherProvider, EmptyConfigurationRefresherProvider>();
107108
}
108109

109110
return services;

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

Lines changed: 517 additions & 517 deletions
Large diffs are not rendered by default.

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,13 @@ public AzureAppConfigurationProvider(IConfigurationClientManager configClientMan
118118

119119
if (options.RegisterAllEnabled)
120120
{
121+
if (options.KvCollectionRefreshInterval <= TimeSpan.Zero)
122+
{
123+
throw new ArgumentException(
124+
$"{nameof(options.KvCollectionRefreshInterval)} must be greater than zero seconds when using RegisterAll for refresh",
125+
nameof(options));
126+
}
127+
121128
MinRefreshInterval = TimeSpan.FromTicks(Math.Min(minWatcherRefreshInterval.Ticks, options.KvCollectionRefreshInterval.Ticks));
122129
}
123130
else if (hasWatchers)
@@ -206,7 +213,7 @@ public async Task RefreshAsync(CancellationToken cancellationToken)
206213
var utcNow = DateTimeOffset.UtcNow;
207214
IEnumerable<KeyValueWatcher> refreshableIndividualKvWatchers = _options.IndividualKvWatchers.Where(kvWatcher => utcNow >= kvWatcher.NextRefreshTime);
208215
IEnumerable<KeyValueWatcher> refreshableFfWatchers = _options.FeatureFlagWatchers.Where(ffWatcher => utcNow >= ffWatcher.NextRefreshTime);
209-
bool isRefreshDue = utcNow >= _nextCollectionRefreshTime;
216+
bool isRefreshDue = _options.RegisterAllEnabled && utcNow >= _nextCollectionRefreshTime;
210217

211218
// Skip refresh if mappedData is loaded, but none of the watchers or adapters are refreshable.
212219
if (_mappedData != null &&
@@ -412,7 +419,7 @@ await ExecuteWithFailOverPolicyAsync(clients, async (client) =>
412419
}
413420
}
414421

415-
if (isRefreshDue)
422+
if (_options.RegisterAllEnabled && isRefreshDue)
416423
{
417424
_nextCollectionRefreshTime = DateTimeOffset.UtcNow.Add(_options.KvCollectionRefreshInterval);
418425
}
@@ -590,9 +597,21 @@ private async Task<Dictionary<string, string>> PrepareData(Dictionary<string, Co
590597
// Reset old feature flag tracing in order to track the information present in the current response from server.
591598
_options.FeatureFlagTracing.ResetFeatureFlagTracing();
592599

600+
// Reset old request tracing values for content type
601+
if (_requestTracingEnabled && _requestTracingOptions != null)
602+
{
603+
_requestTracingOptions.ResetAiConfigurationTracing();
604+
}
605+
593606
foreach (KeyValuePair<string, ConfigurationSetting> kvp in data)
594607
{
595608
IEnumerable<KeyValuePair<string, string>> keyValuePairs = null;
609+
610+
if (_requestTracingEnabled && _requestTracingOptions != null)
611+
{
612+
_requestTracingOptions.UpdateAiConfigurationTracing(kvp.Value.ContentType);
613+
}
614+
596615
keyValuePairs = await ProcessAdapters(kvp.Value, cancellationToken).ConfigureAwait(false);
597616

598617
foreach (KeyValuePair<string, string> kv in keyValuePairs)
@@ -629,7 +648,7 @@ private async Task LoadAsync(bool ignoreFailures, CancellationToken cancellation
629648
{
630649
IEnumerable<ConfigurationClient> clients = _configClientManager.GetClients();
631650

632-
if (_requestTracingOptions != null)
651+
if (_requestTracingEnabled && _requestTracingOptions != null)
633652
{
634653
_requestTracingOptions.ReplicaCount = clients.Count() - 1;
635654
}

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

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,38 @@ internal class AzureAppConfigurationRefresherProvider : IConfigurationRefresherP
1313
{
1414
private static readonly PropertyInfo _propertyInfo = typeof(ChainedConfigurationProvider).GetProperty("Configuration", BindingFlags.Public | BindingFlags.Instance);
1515

16-
public IEnumerable<IConfigurationRefresher> Refreshers { get; }
16+
private readonly IConfiguration _configuration;
17+
private readonly ILoggerFactory _loggerFactory;
18+
private IEnumerable<IConfigurationRefresher> _refreshers;
19+
private bool _rediscoveredRefreshers = false;
1720

18-
public AzureAppConfigurationRefresherProvider(IConfiguration configuration, ILoggerFactory _loggerFactory)
21+
public IEnumerable<IConfigurationRefresher> Refreshers
1922
{
20-
var configurationRoot = configuration as IConfigurationRoot;
23+
get
24+
{
25+
// Ensure latest refreshers are discovered if the configuration has changed since the constructor was called
26+
if (!_rediscoveredRefreshers)
27+
{
28+
_refreshers = DiscoverRefreshers();
29+
30+
_rediscoveredRefreshers = true;
31+
}
32+
33+
return _refreshers;
34+
}
35+
}
36+
37+
public AzureAppConfigurationRefresherProvider(IConfiguration configuration, ILoggerFactory loggerFactory)
38+
{
39+
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
40+
_loggerFactory = loggerFactory;
41+
42+
_refreshers = DiscoverRefreshers();
43+
}
44+
45+
private IEnumerable<IConfigurationRefresher> DiscoverRefreshers()
46+
{
47+
var configurationRoot = _configuration as IConfigurationRoot;
2148
var refreshers = new List<IConfigurationRefresher>();
2249

2350
FindRefreshers(configurationRoot, _loggerFactory, refreshers);
@@ -27,7 +54,7 @@ public AzureAppConfigurationRefresherProvider(IConfiguration configuration, ILog
2754
throw new InvalidOperationException("Unable to access the Azure App Configuration provider. Please ensure that it has been configured correctly.");
2855
}
2956

30-
Refreshers = refreshers;
57+
return refreshers;
3158
}
3259

3360
private void FindRefreshers(IConfigurationRoot configurationRoot, ILoggerFactory loggerFactory, List<IConfigurationRefresher> refreshers)

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
using Azure;
55
using Azure.Data.AppConfiguration;
66
using Azure.Security.KeyVault.Secrets;
7+
using Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions;
8+
using Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement;
79
using System;
810
using System.Collections.Generic;
911
using System.Linq;
12+
using System.Net.Mime;
1013
using System.Text.Json;
1114
using System.Threading;
1215
using System.Threading.Tasks;
@@ -72,8 +75,15 @@ KeyVaultReferenceException CreateKeyVaultReferenceException(string message, Conf
7275

7376
public bool CanProcess(ConfigurationSetting setting)
7477
{
75-
string contentType = setting?.ContentType?.Split(';')[0].Trim();
76-
return string.Equals(contentType, KeyVaultConstants.ContentType);
78+
if (setting == null ||
79+
string.IsNullOrWhiteSpace(setting.Value) ||
80+
string.IsNullOrWhiteSpace(setting.ContentType))
81+
{
82+
return false;
83+
}
84+
85+
return setting.ContentType.TryParseContentType(out ContentType contentType)
86+
&& contentType.IsKeyVaultReference();
7787
}
7888

7989
public void OnChangeDetected(ConfigurationSetting setting = null)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using Azure.Data.AppConfiguration;
2-
using Azure;
1+
using Azure;
2+
using Azure.Data.AppConfiguration;
33
using System.Collections.Generic;
44

55
namespace Microsoft.Extensions.Configuration.AzureAppConfiguration

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ internal class RequestTracingConstants
3131
public const string ReplicaCountKey = "ReplicaCount";
3232
public const string FeaturesKey = "Features";
3333
public const string LoadBalancingEnabledTag = "LB";
34+
public const string AIConfigurationTag = "AI";
35+
public const string AIChatCompletionConfigurationTag = "AICC";
36+
3437
public const string SignalRUsedTag = "SignalR";
3538
public const string FailoverRequestTag = "Failover";
3639
public const string PushRefreshTag = "PushRefresh";
@@ -54,5 +57,8 @@ internal class RequestTracingConstants
5457
public const string SignalRAssemblyName = "Microsoft.AspNetCore.SignalR";
5558

5659
public const string Delimiter = "+";
60+
61+
public const string AIMimeProfile = "https://azconfig.io/mime-profiles/ai";
62+
public const string AIChatCompletionMimeProfile = "https://azconfig.io/mime-profiles/ai/chat-completion";
5763
}
5864
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
using System.Linq;
5+
using System;
6+
using System.Net.Mime;
7+
using Microsoft.Extensions.Configuration.AzureAppConfiguration.AzureKeyVault;
8+
using Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement;
9+
using System.Collections.Generic;
10+
11+
namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions
12+
{
13+
internal static class ContentTypeExtensions
14+
{
15+
public static bool IsAi(this ContentType contentType)
16+
{
17+
return contentType != null &&
18+
contentType.IsJson() &&
19+
!contentType.IsFeatureFlag() &&
20+
!contentType.IsKeyVaultReference() &&
21+
contentType.Parameters.ContainsKey("profile") &&
22+
!string.IsNullOrEmpty(contentType.Parameters["profile"]) &&
23+
contentType.Parameters["profile"].StartsWith(RequestTracingConstants.AIMimeProfile);
24+
}
25+
26+
public static bool IsAiChatCompletion(this ContentType contentType)
27+
{
28+
return contentType != null &&
29+
contentType.IsJson() &&
30+
!contentType.IsFeatureFlag() &&
31+
!contentType.IsKeyVaultReference() &&
32+
contentType.Parameters.ContainsKey("profile") &&
33+
!string.IsNullOrEmpty(contentType.Parameters["profile"]) &&
34+
contentType.Parameters["profile"].StartsWith(RequestTracingConstants.AIChatCompletionMimeProfile);
35+
}
36+
37+
public static bool IsJson(this ContentType contentType)
38+
{
39+
if (contentType == null)
40+
{
41+
return false;
42+
}
43+
44+
string acceptedMainType = "application";
45+
string acceptedSubType = "json";
46+
47+
ReadOnlySpan<char> mediaTypeSpan = contentType.MediaType.AsSpan();
48+
49+
// Since contentType has been validated using System.Net.Mime.ContentType,
50+
// mediaType will always have exactly 2 parts after splitting on '/'
51+
int slashIndex = mediaTypeSpan.IndexOf('/');
52+
53+
if (mediaTypeSpan.Slice(0, slashIndex).Equals(acceptedMainType.AsSpan(), StringComparison.OrdinalIgnoreCase))
54+
{
55+
ReadOnlySpan<char> subTypeSpan = mediaTypeSpan.Slice(slashIndex + 1);
56+
57+
while (!subTypeSpan.IsEmpty)
58+
{
59+
int plusIndex = subTypeSpan.IndexOf('+');
60+
61+
ReadOnlySpan<char> currentSubType = plusIndex == -1 ? subTypeSpan : subTypeSpan.Slice(0, plusIndex);
62+
63+
if (currentSubType.Equals(acceptedSubType.AsSpan(), StringComparison.OrdinalIgnoreCase))
64+
{
65+
return true;
66+
}
67+
68+
subTypeSpan = plusIndex == -1 ? ReadOnlySpan<char>.Empty : subTypeSpan.Slice(plusIndex + 1);
69+
}
70+
}
71+
72+
return false;
73+
}
74+
75+
public static bool IsFeatureFlag(this ContentType contentType)
76+
{
77+
return contentType.MediaType.Equals(FeatureManagementConstants.ContentType);
78+
}
79+
80+
public static bool IsKeyVaultReference(this ContentType contentType)
81+
{
82+
return contentType.MediaType.Equals(KeyVaultConstants.ContentType);
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)