diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs index 8ca456a1..cba9dc1b 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs @@ -146,7 +146,7 @@ public AzureAppConfigurationProvider(IConfigurationClientManager configClientMan string requestTracingDisabled = null; try { - requestTracingDisabled = Environment.GetEnvironmentVariable(RequestTracingConstants.RequestTracingDisabledEnvironmentVariable); + requestTracingDisabled = Environment.GetEnvironmentVariable(EnvironmentVariables.DisableRequestTracing); } catch (SecurityException) { } diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Constants/RequestTracingConstants.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Constants/RequestTracingConstants.cs index 612e1bcc..979ea9ad 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Constants/RequestTracingConstants.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Constants/RequestTracingConstants.cs @@ -5,7 +5,6 @@ namespace Microsoft.Extensions.Configuration.AzureAppConfiguration { internal class RequestTracingConstants { - public const string RequestTracingDisabledEnvironmentVariable = "AZURE_APP_CONFIGURATION_TRACING_DISABLED"; public const string AzureFunctionEnvironmentVariable = "FUNCTIONS_EXTENSION_VERSION"; public const string AzureWebAppEnvironmentVariable = "WEBSITE_SITE_NAME"; public const string ContainerAppEnvironmentVariable = "CONTAINER_APP_NAME"; diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/EnvironmentVariables.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/EnvironmentVariables.cs new file mode 100644 index 00000000..2a223280 --- /dev/null +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/EnvironmentVariables.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +namespace Microsoft.Extensions.Configuration.AzureAppConfiguration +{ + /// + /// Environment variables used to configure Azure App Configuration provider behavior. + /// + internal static class EnvironmentVariables + { + /// + /// Environment variable to disable Feature Management schema compatibility. + /// The value of this variable is a boolean string, e.g. "true" or "false". + /// 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"; + + /// + /// 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"; + } +} diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 13408385..1b1adfb9 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Net.Mime; using System.Security.Cryptography; +using System.Security; using System.Text; using System.Text.Json; using System.Threading; @@ -20,10 +21,21 @@ internal class FeatureManagementKeyValueAdapter : IKeyValueAdapter { private FeatureFlagTracing _featureFlagTracing; private int _featureFlagIndex = 0; + private bool _fmSchemaCompatibilityDisabled = false; 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; } public Task>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken) @@ -33,7 +45,10 @@ public Task>> ProcessKeyValue(Configura var keyValues = new List>(); // Check if we need to process the feature flag using the microsoft schema - if ((featureFlag.Variants != null && featureFlag.Variants.Any()) || featureFlag.Allocation != null || featureFlag.Telemetry != null) + if (_fmSchemaCompatibilityDisabled || + (featureFlag.Variants != null && featureFlag.Variants.Any()) || + featureFlag.Allocation != null || + featureFlag.Telemetry != null) { keyValues = ProcessMicrosoftSchemaFeatureFlag(featureFlag, setting, endpoint); } diff --git a/tests/Tests.AzureAppConfiguration/Unit/FeatureManagementTests.cs b/tests/Tests.AzureAppConfiguration/Unit/FeatureManagementTests.cs index 929bcbc5..21d4e92e 100644 --- a/tests/Tests.AzureAppConfiguration/Unit/FeatureManagementTests.cs +++ b/tests/Tests.AzureAppConfiguration/Unit/FeatureManagementTests.cs @@ -2291,6 +2291,75 @@ public void ThrowsOnIncorrectJsonTypes() } } + [Fact] + public void EnvironmentVariableForcesMicrosoftSchemaForAllFlags() + { + var mixedSchemaFlags = new List + { + _kv, + _variantFeatureFlagCollection[0] + }; + + var mockResponse = new Mock(); + var mockClient = new Mock(MockBehavior.Strict); + + mockClient.Setup(c => c.GetConfigurationSettingsAsync(It.IsAny(), It.IsAny())) + .Returns(new MockAsyncPageable(mixedSchemaFlags)); + + try + { + // Act - Set environment variable to force Microsoft schema + Environment.SetEnvironmentVariable(EnvironmentVariables.DisableFmSchemaCompatibility, "true"); + + var config = new ConfigurationBuilder() + .AddAzureAppConfiguration(options => + { + options.ClientManager = TestHelpers.CreateMockedConfigurationClientManager(mockClient.Object); + options.UseFeatureFlags(); + }) + .Build(); + + // Assert - Both flags should be in Microsoft schema format + // First flag (would be in .NET schema without environment variable) should now be in Microsoft schema + Assert.Equal("Beta", config["feature_management:feature_flags:0:id"]); + Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); + Assert.Equal("Browser", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); + Assert.Equal("Firefox", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:0"]); + + // Second flag (always Microsoft schema) should still be in Microsoft schema + Assert.Equal("VariantsFeature1", config["feature_management:feature_flags:1:id"]); + Assert.Equal("True", config["feature_management:feature_flags:1:enabled"]); + Assert.Equal("Big", config["feature_management:feature_flags:1:variants:0:name"]); + Assert.Equal("600px", config["feature_management:feature_flags:1:variants:0:configuration_value"]); + + // Verify .NET schema paths are NOT present + Assert.Null(config["FeatureManagement:myFeature:EnabledFor:0:Name"]); + Assert.Null(config["FeatureManagement:VariantsFeature1:EnabledFor:0:Name"]); + } + finally + { + // Cleanup - Reset environment variable + Environment.SetEnvironmentVariable("AZURE_APP_CONFIGURATION_FM_SCHEMA_COMPATIBILITY_DISABLED", null); + } + + // Act - Verify normal behavior when environment variable is not set + var configWithoutEnvVar = new ConfigurationBuilder() + .AddAzureAppConfiguration(options => + { + options.ClientManager = TestHelpers.CreateMockedConfigurationClientManager(mockClient.Object); + options.UseFeatureFlags(); + }) + .Build(); + + // First flag (no variants) should be in .NET schema + Assert.Equal("Browser", configWithoutEnvVar["FeatureManagement:Beta:EnabledFor:0:Name"]); + Assert.Equal("Firefox", configWithoutEnvVar["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:0"]); + + // Second flag (has variants) should be in Microsoft schema + Assert.Equal("VariantsFeature1", configWithoutEnvVar["feature_management:feature_flags:0:id"]); + Assert.Equal("Big", configWithoutEnvVar["feature_management:feature_flags:0:variants:0:name"]); + } + Response GetIfChanged(ConfigurationSetting setting, bool onlyIfChanged, CancellationToken cancellationToken) { return Response.FromValue(FirstKeyValue, new MockResponse(200)); diff --git a/tests/Tests.AzureAppConfiguration/Unit/Tests.cs b/tests/Tests.AzureAppConfiguration/Unit/Tests.cs index 1f2ab5fa..7e68f1cf 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(RequestTracingConstants.RequestTracingDisabledEnvironmentVariable, "True"); + Environment.SetEnvironmentVariable(EnvironmentVariables.DisableRequestTracing, "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(RequestTracingConstants.RequestTracingDisabledEnvironmentVariable, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.DisableRequestTracing, null); Environment.SetEnvironmentVariable(RequestTracingConstants.AzureFunctionEnvironmentVariable, "v1.0"); var clientManager1 = TestHelpers.CreateMockedConfigurationClientManager(options);