Giter VIP home page Giter VIP logo

appconfiguration-dotnetprovider's Introduction

Azure App Configuration - .NET Provider

The Azure App Configuration provider for .NET enables developers to configure their applications using centralized configuration located in Azure App Configuration. The API design follows the patterns outlined by the .NET configuration system to make switching to Azure App Configuration a familiar and easy experience.

Getting Started

Official documentation on how to use the Azure App Configuration provider is available in the following quickstarts:

Examples

Data Collection

The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry by setting the environment variable AZURE_APP_CONFIGURATION_TRACING_DISABLED to TRUE. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at https://go.microsoft.com/fwlink/?LinkID=824704. You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices.

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.

When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

appconfiguration-dotnetprovider's People

Contributors

abhilasharora avatar amerjusupovic avatar annelo-msft avatar avanigupta avatar dependabot[bot] avatar drago-draganov avatar eskibear avatar gsgvijay avatar jimmyca15 avatar mbsolomon avatar microsoft-github-policy-service[bot] avatar microsoftopensource avatar msftgits avatar msgarywang avatar pratiksanglikar avatar sajayantony avatar shenmuxiaosen avatar yiming-jia avatar zhenlan avatar zhiyuanliang-ms avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

appconfiguration-dotnetprovider's Issues

Fix broken build for configuration provider

The container image we use for building configuration provider does not have the appropriate SDK available to compile C# 8.0 features, so the current build is broken. This issue tracks fixing it so the current code could compile.

Allow refresh registration for for Complex objects

Given the current configuration:

{
  "config" : {
    "Key1" : "Value1",
    "Key2" : "Value2",
    "Key3" : "Value3",
    "Key4" : "Value4",
  }
}

doing the following will fail with a 404

    config.AddAzureAppConfiguration(options => options.Connect(
            new Uri(builtConfig[_appConfigKey]),
            credentials)
         .ConfigureRefresh(refresh =>
        {
             refresh.Register("config", refreshAll: true)
                .SetCacheExpiration(new TimeSpan(0, 5, 0));
        })
        .ConfigureKeyVault(kv => { kv.SetCredential(credentials); })
            .UseFeatureFlags()
    );

image

It appears that the code currently calls RefreshKeyValueCollections however the only object that can populate the MultiKeyWatchers IEnumerable is private and in the UseFeatureFlags() Method.

It would be nice to be able to register my own types

Observables used to watch configuration are not disposable.

The AzureAppConfigurationProvider supports the capability to watch key-value changes by polling a configuration store. An observable runs at an interval to perform this operation. It uses the same client that is used to pull the initial key-values.

The problem is that this observable runs constantly and there is no way for a user to dispose it when they are using the AzureAppConfigurationProvider. While AzureAppConfigurationProvider implements IDisposable, it is never invoked by the ASP.Net Core 2.1 framework and there is no way for someone who is using IConfigurationBuilder to obtain a reference to this provider to manually dispose it.

It looks like in ASP.Net Core 3.0 this problem will be addressed as the ConfigurationRoot implementation has been updated to dispose any ConfigurationProviders that implement IDisposable.

Expectation:
When I use AzureAppConfigurationProvider within my application and then the app begins to shutdown, the observable's polling Azure App Configuration stop, or I am able to stop them manually.

Reality:
The observables within AzureAppConfigurationProvider cannot be disposed.

Docker support

Im having the following issue when I debug from docker

image

but the same connection string works without debugging docker

Empty label filter should be changed to null label filter.

There is a difference between the null label and an empty string label. Sending a query filter with the null terminating character \0 will result in a query for the null label. We should change the LabelFilter Property for the null label from Empty to Null as it is in the SDK.

Add a way to reload all config when a monitored KV is changed

When a user changes multiple KVs in a config store, it's normally not a good idea to reload each individual KVs in the application as soon as each change happens because the application may not work when configs are only partially updated.

