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();
}
}
}