diff --git a/src/Microsoft.Azure.AppConfiguration.AspNetCore/Microsoft.Azure.AppConfiguration.AspNetCore.csproj b/src/Microsoft.Azure.AppConfiguration.AspNetCore/Microsoft.Azure.AppConfiguration.AspNetCore.csproj
index 2afe5669..72e54087 100644
--- a/src/Microsoft.Azure.AppConfiguration.AspNetCore/Microsoft.Azure.AppConfiguration.AspNetCore.csproj
+++ b/src/Microsoft.Azure.AppConfiguration.AspNetCore/Microsoft.Azure.AppConfiguration.AspNetCore.csproj
@@ -21,7 +21,7 @@
- 8.4.0
+ 8.5.0
diff --git a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/Microsoft.Azure.AppConfiguration.Functions.Worker.csproj b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/Microsoft.Azure.AppConfiguration.Functions.Worker.csproj
index beb997c4..2531bbe3 100644
--- a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/Microsoft.Azure.AppConfiguration.Functions.Worker.csproj
+++ b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/Microsoft.Azure.AppConfiguration.Functions.Worker.csproj
@@ -24,7 +24,7 @@
- 8.4.0
+ 8.5.0
diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AlwaysHealthyHealthCheck.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AlwaysHealthyHealthCheck.cs
new file mode 100644
index 00000000..6e383542
--- /dev/null
+++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AlwaysHealthyHealthCheck.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+//
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using System.Threading.Tasks;
+using System.Threading;
+
+namespace Microsoft.Extensions.Configuration.AzureAppConfiguration
+{
+ internal sealed class AlwaysHealthyHealthCheck : IHealthCheck
+ {
+ private static readonly Task _healthyResult = Task.FromResult(HealthCheckResult.Healthy());
+
+ public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
+ {
+ return _healthyResult;
+ }
+ }
+}
diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationExtensions.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationExtensions.cs
index 8d141969..a5657c2b 100644
--- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationExtensions.cs
+++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationExtensions.cs
@@ -7,7 +7,6 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;
using System.Collections.Generic;
-using System.Security;
namespace Microsoft.Extensions.Configuration
{
@@ -16,19 +15,7 @@ namespace Microsoft.Extensions.Configuration
///
public static class AzureAppConfigurationExtensions
{
- private const string DisableProviderEnvironmentVariable = "AZURE_APP_CONFIGURATION_PROVIDER_DISABLED";
- private static readonly bool _isProviderDisabled = IsProviderDisabled();
-
- private static bool IsProviderDisabled()
- {
- try
- {
- return bool.TryParse(Environment.GetEnvironmentVariable(DisableProviderEnvironmentVariable), out bool disabled) ? disabled : false;
- }
- catch (SecurityException) { }
-
- return false;
- }
+ private static readonly bool _isProviderDisabled = EnvironmentVariableHelper.GetBoolOrDefault(EnvironmentVariableNames.AppConfigurationProviderDisabled);
///
/// Adds key-value data from an Azure App Configuration store to a configuration builder using its connection string.
diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationHealthCheck.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationHealthCheck.cs
index 76b706c0..4877a6ca 100644
--- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationHealthCheck.cs
+++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationHealthCheck.cs
@@ -11,7 +11,7 @@
namespace Microsoft.Extensions.Configuration.AzureAppConfiguration
{
- internal class AzureAppConfigurationHealthCheck : IHealthCheck
+ internal sealed class AzureAppConfigurationHealthCheck : IHealthCheck
{
private static readonly PropertyInfo _propertyInfo = typeof(ChainedConfigurationProvider).GetProperty("Configuration", BindingFlags.Public | BindingFlags.Instance);
private readonly IEnumerable _healthChecks;
@@ -37,17 +37,27 @@ public async Task CheckHealthAsync(HealthCheckContext context
return HealthCheckResult.Unhealthy(HealthCheckConstants.NoProviderFoundMessage);
}
+ HealthCheckResult worstResult = HealthCheckResult.Healthy();
+
foreach (IHealthCheck healthCheck in _healthChecks)
{
var result = await healthCheck.CheckHealthAsync(context, cancellationToken).ConfigureAwait(false);
+ // Keep track of the worst health status found
+ // HealthStatus enum is ordered: Unhealthy(0) < Degraded(1) < Healthy(2)
+ if (result.Status < worstResult.Status)
+ {
+ worstResult = result;
+ }
+
+ // If an Unhealthy status is found, short-circuit since that's the worst possible
if (result.Status == HealthStatus.Unhealthy)
{
return result;
}
}
- return HealthCheckResult.Healthy();
+ return worstResult;
}
private void FindHealthChecks(IConfigurationRoot configurationRoot, List healthChecks)
diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationHealthChecksBuilderExtensions.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationHealthChecksBuilderExtensions.cs
index f006b746..25de3926 100644
--- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationHealthChecksBuilderExtensions.cs
+++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationHealthChecksBuilderExtensions.cs
@@ -14,6 +14,8 @@ namespace Microsoft.Extensions.DependencyInjection
///
public static class AzureAppConfigurationHealthChecksBuilderExtensions
{
+ private static readonly bool _isProviderDisabled = EnvironmentVariableHelper.GetBoolOrDefault(EnvironmentVariableNames.AppConfigurationProviderDisabled);
+
///
/// Add a health check for Azure App Configuration to given .
///
@@ -32,10 +34,20 @@ public static IHealthChecksBuilder AddAzureAppConfiguration(
IEnumerable tags = default,
TimeSpan? timeout = default)
{
+ IHealthCheck CreateHealthCheck(IServiceProvider sp)
+ {
+ if (_isProviderDisabled)
+ {
+ return new AlwaysHealthyHealthCheck();
+ }
+
+ return new AzureAppConfigurationHealthCheck(
+ factory?.Invoke(sp) ?? sp.GetRequiredService());
+ }
+
return builder.Add(new HealthCheckRegistration(
name ?? HealthCheckConstants.HealthCheckRegistrationName,
- sp => new AzureAppConfigurationHealthCheck(
- factory?.Invoke(sp) ?? sp.GetRequiredService()),
+ CreateHealthCheck,
failureStatus,
tags,
timeout));
diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs
index f34eed39..fc92d129 100644
--- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs
+++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs
@@ -16,7 +16,6 @@
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
-using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -143,15 +142,7 @@ public AzureAppConfigurationProvider(IConfigurationClientManager configClientMan
MinRefreshInterval = RefreshConstants.DefaultRefreshInterval;
}
- // Enable request tracing if not opt-out
- string requestTracingDisabled = null;
- try
- {
- requestTracingDisabled = Environment.GetEnvironmentVariable(EnvironmentVariables.DisableRequestTracing);
- }
- catch (SecurityException) { }
-
- _requestTracingEnabled = bool.TryParse(requestTracingDisabled, out bool tracingDisabled) ? !tracingDisabled : true;
+ _requestTracingEnabled = !EnvironmentVariableHelper.GetBoolOrDefault(EnvironmentVariableNames.RequestTracingDisabled);
if (_requestTracingEnabled)
{
@@ -583,15 +574,17 @@ public void ProcessPushNotification(PushNotification pushNotification, TimeSpan?
public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
+ HealthStatus failureStatus = context.Registration?.FailureStatus ?? HealthStatus.Unhealthy;
+
if (!_lastSuccessfulAttempt.HasValue)
{
- return HealthCheckResult.Unhealthy(HealthCheckConstants.LoadNotCompletedMessage);
+ return new HealthCheckResult(status: failureStatus, description: HealthCheckConstants.LoadNotCompletedMessage);
}
if (_lastFailedAttempt.HasValue &&
_lastSuccessfulAttempt.Value < _lastFailedAttempt.Value)
{
- return HealthCheckResult.Unhealthy(HealthCheckConstants.RefreshFailedMessage);
+ return new HealthCheckResult(status: failureStatus, description: HealthCheckConstants.RefreshFailedMessage);
}
return HealthCheckResult.Healthy();
diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationRefresherProvider.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationRefresherProvider.cs
index d2b31071..004a9113 100644
--- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationRefresherProvider.cs
+++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationRefresherProvider.cs
@@ -63,10 +63,14 @@ private void FindRefreshers(IConfigurationRoot configurationRoot, ILoggerFactory
{
foreach (IConfigurationProvider provider in configurationRoot.Providers)
{
- if (provider is AzureAppConfigurationProvider appConfigurationProvider)
+ if (provider is IConfigurationRefresher configurationRefresher)
{
- appConfigurationProvider.LoggerFactory = loggerFactory;
- refreshers.Add(appConfigurationProvider);
+ refreshers.Add(configurationRefresher);
+
+ if (configurationRefresher is AzureAppConfigurationProvider azureAppConfigurationProvider)
+ {
+ azureAppConfigurationProvider.LoggerFactory = loggerFactory;
+ }
}
else if (provider is ChainedConfigurationProvider chainedProvider)
{
diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/EnvironmentVariableHelper.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/EnvironmentVariableHelper.cs
new file mode 100644
index 00000000..242b405e
--- /dev/null
+++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/EnvironmentVariableHelper.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+//
+
+using System;
+using System.Security;
+
+namespace Microsoft.Extensions.Configuration.AzureAppConfiguration
+{
+ internal static class EnvironmentVariableHelper
+ {
+ public static bool GetBoolOrDefault(string variableName)
+ {
+ try
+ {
+ return bool.TryParse(Environment.GetEnvironmentVariable(variableName), out bool disabled) ? disabled : false;
+ }
+ catch (SecurityException) { }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/EnvironmentVariables.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/EnvironmentVariableNames.cs
similarity index 60%
rename from src/Microsoft.Extensions.Configuration.AzureAppConfiguration/EnvironmentVariables.cs
rename to src/Microsoft.Extensions.Configuration.AzureAppConfiguration/EnvironmentVariableNames.cs
index 2a223280..16c2bf00 100644
--- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/EnvironmentVariables.cs
+++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/EnvironmentVariableNames.cs
@@ -6,7 +6,7 @@ namespace Microsoft.Extensions.Configuration.AzureAppConfiguration
///
/// Environment variables used to configure Azure App Configuration provider behavior.
///
- internal static class EnvironmentVariables
+ internal static class EnvironmentVariableNames
{
///
/// Environment variable to disable Feature Management schema compatibility.
@@ -14,12 +14,18 @@ internal static class EnvironmentVariables
/// When set to "true", schema compatibility checks for feature flags are disabled,
/// and all feature flags will be interpreted using the Microsoft Feature Flags schema.
///
- public const string DisableFmSchemaCompatibility = "AZURE_APP_CONFIGURATION_FM_SCHEMA_COMPATIBILITY_DISABLED";
+ public const string FmSchemacompatibilityDisabled = "AZURE_APP_CONFIGURATION_FM_SCHEMA_COMPATIBILITY_DISABLED";
///
/// Environment variable to disable request tracing.
/// The value of this variable is a boolean string, e.g. "true" or "false".
///
- public const string DisableRequestTracing = "AZURE_APP_CONFIGURATION_TRACING_DISABLED";
+ public const string RequestTracingDisabled = "AZURE_APP_CONFIGURATION_TRACING_DISABLED";
+
+ ///
+ /// Environment variable to disable Azure App Configuration provider.
+ /// The value of this variable is a boolean string, e.g. "true" or "false".
+ ///
+ public const string AppConfigurationProviderDisabled = "AZURE_APP_CONFIGURATION_PROVIDER_DISABLED";
}
}
diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs
index 1b1adfb9..fdd7f2fd 100644
--- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs
+++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs
@@ -27,15 +27,7 @@ public FeatureManagementKeyValueAdapter(FeatureFlagTracing featureFlagTracing)
{
_featureFlagTracing = featureFlagTracing ?? throw new ArgumentNullException(nameof(featureFlagTracing));
- string fmSchemaCompatibilityDisabled = null;
-
- try
- {
- fmSchemaCompatibilityDisabled = Environment.GetEnvironmentVariable(EnvironmentVariables.DisableFmSchemaCompatibility);
- }
- catch (SecurityException) { }
-
- _fmSchemaCompatibilityDisabled = bool.TryParse(fmSchemaCompatibilityDisabled, out bool disabled) ? disabled : false;
+ _fmSchemaCompatibilityDisabled = EnvironmentVariableHelper.GetBoolOrDefault(EnvironmentVariableNames.FmSchemacompatibilityDisabled);
}
public Task>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken)
diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Microsoft.Extensions.Configuration.AzureAppConfiguration.csproj b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Microsoft.Extensions.Configuration.AzureAppConfiguration.csproj
index ed0ec6e5..8c8241a7 100644
--- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Microsoft.Extensions.Configuration.AzureAppConfiguration.csproj
+++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Microsoft.Extensions.Configuration.AzureAppConfiguration.csproj
@@ -15,7 +15,7 @@
-
+
@@ -38,7 +38,7 @@
- 8.4.0
+ 8.5.0
diff --git a/tests/Tests.AzureAppConfiguration/Unit/FeatureManagementTests.cs b/tests/Tests.AzureAppConfiguration/Unit/FeatureManagementTests.cs
index 21d4e92e..439c63bb 100644
--- a/tests/Tests.AzureAppConfiguration/Unit/FeatureManagementTests.cs
+++ b/tests/Tests.AzureAppConfiguration/Unit/FeatureManagementTests.cs
@@ -1072,7 +1072,7 @@ public void PreservesDefaultQuery()
options.UseFeatureFlags();
}).Build();
- bool performedDefaultQuery = mockTransport.Requests.Any(r => r.Uri.PathAndQuery.Contains("/kv?key=%2A&label=%00"));
+ bool performedDefaultQuery = mockTransport.Requests.Any(r => r.Uri.PathAndQuery.Contains("/kv?api-version=2023-11-01&key=%2A&label=%00"));
bool queriedFeatureFlags = mockTransport.Requests.Any(r => r.Uri.PathAndQuery.Contains(Uri.EscapeDataString(FeatureManagementConstants.FeatureFlagMarker)));
Assert.True(performedDefaultQuery);
@@ -1100,7 +1100,7 @@ public void QueriesFeatureFlags()
})
.Build();
- bool performedDefaultQuery = mockTransport.Requests.Any(r => r.Uri.PathAndQuery.Contains("/kv?key=%2A&label=%00"));
+ bool performedDefaultQuery = mockTransport.Requests.Any(r => r.Uri.PathAndQuery.Contains("/kv?api-version=2023-11-01&key=%2A&label=%00"));
bool queriedFeatureFlags = mockTransport.Requests.Any(r => r.Uri.PathAndQuery.Contains(Uri.EscapeDataString(FeatureManagementConstants.FeatureFlagMarker)));
Assert.True(performedDefaultQuery);
@@ -2309,7 +2309,7 @@ public void EnvironmentVariableForcesMicrosoftSchemaForAllFlags()
try
{
// Act - Set environment variable to force Microsoft schema
- Environment.SetEnvironmentVariable(EnvironmentVariables.DisableFmSchemaCompatibility, "true");
+ Environment.SetEnvironmentVariable(EnvironmentVariableNames.FmSchemacompatibilityDisabled, "true");
var config = new ConfigurationBuilder()
.AddAzureAppConfiguration(options =>
diff --git a/tests/Tests.AzureAppConfiguration/Unit/HealthCheckTest.cs b/tests/Tests.AzureAppConfiguration/Unit/HealthCheckTest.cs
index 9dce8e82..4484def0 100644
--- a/tests/Tests.AzureAppConfiguration/Unit/HealthCheckTest.cs
+++ b/tests/Tests.AzureAppConfiguration/Unit/HealthCheckTest.cs
@@ -136,5 +136,54 @@ public async Task HealthCheckTests_RegisterAzureAppConfigurationHealthCheck()
Assert.Contains(HealthCheckConstants.HealthCheckRegistrationName, result.Entries.Keys);
Assert.Equal(HealthStatus.Healthy, result.Entries[HealthCheckConstants.HealthCheckRegistrationName].Status);
}
+
+ [Fact]
+ public async Task HealthCheckTests_ShouldRespectHealthCheckRegistration()
+ {
+ IConfigurationRefresher refresher = null;
+ var mockResponse = new Mock();
+ var mockClient = new Mock(MockBehavior.Strict);
+
+ mockClient.SetupSequence(c => c.GetConfigurationSettingsAsync(It.IsAny(), It.IsAny()))
+ .Returns(new MockAsyncPageable(kvCollection))
+ .Throws(new RequestFailedException(503, "Request failed."));
+
+ var config = new ConfigurationBuilder()
+ .AddAzureAppConfiguration(options =>
+ {
+ options.ClientManager = TestHelpers.CreateMockedConfigurationClientManager(mockClient.Object);
+ options.MinBackoffDuration = TimeSpan.FromSeconds(2);
+ options.ConfigurationSettingPageIterator = new MockConfigurationSettingPageIterator();
+ options.ConfigureRefresh(refreshOptions =>
+ {
+ refreshOptions.RegisterAll()
+ .SetRefreshInterval(TimeSpan.FromSeconds(1));
+ });
+ refresher = options.GetRefresher();
+ })
+ .Build();
+
+ var services = new ServiceCollection();
+ services.AddSingleton(config);
+ services.AddLogging(); // add logging for health check service
+ services.AddHealthChecks()
+ .AddAzureAppConfiguration(
+ name: "TestName",
+ failureStatus: HealthStatus.Degraded);
+ var provider = services.BuildServiceProvider();
+ var healthCheckService = provider.GetRequiredService();
+
+ var result = await healthCheckService.CheckHealthAsync();
+ Assert.Equal(HealthStatus.Healthy, result.Status);
+
+ // Wait for the refresh interval to expire
+ Thread.Sleep(2000);
+
+ await refresher.TryRefreshAsync();
+ result = await healthCheckService.CheckHealthAsync();
+ Assert.Equal(HealthStatus.Degraded, result.Status);
+ Assert.Contains("TestName", result.Entries.Keys);
+ Assert.Equal(HealthStatus.Degraded, result.Entries["TestName"].Status);
+ }
}
}
diff --git a/tests/Tests.AzureAppConfiguration/Unit/Tests.cs b/tests/Tests.AzureAppConfiguration/Unit/Tests.cs
index 37fb970f..eb08bba2 100644
--- a/tests/Tests.AzureAppConfiguration/Unit/Tests.cs
+++ b/tests/Tests.AzureAppConfiguration/Unit/Tests.cs
@@ -273,7 +273,7 @@ public void TestTurnOffRequestTracing()
var options = new AzureAppConfigurationOptions();
options.ClientOptions.Transport = mockTransport;
- Environment.SetEnvironmentVariable(EnvironmentVariables.DisableRequestTracing, "True");
+ Environment.SetEnvironmentVariable(EnvironmentVariableNames.RequestTracingDisabled, "True");
var clientManager = TestHelpers.CreateMockedConfigurationClientManager(options);
var config = new ConfigurationBuilder()
@@ -296,7 +296,7 @@ public void TestTurnOffRequestTracing()
options.ClientOptions.Transport = mockTransport;
// Delete the request tracing environment variable
- Environment.SetEnvironmentVariable(EnvironmentVariables.DisableRequestTracing, null);
+ Environment.SetEnvironmentVariable(EnvironmentVariableNames.RequestTracingDisabled, null);
Environment.SetEnvironmentVariable(RequestTracingConstants.AzureFunctionEnvironmentVariable, "v1.0");
var clientManager1 = TestHelpers.CreateMockedConfigurationClientManager(options);