Our recommendation is for the user to create a single KV that the application will monitor. The user will update the monitoring KV only after the update of all other KVs is completed. The config provider should reload all KVs (not just the monitoring KV) when the monitoring KV is changed.

Propose to add a new function

 public AzconfigOptions WatchAndRefreshAll(string key, int pollInterval, string label = "")

Connection string parameter lacks validation

As @bradhav25 mentioned in PR #4, there is possibly room to improve on the validation performed on the connection string that is passed when connecting to a remote configuration source. The current lack of validation is not a security concern, but more of a usability concern. For example, misspelled connection string segments to not trigger an argument exception, nor do repeated segments.

Update refresh to use SetCacheExpiration time to recover from initial load failures

The RefreshAsync and TryRefreshAsync methods were updated to be able to detect a failure and re-attempt to load the initial settings from App Configuration. It is possible to leverage this feature even when there are no settings registered for refresh. In this case, the current behavior is to use a default cache expiration time of 30 seconds for auto-recovery of the initial configuration load.

In such cases, if a custom value of the cache expiration time has been specified by the user through the SetCacheExpiration method, we would like to use it as the cache expiration time for recovery of initial configuration load, instead of the default cache expiration time. For example, a cache expiration time of 10 seconds would be used for the following code snippet, instead of the default cache expiration time of 30 seconds.

IConfiguration configuration = builder.Build();

builder.AddAzureAppConfiguration(options =>
{
    options.Connect(configuration["connection_string"])
            .ConfigureRefresh(refresh =>
            {
                refresh.SetCacheExpiration(TimeSpan.FromSeconds(10));
            });

    _refresher = options.GetRefresher();
});

Watching leads to App Configuration quota being consumed very quickly.

At the moment, it is very easy for developers to shoot themselves in their foot by consuming a large amount of their per second quota by polling. If a user watches a few keys in an App Configuration and then deploys a few instances of that app then the quota could easily be reached.

We should come up with ways to handle multiple observations so that developers do not shoot themselves in the foot by watching multiple things.

An example solution:
Stagger watch intervals so that they occur in different seconds on the observation period. For example, if I want to watch 4 keys and I want the poll interval to be the default 30 seconds, then I can send a poll request to a single key every 7.5 seconds rather than 4 keys every 30 seconds in the same second.

Another piece that would need to be considered is not letting multiple observations occur in the same second.

RefreshAsync: Schedule config refresh at the end of cache expiration window

In the scenario that the RefreshAsync is called in a function that is triggered by Event Grid events sent by AppConfig, the configuration refresh could be missed (ignored) if the event arrives at the time the current cache expiration window is not yet reached. The proposal is to schedule a configuration refresh at the end of the cache expiration window in this case. Only one real configuration refresh should happen even if multiple are scheduled.

cc: @jimmyca15 @drago-draganov

Observable not reset when change is observed

Whenever a key-value is set up to be watched, the code uses a subscription on an observable. When the key-value is changed, the subscription call back is called. The problem is that currently this subscription performs some work to update the configuration and then exits. The subscription stays alive. This means after the next time interval when the observable checks for changes, it will assume there are changes again for the change that caused the previous callback to fire.

When the observable subscription is called, it should tear down the existing subscription and set up a new subscription with the new key-value so that the same change doesn't cause the callback to trigger every time the observable change detection runs.

The offending code is here:

                _subscriptions.Add(observable.Subscribe((observedKv) =>
                {
                    _settings[watchedKey] = observedKv;
                    SetData(_settings);
                }));

Add ability to perform custom transforms on App Configuration Settings.

Some applications may need to perform custom transforms on the configuration settings they retrieve from Azure App Configuration. An example might be if an application uses a custom defined content type and has to process the value before it is acceptable to be placed in .NET Core's IConfiguration. This is the kind of thing that is done for Key Vault references today.

This can be accomplished by exposing a new Map method to AzureAppConfigurationOptions.

Proposed API

/// Performs custom mapping for configuration settings retrieved from Azure App Configuration
public AppConfigurationOptions AppConfigurationOptions.Map(Func<ConfigurationSetting, ConfigurationSetting> mapper);

