< Summary

Class:Azure.Extensions.AspNetCore.Configuration.Secrets.AzureKeyVaultConfigurationProvider
Assembly:Azure.Extensions.AspNetCore.Configuration.Secrets
File(s):C:\Git\azure-sdk-for-net\sdk\extensions\Azure.Extensions.AspNetCore.Configuration.Secrets\src\AzureKeyVaultConfigurationProvider.cs
Covered lines:57
Uncovered lines:5
Coverable lines:62
Total lines:175
Line coverage:91.9% (57 of 62)
Covered branches:34
Total branches:40
Branch coverage:85% (34 of 40)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-100%100%
Load()-100%100%
PollForSecretChangesAsync()-57.14%50%
WaitForReload()-0%100%
LoadAsync()-100%85.71%
SetData(...)-100%100%
Dispose()-100%100%
.ctor(...)-100%100%
get_Key()-100%100%
get_Value()-100%100%
get_Updated()-100%100%
IsUpToDate(...)-66.67%50%

File(s)

C:\Git\azure-sdk-for-net\sdk\extensions\Azure.Extensions.AspNetCore.Configuration.Secrets\src\AzureKeyVaultConfigurationProvider.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Linq;
 7using System.Threading;
 8using System.Threading.Tasks;
 9using Azure.Core;
 10using Azure.Security.KeyVault.Secrets;
 11using Microsoft.Extensions.Configuration;
 12
 13namespace Azure.Extensions.AspNetCore.Configuration.Secrets
 14{
 15    /// <summary>
 16    /// An AzureKeyVault based <see cref="ConfigurationProvider"/>.
 17    /// </summary>
 18    internal class AzureKeyVaultConfigurationProvider : ConfigurationProvider, IDisposable
 19    {
 20        private readonly TimeSpan? _reloadInterval;
 21        private readonly SecretClient _client;
 22        private readonly KeyVaultSecretManager _manager;
 23        private Dictionary<string, LoadedSecret> _loadedSecrets;
 24        private Task _pollingTask;
 25        private readonly CancellationTokenSource _cancellationToken;
 26
 27        /// <summary>
 28        /// Creates a new instance of <see cref="AzureKeyVaultConfigurationProvider"/>.
 29        /// </summary>
 30        /// <param name="client">The <see cref="SecretClient"/> to use for retrieving values.</param>
 31        /// <param name="manager">The <see cref="KeyVaultSecretManager"/> to use in managing values.</param>
 32        /// <param name="reloadInterval">The timespan to wait in between each attempt at polling the Azure Key Vault for
 2833        public AzureKeyVaultConfigurationProvider(SecretClient client, KeyVaultSecretManager manager, TimeSpan? reloadIn
 34        {
 2835            Argument.AssertNotNull(client, nameof(client));
 2836            Argument.AssertNotNull(manager, nameof(manager));
 37
 2638            _client = client;
 2639            _manager = manager;
 2640            if (reloadInterval != null && reloadInterval.Value <= TimeSpan.Zero)
 41            {
 442                throw new ArgumentOutOfRangeException(nameof(reloadInterval), reloadInterval, nameof(reloadInterval) + "
 43            }
 44
 2245            _pollingTask = null;
 2246            _cancellationToken = new CancellationTokenSource();
 2247            _reloadInterval = reloadInterval;
 2248        }
 49
 50        /// <summary>
 51        /// Load secrets into this provider.
 52        /// </summary>
 2453        public override void Load() => LoadAsync().GetAwaiter().GetResult();
 54
 55        private async Task PollForSecretChangesAsync()
 56        {
 2057            while (!_cancellationToken.IsCancellationRequested)
 58            {
 2059                await WaitForReload().ConfigureAwait(false);
 60                try
 61                {
 1062                    await LoadAsync().ConfigureAwait(false);
 1063                }
 064                catch (Exception)
 65                {
 66                    // Ignore
 067                }
 68            }
 069        }
 70
 71        protected virtual Task WaitForReload()
 72        {
 73            // WaitForReload is only called when the _reloadInterval has a value.
 074            return Task.Delay(_reloadInterval.Value, _cancellationToken.Token);
 75        }
 76
 77        private async Task LoadAsync()
 78        {
 3479            var secretPages = _client.GetPropertiesOfSecretsAsync();
 80
 3481            var tasks = new List<Task<Response<KeyVaultSecret>>>();
 3482            var newLoadedSecrets = new Dictionary<string, LoadedSecret>();
 3483            var oldLoadedSecrets = Interlocked.Exchange(ref _loadedSecrets, null);
 84
 17285            await foreach (var secret in secretPages.ConfigureAwait(false))
 86            {
 5287                if (!_manager.Load(secret) || secret.Enabled != true)
 88                {
 89                    continue;
 90                }
 91
 4492                var secretId = secret.Name;
 4493                if (oldLoadedSecrets != null &&
 4494                    oldLoadedSecrets.TryGetValue(secretId, out var existingSecret) &&
 4495                    existingSecret.IsUpToDate(secret.UpdatedOn))
 96                {
 897                    oldLoadedSecrets.Remove(secretId);
 898                    newLoadedSecrets.Add(secretId, existingSecret);
 99                }
 100                else
 101                {
 36102                    tasks.Add(_client.GetSecretAsync(secret.Name));
 103                }
 104            }
 105
 34106            await Task.WhenAll(tasks).ConfigureAwait(false);
 107
 140108            foreach (var task in tasks)
 109            {
 36110                var secretBundle = task.Result;
 36111                newLoadedSecrets.Add(secretBundle.Value.Name, new LoadedSecret(_manager.GetKey(secretBundle), secretBund
 112            }
 113
 34114            _loadedSecrets = newLoadedSecrets;
 115
 116            // Reload is needed if we are loading secrets that were not loaded before or
 117            // secret that was loaded previously is not available anymore
 34118            if (tasks.Any() || oldLoadedSecrets?.Any() == true)
 119            {
 32120                SetData(_loadedSecrets, fireToken: oldLoadedSecrets != null);
 121            }
 122
 123            // schedule a polling task only if none exists and a valid delay is specified
 34124            if (_pollingTask == null && _reloadInterval != null)
 125            {
 10126                _pollingTask = PollForSecretChangesAsync();
 127            }
 34128        }
 129
 130        private void SetData(Dictionary<string, LoadedSecret> loadedSecrets, bool fireToken)
 131        {
 32132            var data = new Dictionary<string, string>(loadedSecrets.Count, StringComparer.OrdinalIgnoreCase);
 148133            foreach (var secretItem in loadedSecrets)
 134            {
 42135                data.Add(secretItem.Value.Key, secretItem.Value.Value);
 136            }
 137
 32138            Data = data;
 32139            if (fireToken)
 140            {
 10141                OnReload();
 142            }
 32143        }
 144
 145        /// <inheritdoc/>
 146        public void Dispose()
 147        {
 20148            _cancellationToken.Cancel();
 20149        }
 150
 151        private readonly struct LoadedSecret
 152        {
 153            public LoadedSecret(string key, string value, DateTimeOffset? updated)
 154            {
 36155                Key = key;
 36156                Value = value;
 36157                Updated = updated;
 36158            }
 159
 42160            public string Key { get; }
 42161            public string Value { get; }
 24162            public DateTimeOffset? Updated { get; }
 163
 164            public bool IsUpToDate(DateTimeOffset? updated)
 165            {
 12166                if (updated.HasValue != Updated.HasValue)
 167                {
 0168                    return false;
 169                }
 170
 12171                return updated.GetValueOrDefault() == Updated.GetValueOrDefault();
 172            }
 173        }
 174    }
 175}