Skip to content

add support for connecting to Azure Front Door with Azure App Configuration store as an origin #601

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 72 commits into
base: preview
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
07d60c1
add connect Cdn
SamSadfa Oct 22, 2024
c2fd979
Update src/Microsoft.Extensions.Configuration.AzureAppConfiguration/A…
samsadsam Oct 22, 2024
d16a344
make internal
SamSadfa Oct 22, 2024
c8b3535
Merge branch 'user/samisadfa/connect-cdn' of https://github.com/Azure…
SamSadfa Oct 22, 2024
f3d6b58
Merge branch 'main' into user/samisadfa/connect-cdn
SamSadfa Oct 31, 2024
a2e1c5d
add cdn client manager and api version policy
SamSadfa Oct 31, 2024
9b2bcc9
remove unused param
samsadsam Oct 31, 2024
bf85303
fix format
samsadsam Nov 4, 2024
7c4fddb
users cannot Connect/ConnectCdn at the same time
samsadsam Nov 4, 2024
d323ecd
fix assertion
samsadsam Nov 4, 2024
a2ff1b1
feedback
samsadsam Nov 7, 2024
5f16867
add cdn tracing
samsadsam Nov 7, 2024
65b81fc
merge preview
samsadsam Jan 23, 2025
0e2c621
merge with preview
samsadsam May 28, 2025
8fc28e8
remove api version policy
samsadsam May 28, 2025
2d73ebb
address comment
samsadsam May 28, 2025
ef09e70
add cdn cache busting policy + accessor for sentinel keys
samsadsam May 28, 2025
b8ca58e
now refresh with sentinel key works (tested)
samsadsam May 29, 2025
f1e4bed
move cdn client manager under cdn folder
samsadsam May 29, 2025
47a27ea
add comment
samsadsam May 29, 2025
491e948
add collection monitoring cdn cache busting support
samsadsam May 29, 2025
782dc33
change design to correct one
samsadsam May 30, 2025
dec256b
bug fix
samsadsam May 30, 2025
186288b
another bug fix
samsadsam May 30, 2025
dbfa697
iterate and implement new design
samsadsam Jun 3, 2025
cfc83ce
make code clearer
samsadsam Jun 3, 2025
3631731
nit
samsadsam Jun 3, 2025
2a7f79b
add purpose and simplify hash function
samsadsam Jun 3, 2025
d78de74
nit: cleanup and simplification
samsadsam Jun 3, 2025
43a83a0
nit: use nameof on exception message string
samsadsam Jun 3, 2025
7ff8e94
nit: Cdn.ConfigurationClientManager takes one cdn endpoint
samsadsam Jun 3, 2025
73be7e9
nit: _client -> _clientWrapper
samsadsam Jun 3, 2025
bd941f0
remove last newline, sort etags before
samsadsam Jun 4, 2025
b89f870
rename
samsadsam Jun 4, 2025
4f5108f
handle deleted sentinel kv case
samsadsam Jun 4, 2025
4fc69d3
redesign, no need to ensure state does not regress, eventual consiste…
samsadsam Jun 4, 2025
1d62185
disable load balancing and replica discovery for cdn scenario
samsadsam Jun 4, 2025
6528a36
nit: rename cdn classes accordingly
samsadsam Jun 5, 2025
a846f0f
nit: change HaveCollectionChanged to reflect its new role.
samsadsam Jun 5, 2025
c95e9ce
nit
samsadsam Jun 5, 2025
e1740c9
load balancing is not supported when cdn enabled
samsadsam Jun 5, 2025
29eed77
move load balacing check when cdn is enabled to source
samsadsam Jun 6, 2025
66eccf6
be clearer
samsadsam Jun 6, 2025
326d85b
address avani's comments
samsadsam Jun 6, 2025
984650e
adopt new pattern for tracing features
samsadsam Jun 6, 2025
e64307a
bug fix
samsadsam Jun 6, 2025
f19cb5d
address jimmy's refactor comment
samsadsam Jun 7, 2025
1803e6b
change implementation
samsadsam Jun 7, 2025
4a54b2e
nit
samsadsam Jun 7, 2025
e544048
nit: add new lines
samsadsam Jun 7, 2025
ad8c49f
Merge branch 'preview' into user/samisadfa/connect-cdn
samsadsam Jun 7, 2025
ad5ac9b
add refresh under cdn testing and fix bug
samsadsam Jun 7, 2025
33a9fb5
make tests more robust
samsadsam Jun 7, 2025
de1dc29
Merge branch 'user/samisadfa/connect-cdn' of https://github.com/Azure…
samsadsam Jun 7, 2025
1abb191
nit
samsadsam Jun 7, 2025
911f815
nits
samsadsam Jun 9, 2025
568a7e8
ensure test is inductive
samsadsam Jun 9, 2025
4880269
handle ConnectCdn with SetClientFactory scenario
samsadsam Jun 10, 2025
23acfff
nit: add ms license
samsadsam Jun 10, 2025
bd487dc
move all cdn related tests to cdn tests
samsadsam Jun 10, 2025
2c2a413
add parallel test
samsadsam Jun 10, 2025
d8f280a
fix bug
samsadsam Jun 10, 2025
ae0e448
tests: add delete sentinel key to test
samsadsam Jun 10, 2025
feaec53
done
samsadsam Jun 12, 2025
3f71dd4
done1
samsadsam Jun 12, 2025
c1d15aa
done2
samsadsam Jun 12, 2025
0292b78
Update src/Microsoft.Extensions.Configuration.AzureAppConfiguration/A…
samsadsam Jun 12, 2025
be6d888
done3
samsadsam Jun 12, 2025
cfb5cf6
Merge branch 'user/samisadfa/connect-cdn' of https://github.com/Azure…
samsadsam Jun 12, 2025
eef8180
donedone
samsadsam Jun 12, 2025
cae49ce
donedonedone
samsadsam Jun 23, 2025
39ddcae
remove auth header when connecting to cdn
samsadsam Jun 25, 2025
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 @@ -302,6 +302,11 @@ public AzureAppConfigurationOptions Connect(string connectionString)
/// </param>
public AzureAppConfigurationOptions Connect(IEnumerable<string> connectionStrings)
{
if (Credential is EmptyTokenCredential)
{
throw new InvalidOperationException("Cannot connect to both Azure App Configuration and CDN at the same time.");
}

if (connectionStrings == null || !connectionStrings.Any())
{
throw new ArgumentNullException(nameof(connectionStrings));
Expand All @@ -318,6 +323,27 @@ public AzureAppConfigurationOptions Connect(IEnumerable<string> connectionString
return this;
}

/// <summary>
/// Connect the provider to CDN endpoint.
/// </summary>
/// <param name="endpoint">The endpoint of the CDN instance to connect to.</param>
public AzureAppConfigurationOptions ConnectCdn(Uri endpoint)
{
if ((Credential != null && !(Credential is EmptyTokenCredential)) || (ConnectionStrings?.Any() ?? false))
{
throw new InvalidOperationException("Cannot connect to both Azure App Configuration and CDN at the same time.");
}

if (endpoint == null)
{
throw new ArgumentNullException(nameof(endpoint));
}

ClientOptions.AddPolicy(new CdnApiVersionPolicy(), HttpPipelinePosition.PerRetry);

return Connect(new List<Uri>() { endpoint }, new EmptyTokenCredential());
}

/// <summary>
/// Connect the provider to Azure App Configuration using endpoint and token credentials.
/// </summary>
Expand Down Expand Up @@ -345,6 +371,11 @@ public AzureAppConfigurationOptions Connect(Uri endpoint, TokenCredential creden
/// <param name="credential">Token credential to use to connect.</param>
public AzureAppConfigurationOptions Connect(IEnumerable<Uri> endpoints, TokenCredential credential)
{
if (Credential is EmptyTokenCredential)
{
throw new InvalidOperationException("Cannot connect to both Azure App Configuration and CDN at the same time.");
}

if (endpoints == null || !endpoints.Any())
{
throw new ArgumentNullException(nameof(endpoints));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ await ExecuteWithFailOverPolicyAsync(clients, async (client) =>
if (_watchedSettings.TryGetValue(watchedKeyLabel, out ConfigurationSetting watchedKv))
{
await TracingUtils.CallWithRequestTracing(_requestTracingEnabled, RequestType.Watch, _requestTracingOptions,
async () => change = await client.GetKeyValueChange(watchedKv, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false);
async () => change = await client.GetKeyValueChange(watchedKv, cancellationToken, !(_options.Credential is EmptyTokenCredential)).ConfigureAwait(false)).ConfigureAwait(false);
}
else
{
Expand Down Expand Up @@ -968,7 +968,8 @@ private void SetRequestTracingOptions()
IsKeyVaultRefreshConfigured = _options.IsKeyVaultRefreshConfigured,
ReplicaCount = _options.Endpoints?.Count() - 1 ?? _options.ConnectionStrings?.Count() - 1 ?? 0,
FeatureFlagTracing = _options.FeatureFlagTracing,
IsLoadBalancingEnabled = _options.LoadBalancingEnabled
IsLoadBalancingEnabled = _options.LoadBalancingEnabled,
IsCdnUsed = _options.Credential is EmptyTokenCredential
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,14 @@ public IConfigurationProvider Build(IConfigurationBuilder builder)
throw new ArgumentException($"Please call {nameof(AzureAppConfigurationOptions)}.{nameof(AzureAppConfigurationOptions.Connect)} to specify how to connect to Azure App Configuration.");
}

provider = new AzureAppConfigurationProvider(new ConfigurationClientManager(clientFactory, endpoints, options.ReplicaDiscoveryEnabled, options.LoadBalancingEnabled), options, _optional);
if (options.Credential is EmptyTokenCredential)
{
provider = new AzureAppConfigurationProvider(new CdnConfigurationClientManager(clientFactory, endpoints), options, _optional);
}
else
{
provider = new AzureAppConfigurationProvider(new ConfigurationClientManager(clientFactory, endpoints, options.ReplicaDiscoveryEnabled, options.LoadBalancingEnabled), options, _optional);
}
}
catch (InvalidOperationException ex) // InvalidOperationException is thrown when any problems are found while configuring AzureAppConfigurationOptions or when SDK fails to create a configurationClient.
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using Azure.Core;
using Azure.Core.Pipeline;
using System;
using System.Collections.Specialized;
using System.Threading.Tasks;
using System.Web;

namespace Microsoft.Extensions.Configuration.AzureAppConfiguration
{
/// <summary>
/// A policy that adds the API version query parameter to HTTP requests.
/// </summary>
public class CdnApiVersionPolicy : HttpPipelinePolicy
{
/// <summary>
/// Processes the HTTP message by adding the API version query parameter.
/// </summary>
/// <param name="message">The HTTP message to process.</param>
/// <param name="pipeline">The pipeline of HTTP policies to apply.</param>
public override void Process(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
{
message.Request.Uri.Reset(AlterApiVersion(message.Request.Uri.ToUri()));

ProcessNext(message, pipeline);
}

/// <summary>
/// Processes the HTTP message asynchronously by adding the API version query parameter.
/// </summary>
/// <param name="message">The HTTP message to process.</param>
/// <param name="pipeline">The pipeline of HTTP policies to apply.</param>
/// <returns>A ValueTask representing the asynchronous operation.</returns>
public override async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
{
message.Request.Uri.Reset(AlterApiVersion(message.Request.Uri.ToUri()));

await ProcessNextAsync(message, pipeline).ConfigureAwait(false);
}

private static Uri AlterApiVersion(Uri uri)
{
var uriBuilder = new UriBuilder(uri);

NameValueCollection query = HttpUtility.ParseQueryString(uriBuilder.Query);
query["api-version"] = "2024-09-01-preview";

uriBuilder.Query = query.ToString();

return uriBuilder.Uri;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using Azure.Data.AppConfiguration;
using Microsoft.Extensions.Azure;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Extensions.Configuration.AzureAppConfiguration
{
internal class CdnConfigurationClientManager : IConfigurationClientManager
{
private readonly IList<ConfigurationClientWrapper> _clients;

public CdnConfigurationClientManager(
IAzureClientFactory<ConfigurationClient> clientFactory,
IEnumerable<Uri> endpoints)
{
if (clientFactory == null)
{
throw new ArgumentNullException(nameof(clientFactory));
}

_clients = endpoints
.Select(endpoint => new ConfigurationClientWrapper(endpoint, clientFactory.CreateClient(endpoint.AbsoluteUri)))
.ToList();
}

public IEnumerable<ConfigurationClient> GetClients()
{
return _clients.Select(c => c.Client);
}

public void RefreshClients()
{
return;
}

public bool UpdateSyncToken(Uri endpoint, string syncToken)
{
if (endpoint == null)
{
throw new ArgumentNullException(nameof(endpoint));
}

if (string.IsNullOrWhiteSpace(syncToken))
{
throw new ArgumentNullException(nameof(syncToken));
}

ConfigurationClientWrapper clientWrapper = _clients.SingleOrDefault(c => new EndpointComparer().Equals(c.Endpoint, endpoint));

if (clientWrapper != null)
{
clientWrapper.Client.UpdateSyncToken(syncToken);

return true;
}

return false;
}

public Uri GetEndpointForClient(ConfigurationClient client)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}

ConfigurationClientWrapper currentClient = _clients.FirstOrDefault(c => c.Client == client);

return currentClient?.Endpoint;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ internal class RequestTracingConstants
public const string LoadBalancingEnabledTag = "LB";
public const string SignalRUsedTag = "SignalR";
public const string FailoverRequestTag = "Failover";
public const string CdnUsedTag = "CDN";

public const string FeatureFlagFilterTypeKey = "Filter";
public const string CustomFilter = "CSTM";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Azure.Core;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Extensions.Configuration.AzureAppConfiguration
{
/// <summary>
/// A token credential that provides an empty token.
/// </summary>
internal class EmptyTokenCredential : TokenCredential
{
/// <summary>
/// Gets an empty token.
/// </summary>
/// <param name="requestContext">The context of the token request.</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
/// <returns>An empty access token.</returns>
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
return new AccessToken(string.Empty, DateTimeOffset.MaxValue);
}

/// <summary>
/// Asynchronously gets an empty token.
/// </summary>
/// <param name="requestContext">The context of the token request.</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains an empty access token.</returns>
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
return new ValueTask<AccessToken>(new AccessToken(string.Empty, DateTimeOffset.MaxValue));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions
{
internal static class ConfigurationClientExtensions
{
public static async Task<KeyValueChange> GetKeyValueChange(this ConfigurationClient client, ConfigurationSetting setting, CancellationToken cancellationToken)
public static async Task<KeyValueChange> GetKeyValueChange(this ConfigurationClient client, ConfigurationSetting setting, CancellationToken cancellationToken, bool makeConditionalRequest = true)
{
if (setting == null)
{
Expand All @@ -30,7 +30,7 @@ public static async Task<KeyValueChange> GetKeyValueChange(this ConfigurationCli

try
{
Response<ConfigurationSetting> response = await client.GetConfigurationSettingAsync(setting, onlyIfChanged: true, cancellationToken).ConfigureAwait(false);
Response<ConfigurationSetting> response = await client.GetConfigurationSettingAsync(setting, onlyIfChanged: makeConditionalRequest, cancellationToken).ConfigureAwait(false);
if (response.GetRawResponse().Status == (int)HttpStatusCode.OK &&
!response.Value.ETag.Equals(setting.ETag))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ internal class RequestTracingOptions
/// </summary>
public bool IsFailoverRequest { get; set; } = false;

/// <summary>
/// Flag to indicate wether the request is sent to a CDN.
/// </summary>
public bool IsCdnUsed { get; set; } = false;

/// <summary>
/// Checks whether any tracing feature is used.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ private static string CreateCorrelationContextHeader(RequestType requestType, Re
correlationContextTags.Add(RequestTracingConstants.FailoverRequestTag);
}

if (requestTracingOptions.IsCdnUsed)
{
correlationContextTags.Add(RequestTracingConstants.CdnUsedTag);
}

var sb = new StringBuilder();

foreach (KeyValuePair<string, string> kvp in correlationContextKeyValues)
Expand Down
Loading