Usage

config.AddAzureAppConfiguration(options =>
    {
        options.Connect(settings["connection_string"])
            .Map((ConfigurationSetting setting) =>
            {
                if (setting.ContentType == "application/myContentType")
                {  
                    return new ConfigurationSetting(
                        key: setting.Key,
                        value: PerformSomeTransform(setting.Value),
                        label: setting.Label);
                }

                return setting;
        });
    });

The Value of the configuration setting is what is exposed from IConfiguration.

CC @zhenlan @drago-draganov @abhilasharora

Preferred Date Time is not preferrable for configuration retrieval.

With the initial release of the AppConfigurationProvider, preferred date time was offered to provide the capability to retrieve configuration as it existed in a point in time. This uses Azure App Configuration's revision feature. Revisions are only kept for a certain amount of days before they are deleted from an App Configuration. This means that applications that use preferred time would eventually no longer function correctly.

This problem is better solved by creating a snapshot of key-values using a certain label that marks a point in time. Then during startup the label should be specified when querying for configuration.

We should remove preferred date time from the provider.

Understand how the config provider is used

  • Add a way so we can understand the request config provider sends is to
    • load all config at app start-up
    • use feature flags
    • connect with the managed identity
  • Provide users a way to opt out

User agent contains the provider package name and version more than once

Problem
The user agent string that is logged for calls made to App Configuration using the .NET Core configuration provider could contain the package name and version multiple times. This behavior can be noticed for around 0.5% of the total requests. This makes it harder to group these requests for analysis.

Example 1
Microsoft.Extensions.Configuration.AzureAppConfiguration/2.1.0.0 Microsoft.Extensions.Configuration.AzureAppConfiguration/2.1.0.0 azsdk-net-Data.AppConfiguration/1.0.0-preview.4,(.NET Framework 4.7.3468.0; Microsoft Windows 10.0.17134 )

Example 2
Microsoft.Extensions.Configuration.AzureAppConfiguration/2.1.0.0 Microsoft.Extensions.Configuration.AzureAppConfiguration/2.1.0.0 Microsoft.Extensions.Configuration.AzureAppConfiguration/2.1.0.0 azsdk-net-Data.AppConfiguration/1.0.0-preview.4,(.NET Framework 4.7.3468.0; Microsoft Windows 10.0.17134 )

Observations

  1. Based on analysis of the logs with such unexpected values of user agent, the issue seems to be limited to calls that are made by the SDK as a part of retry mechanism when the initial call to the server fails with a retriable error.
  2. The issue is limited to version 2.1.0-preview-010380001-1099 and higher of the configuration provider, which include changes to migrate to the Azure.Data.AppConfiguration SDK.

Root Cause
A further analysis of the code revealed that we process the user agent header policy once per retry attempt instead of once per call. This causes the name and version of the provider package to be prepended to the header from the previous attempt for each successive retry attempt, leading to this issue.

TryRefreshAsync does not handle exceptions originating in Azure.Identity

The package Azure.Identity throws Azure.Identity.AuthenticationFailedException when it fails to use a TokenCredential. For example, when new DefaultAzureCredentials() is used to connect to Azure App Configuration but no valid identity could be found, then it throws AuthenticationFailedException. TryRefreshAsync method does not handle this exception and will throw for such cases, instead of returning false as the result.

Ensure character escaping is done correctly on SDK 1.0.0-preview.3

The following test cases must pass:

                            refresh.Register("Settings:BackgroundColor")
                                .Register("!@#$^&()")
                                .Register("!@#$^&()*")
                                .Register("~!@#$%^&()_+")
                                .Register("~!@#$%^&()_+*")
                                .Register("*~!@#$%^&()_+")
                                .Register("~!@#$*%^&()_+")  // Behavior should match old SDK
                                .Register("~!@#$%^&()_+\\")
                                .Register("\\~!@#$%^&()_+")
                                .Register("~!@#$\\%^&()_+")  // Behavior should match old SDK
                                .Register("~!@#$%^&()_+,")
                                .Register(",~!@#$%^&()_+")
                                .Register("~!@#$,%^&()_+")  // Behavior should match old SDK

