using System; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using GB5Shared.Vault.Auth; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Polly; using Polly.Retry; using VaultSharp; using VaultSharp.Core; namespace GB5Shared.Vault { /// /// Registers the GB5 Vault abstraction. Call in Program.cs: /// builder.Services.AddGB5Vault(builder.Configuration); /// Bind the "Vault" section from appsettings.json or environment variables /// (e.g. Vault__Address, Vault__Token) — never hardcode Vault addresses/tokens. /// public static class VaultServiceExtensions { public static IServiceCollection AddGB5Vault(this IServiceCollection services, IConfiguration configuration) { var section = configuration.GetSection(VaultOptions.SectionName); var options = section.Get() ?? new VaultOptions(); if (string.IsNullOrWhiteSpace(options.Address)) throw new InvalidOperationException( $"{VaultOptions.SectionName}:Address must be configured to use AddGB5Vault."); if (options.AuthMethod == VaultAuthMethod.Token && string.IsNullOrWhiteSpace(options.Token)) throw new InvalidOperationException( $"{VaultOptions.SectionName}:Token must be configured when {VaultOptions.SectionName}:AuthMethod is 'Token'."); services.Configure(section); services.AddMemoryCache(); services.AddSingleton(); services.AddSingleton(sp => { var provider = sp.GetServices() .FirstOrDefault(p => p.Method == options.AuthMethod) ?? throw new InvalidOperationException( $"No Vault auth provider is registered for AuthMethod '{options.AuthMethod}'."); var authMethod = provider.Create(options); var settings = new VaultClientSettings(options.Address, authMethod) { VaultServiceTimeout = TimeSpan.FromSeconds(options.RequestTimeoutSeconds) }; if (!string.IsNullOrWhiteSpace(options.Namespace)) settings.Namespace = options.Namespace; return new VaultClient(settings); }); services.AddSingleton(_ => BuildResiliencePipeline(options)); services.AddSingleton(); return services; } /// /// Exponential-backoff retry for transient Vault failures (5xx / timeouts / connection errors). /// Client errors (bad path, auth failure, etc.) surface immediately — retrying those never helps. /// private static ResiliencePipeline BuildResiliencePipeline(VaultOptions options) { var retryOptions = new RetryStrategyOptions { ShouldHandle = args => new ValueTask( args.Outcome.Exception is VaultApiException or HttpRequestException or TaskCanceledException), MaxRetryAttempts = options.RetryMaxAttempts, BackoffType = DelayBackoffType.Exponential, Delay = TimeSpan.FromMilliseconds(options.RetryBaseDelayMilliseconds) }; return new ResiliencePipelineBuilder() .AddRetry(retryOptions) .Build(); } } }