Consider adding logging for TryRefreshAsync method

Add logging for failures during IConfigurationRefresher.TryRefreshAsync to allow users to fix errors during the refresh operation. Check the discussion in #119 for more details. Also allow users of the middleware to register an ILogger to retrieve failure logs.

Allow configurable pull refresh cache expiration separate for Keyvault references (secrets)

Configuration data and secrets often evolve with different velocities. Shorter cache expiration makes sense for feature management, sentinel keys monitoring, etc.. Unfortunately, that may put stress on Keyvault, considering the fact that each secret must be fetched one at a time. That is also amplified when multiple provider instances are active.

AzureAppConfigurationKeyVaultOptions is perhaps a good place to setup cache expiration. If not specified, default to the existing cache expiration.

Make IConfigurationRefresher instance available via DI

It's useful for user code to obtain the instance of IConfigurationRefresher so they can refresh configuration synchronously or write their own middleware etc.

Proposal

Add extension method below that user can call in ConfigureServices(...)

public static IServiceCollection AddAzureAppConfiguration(this IServiceCollection services)

Under the cover, this API will add IConfigurationRefresherAccessor to the DI.

public interface IConfigurationRefresherAccessor
{
    IEnumerable<IConfigurationRefresher> Refreshers { get; set; }
}

User code can then get the instances of IConfigurationRefresher (via IConfigurationRefresherAccessor) from the DI.

Note: the implementation of IConfigurationRefresherAccessor will need to get IConfiguration from DI and cast it to IConfigurationRoot etc, just like what we do in the middleware today.

Other Considerations

  • We may need to consider exposing other properties in IConfigurationRefresher so users can differentiate instances from one to another.
  • We may consider changing our middleware to obtain instances of IConfigurationRefresher in the same way. This means users must call AddAzureAppConfiguration before they can call UseAzureAppConfiguration.

cc: @abhilasharora @jimmyca15 @drago-draganov

Change to case-insensitive for config keys

From @jimmyca15

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-2.1&tabs=basicconfiguration#conventions

“Keys are case-insensitive. For example, ConnectionString and connectionstring are treated as equivalent keys.”

I think we should just update the .net core config provider to be case insensitive. In the event two KVs are present with the same case-insensitive key then the kv put into the configuration provider might be considered random, but that’s the way .net core behaves.

Updating this line should do the trick.

Update to preview 4 of Azure SDK

Breaking changes have been made to Azure.Core, which have ripple effects to Azure.Data.AppConfiguration. We will update this after the initial provider PR is checked in.

Throw an exception if ConfigureRefresh is called without any refresh registrations

We want to be able to prevent users from calling ConfigureRefresh with no key-values registered for refresh using the Register method. The current behavior is to allow users to retrieve an instance of the refresher without registering any key-value for refresh. However, it does not have a fully working use case and could lead to confusion in behavior when ConfigureRefresh is used more than once with different initializer.

After this change, the following code should throw an exception with an error message indicating that ConfigureRefresh cannot be used without any refresh registration.

IConfiguration configuration = builder.Build();

builder.AddAzureAppConfiguration(options =>
{
    options.Connect(configuration["connection_string"])
            .ConfigureRefresh(refresh =>
            {
                refresh.SetCacheExpiration(TimeSpan.FromSeconds(10));
            });

    _refresher = options.GetRefresher();
});

Provide default poll interval for watch function

So

  • A user doesn't have to worry about it in most cases
  • We can have a more reasonable default poll interval that should work for most applications and won't send too much unnecessary traffic to the config service. A proposed default poll interval is 30s, which should work just fine for most web applications.

Watch silently fails if the poll interval is too small.

Currently we watch for changes in a fire-and-forget background task. When we hook up the observable for key-values we use the poll interval specified by the user in AzureAppConfigurationOptions. This could possibly be less than the minimum 500 ms poll interval required by the AzconfigClient.

Options for fixing:

  1. Add 500ms poll interval validation in AzureAppConfigurationOptions.Watch
  2. Bubble up any errors in hooking up observables for watch behavior.

Update middleware to throw when no refresher instance is found

When no instance of IConfigurationRefresher could be retrieved from the IConfiguration instance received from the dependency injection container, the middleware should throw. This will help avoid silent failures in case there is an issue setting up App Configuration.

Add IConfigurationRefresher.TryRefresh API

To make it easier for customers to trigger configuration refresh without worrying about crashing the app due to various transient errors, propose to add the API below in IConfigurationRefresher.

Task<bool> TryRefresh()

Details
The TryRefresh will not throw on Transient or any FailedRequestException, regardless of the status code (even 401, 403)

The existing API Refresh will not catch anything.

More discussion
Propose to follow the convention and rename APIs to

Task RefreshAsync();
Task<bool> TryRefreshAsync();

Allow access to configuration settings

I'm using Azure App Configuration to configure multiple microservices. With this setup it's quite nice to be able to configure settings on an environment level that get shared by all microservices. I can easily scope the settings to a specific microservice by using labels. So far so good.

One of these microservices hosts an SPA frontend (Blazor WebAssembly). I'd like to be able to expose some of the settings to this frontend. To avoid exposing too much, I'm only going to expose settings which have a specific tag.

In order to achieve this, I need access to the 'raw' ConfigurationSetting objects in _settings field of AzureAppConfigurationProvider class. I don't want to query Azure App Configuration myself, because I would need to make sure the logic is 100% the same as in the configuration provider. Furthermore, I would also need to implement all the robustness features that are present in the configuration provider.

I think it would be possible to do this through the IOfflineCache and deserializing the data. But this is quite a hack and doesn't seem very future proof. Would you guys be open to a PR with something like IConfigurationAccess that would allow access to the ConfigurationSetting objects whenever they change in the configuration provider?

Fix bug that causes feature flags to be refreshed even when there are no changes

Currently, when feature flags are refreshed, we first make a request to fetch the Key and ETag for all feature flags. Then we compare it to the ETag for the feature flags that are currently in the client configuration. The expected behavior is to fetch the updated values of the feature flags only if we detect a change in the 'ETag' for one or more feature flags. However, the current code has a bug where the feature flag values are being fetched even when no change is detected based on the ETag comparisons. This item tracks fixing that bug, so we make a single request instead of two requests to refresh the feature flag values when no change is detected.

Update RefreshAsync to handle exceptions from Azure.Identity

The package Azure.Identity throws Azure.Identity.AuthenticationFailedException when it fails to use a TokenCredential. For example, when new DefaultAzureCredentials() is used to connect to Azure App Configuration but no valid identity could be found, then it throws AuthenticationFailedException. This issue tracks the code changes needed to handle all exceptions from Azure.Identity package without adding an explicit dependency.

Improvements for Offline File Cache

When managed identity (or any AAD authentication) is used to connect to App Configuration, the user need to provide Key, IV and SignKey as part of OfflineFileCacheOptions.

We should allow OfflineFileCache used with AAD auth but throw a more helpful exception when the above condition is detected.

See Azure/AppConfiguration#101 for more details fo the discussion.

Allow selection and sentinel keys on feature flags

Hi everyone,

We are currently building a feature flag infrastructure for all our applications. We want to use AppConfiguration, AppConfiguration-DotnetProvider and FeatureManagement-Dotnet to store and check the feature flags on the applications. We'll build our own feature flag management app though, to provide advanced logic (custom filters, audit, authentication/authorization, more...) to our internal users.

Since we are in a micro-services environment, we would like to reuse a single Azure App Configuration to decrease the costs and, hopefully, the infrastructure complexity.

We would ideally like to use and watch feature flags the same way it's done with "classic" app configuration usage.

For example, with the SDK we can currently do this:

                options.Select("App:MyApp:*");

                options.Connect(new Uri(endpoint), credential).ConfigureRefresh(refreshOptions =>
                        {
                            refreshOptions.Register("App:MyAppSentinelKey", true);
                        });

Bu this can't be done use feature flags.

The main reasons we would like to do this would be:

  • to reduce the number of feature flags that are scanned every 30s (or whatever the cache expiration is set to)

Indeed, putting aside the overload that it surely represents on the AppConfiguration instance, it would also require multiple calls for each multiple of 100 feature flags, because of paging. Since our instance will be used by many apps, we expect it to contains many feature flags. If we imagine the app containing 1000 feature flags, this would mean that each app has to make 10 calls every 30s just to check if something has changed. We would break the 20k max requests per hour with just 17 apps.
Of course, this could mitigated by increasing the cache expiration, but we'd loose reactivity, with doesn't seems to be a good tradeoff.

  • to avoid downloading feature flags updated data that aren't used by the application.

Indeed, with the currently implemented feature flag configuration, the app watches all the feature flags. This means that when a feature flag used by app A is updated, it will be watched & downloaded by app B, even if it doesn't use it.

For these reasons, we'd like to be able to use explicit .Select and .Register, which will eliminate those two drawbacks by providing the ability to select only the feature flags that are useful to the app, and by using sentinel keys that will be automatically updated by our Feature Flags management app.

We actually managed to enable this by copying and adapting this method

public AzureAppConfigurationOptions UseFeatureFlags(Action<FeatureFlagOptions> configure = null)

into something like this:

    public static class AzureAppConfigurationOptionsExtensions
    {
        internal static readonly TimeSpan DefaultFeatureFlagsCacheExpiration = TimeSpan.FromSeconds(30);
        internal static readonly TimeSpan MinimumFeatureFlagsCacheExpiration = TimeSpan.FromMilliseconds(1000);

        public static AzureAppConfigurationOptions UseMyFeatureFlags(this AzureAppConfigurationOptions options, Action<MyFeatureFlagsOptions> configure)
        {
            // This code is identical
            var featureFlagOptions = new MyFeatureFlagsOptions();
            configure?.Invoke(featureFlagOptions);

            if (featureFlagOptions.CacheExpirationTime < MinimumFeatureFlagsCacheExpiration)
            {
                throw new ArgumentOutOfRangeException(nameof(featureFlagOptions.CacheExpirationTime), featureFlagOptions.CacheExpirationTime.TotalMilliseconds,
                    string.Format(ErrorMessages.CacheExpirationTimeTooShort, MinimumFeatureFlagsCacheExpiration.TotalMilliseconds));
            }

            // This is just a Reflection hack to add FeatureManagementKeyValueAdapter
            var adapters = typeof(AzureAppConfigurationOptions).GetField("_adapters", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(options) as IList;

            var featureManagementKeyValueAdapter = Activator.CreateInstance(typeof(AzureAppConfigurationOptions).Assembly.GetType("Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement.FeatureManagementKeyValueAdapter"), nonPublic: true);

            adapters.Add(featureManagementKeyValueAdapter);

            // This allows us to select only the flags that we use.
            foreach (var featureFlagKey in featureFlagOptions.FeatureFlags)
            {
                options.Select(FeatureManagementConstants.FeatureFlagMarker + featureFlagKey, featureFlagOptions.Label);
            }

            options.ConfigureRefresh(r =>
            {
                r.SetCacheExpiration(featureFlagOptions.CacheExpirationTime);
                // This allows us to use a sentinel key.
                r.Register(FeatureManagementConstants.FeatureFlagScopeKey + featureFlagOptions.Scope, featureFlagOptions.Label, true);
            });

            return options;
        }

    }

    internal class FeatureManagementConstants
    {
        public const string FeatureFlagScopeKey = ".appconfig.scope/";
        public const string FeatureFlagMarker = ".appconfig.featureflag.my/";
        public const string ContentType = "application/vnd.microsoft.appconfig.ff+json";
        public const string SectionName = "FeatureManagement";
        public const string EnabledFor = "EnabledFor";
    }

    public class MyFeatureFlagsOptions : FeatureFlagOptions
    {
        internal List<string> FeatureFlags { get; set; } = new List<string>();

        public string Scope { get; set; } = default!;

        // As you can see here, Feature Flags are opted-in
        public void Use(string feature)
        {
            FeatureFlags.Add(feature);
        }
    }

As you can see, this code isn't pretty, but quite simple.
Additionally to FeatureManagementKeyValueAdapter and _adapters which are respectively internal and private, there is one additional hack which consists of using a different prefix for the feature flags, that can be seen on FeatureManagementConstants.FeatureFlagMarker.
This is necessary because there is a check here that forces a full scanning during refresh if one of the watched key filter starts with .appconfig.featureflag/:

bool useDefaultQuery = !_options.KeyValueSelectors.Any(selector => !selector.KeyFilter.StartsWith(FeatureManagementConstants.FeatureFlagMarker));

By putting all of our feature flags under .appconfig.featureflag.my/, we workaround this check.

So my final questions would be:

  • Would it be possible to implement AzureAppConfigurationOptions.Select and AzureAppConfigurationRefreshOptions.Register on feature flags usage? It could probably be additional properties/methods on FeatureFlagOptions, or perhaps another overload (or anything you could think of!).
  • If not, could you please expose some private/internal dependencies, so we can implement it ourselves? This would include exposing some way of adding FeatureManagementKeyValueAdapter, and also, ideally, provide a way to disable the global scan on AzureAppConfigurationProvider.LoadAll.

Allow refresh to be registered for multiple key-values with same key but different labels

This issue tracks the changes needed to allow users to register refresh for multiple key-values with the same key but different labels.

var configBuilder = new ConfigurationBuilder().AddAzureAppConfiguration(options =>
{
    options.Connect(connectionString)
        .Select(KeyFilter.Any, LabelFilter.Null)
        .Select(KeyFilter.Any, "some_label")
        .ConfigureRefresh(refresh =>
        {
            refresh.Register("some_key", LabelFilter.Null, true)
                   .Register("some_key", "some_label", true);
        })
});

Support watching multiple keys with wildcard

Currently watching for changes in key-values requires a 1:1 call to Watch(). Often times, configuration sections are broken into objects such as logging:enabled and security:sanitizeUrls. Many applications may wish to watch all keys for a certain section of configuration. They may want to do something like Watch("Settings:*"). Then when there is a server side change to the KV with the key Settings:BackgroundColor or with the key Settings:TextSpeed, the configuration will be updated.

If watch fails, it will not restart, leaving polling ups in a detached state.

At the moment, the watch functionality of the provider can throw an exception based on a number of scenarios including network issues which can happen at any time. The exception happens on the observation thread which by default is in the background and will go completely unnoticed. This means that if an application is created that depends on watching key-values and an exception occurs in the watch the application will cease to function correctly, and it could happen silently.

The fix would be to devise a sensible way to either restart the watch automatically or allow the user to plugin and restart the watch if it throws.

The optional:true parameter for AddAzureAppConfiguration does not work

I have code below and a key vault reference in the store.

builder.AddAzureAppConfiguration(options =>
{
    options.Connect(configuration["ConnectionString"])
}, optional:true);

The app does not have access to my key vault, but since I specified optional:true, the app is not expected to throw. However, it does:

Unhandled exception. Microsoft.Extensions.Configuration.AzureAppConfiguration.KeyVaultReferenceException: No key vault credential configured and no matching secret client could be found.. ErrorCode:, Key:foo, Label:, Etag:oyI5kwp9Dp68pxoux30v7G6lVQJ, SecretIdentifier:https://keyvaultreference.vault.azure.net/secrets/MySecret
 ---> System.UnauthorizedAccessException: No key vault credential configured and no matching secret client could be found.
   at Microsoft.Extensions.Configuration.AzureAppConfiguration.AzureKeyVault.AzureKeyVaultSecretProvider.GetSecretValue(Uri secretUri, CancellationToken cancellationToken)
   at Microsoft.Extensions.Configuration.AzureAppConfiguration.AzureKeyVault.AzureKeyVaultKeyValueAdapter.ProcessKeyValue(ConfigurationSetting setting, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.Configuration.AzureAppConfiguration.AzureKeyVault.AzureKeyVaultKeyValueAdapter.ProcessKeyValue(ConfigurationSetting setting, CancellationToken cancellationToken)
   at Microsoft.Extensions.Configuration.AzureAppConfiguration.AzureAppConfigurationProvider.ProcessAdapters(ConfigurationSetting setting, CancellationToken cancellationToken)
   at Microsoft.Extensions.Configuration.AzureAppConfiguration.AzureAppConfigurationProvider.SetData(IDictionary`2 data, CancellationToken cancellationToken)
   at Microsoft.Extensions.Configuration.AzureAppConfiguration.AzureAppConfigurationProvider.LoadAll()
   at Microsoft.Extensions.Configuration.AzureAppConfiguration.AzureAppConfigurationProvider.Load()
   at Microsoft.Extensions.Configuration.ConfigurationRoot..ctor(IList`1 providers)
   at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
   at Microsoft.Extensions.Configuration.AzureAppConfiguration.Examples.ConsoleApplication.Program.Configure()
   at Microsoft.Extensions.Configuration.AzureAppConfiguration.Examples.ConsoleApplication.Program.Main(String[] args)

Add a new API to allow custom client options

Proposed API

public AzureAppConfigurationOptions ConfigureClientOptions(Action<ConfigurationClientOptions> options)

This API should allow a user to overwirte the properties of ConfigurationClientOptions we use by default.

For example, in the case of Azure/AppConfiguration#179, the customer can do

using HttpClient client = new HttpClient(
    new HttpClientHandler()
    {
        Proxy = new WebProxy(new Uri("http://example.com"))
    });

builder.AddAzureAppConfiguration(o =>
    o.Connect(...)
     .ConfigureClientOptions(co => co.Transport = new HttpClientTransport(client));
)

Reference here.

cc: @drago-draganov @jimmyca15

Optional remote config store

We should allow users to specify whether a remote config store is optional. If specified, we don't fail in case the remote config store is not accessible for any reason.

API rename `Use` -> `Select`

There are confusions about the current API Use whether it takes a specific key name or a filter of keys. Select is considered a better naming to indicate the API is meant to take a key filter.

Loading Key Vault values is attempted even if ConfigureKeyVault isn't called

Issue originally brought up in the docs repo: MicrosoftDocs/azure-docs#49293

Repro:

  1. Setup an App Config instance that contains a Key Vault reference.
  2. Configure App Configuration without calling ConfigureKeyVault()
[assembly: FunctionsStartup(typeof(FunctionApp.Startup))]
namespace FunctionApp
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var config = new ConfigurationBuilder();

            config.AddEnvironmentVariables();
            TokenCredential credential = new DefaultAzureCredential();
            var endpoint = new Uri(Environment.GetEnvironmentVariable("AppConfigEndpoint"));

            //Setup Azure App Config
            config.AddAzureAppConfiguration(options =>
            {
                options.Connect(endpoint, credential);
            });

            builder.Services.Replace(
                ServiceDescriptor.Singleton(typeof(IConfiguration),
                config.Build()));
        }
    }
}

Expected Behavior: App Config provider won't try to load any results from Key Vault.

Actual Behavior: "Microsoft.Extensions.Configuration.AzureAppConfiguration.KeyVaultReferenceException: 'No key vault credential configured and no matching secret client could be found."

This is the same result as when ConfigureKeyVault() is called with a null credential. This means that if there are any Key Vault references in an App Config instance, all the apps using it will need to pass credentials to ConfigureKeyVault() even if they won't be using Key Vault references or use Select() statements to try to avoid loading any.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.