< Summary - Envilder .NET SDK

Information
Class: Envilder.Infrastructure.SecretProviderFactory
Assembly: Envilder
File(s): /home/runner/work/envilder/envilder/src/sdks/dotnet/Infrastructure/SecretProviderFactory.cs
Tag: 352_26963168797
Line coverage
97%
Covered lines: 40
Uncovered lines: 1
Coverable lines: 41
Total lines: 125
Line coverage: 97.5%
Branch coverage
94%
Covered branches: 36
Total branches: 38
Branch coverage: 94.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Create(...)100%1616100%
ValidateCrossProviderConfig(...)100%88100%
CreateAzureSecretProvider(...)100%22100%
CreateAwsSecretProvider(...)100%44100%
ResolveProfileRegion(...)75%5466.67%
ResolveRegion()75%44100%

File(s)

/home/runner/work/envilder/envilder/src/sdks/dotnet/Infrastructure/SecretProviderFactory.cs

#LineLine coverage
 1namespace Envilder.Infrastructure;
 2
 3using Amazon;
 4using Amazon.Runtime.CredentialManagement;
 5using Amazon.SimpleSystemsManagement;
 6using global::Azure.Identity;
 7using global::Azure.Security.KeyVault.Secrets;
 8using global::Envilder.Domain.Ports;
 9using global::Envilder.Infrastructure.Aws;
 10using global::Envilder.Infrastructure.Azure;
 11using System;
 12
 13/// <summary>
 14/// Creates the appropriate <see cref="ISecretProvider"/> implementation
 15/// based on the map file configuration and optional runtime overrides.
 16/// </summary>
 17internal static class SecretProviderFactory
 18{
 119  private static readonly RegionEndpoint FallbackRegion = RegionEndpoint.USEast1;
 20
 21  /// <summary>
 22  /// Creates an <see cref="ISecretProvider"/> for the provider specified in
 23  /// <paramref name="config"/>. When <paramref name="options"/> is provided,
 24  /// its values take precedence over <paramref name="config"/>.
 25  /// </summary>
 26  /// <param name="config">Configuration from the <c>$config</c> section of a map file.</param>
 27  /// <param name="options">Optional runtime overrides (e.g. CLI flags).</param>
 28  /// <returns>A ready-to-use secret provider.</returns>
 29  /// <exception cref="InvalidOperationException">
 30  /// Thrown when Azure is selected but no Vault URL is provided.
 31  /// </exception>
 32  public static ISecretProvider Create(MapFileConfig config, EnvilderOptions? options = null)
 33  {
 134    if (config is null)
 35    {
 136      throw new ArgumentNullException(nameof(config));
 37    }
 38
 139    var provider = options?.Provider ?? config.Provider;
 140    var profile = options?.Profile ?? config.Profile;
 141    var vaultUrl = options?.VaultUrl ?? config.VaultUrl;
 42
 143    ValidateCrossProviderConfig(provider, profile, vaultUrl);
 44
 145    return provider switch
 146    {
 147      SecretProviderType.Azure => CreateAzureSecretProvider(vaultUrl),
 148      _ => CreateAwsSecretProvider(profile),
 149    };
 50  }
 51
 52  private static void ValidateCrossProviderConfig(
 53    SecretProviderType? provider,
 54    string? profile,
 55    string? vaultUrl)
 56  {
 157    var isAzure = provider == SecretProviderType.Azure;
 58
 159    if (isAzure && !string.IsNullOrWhiteSpace(profile))
 60    {
 161      throw new InvalidOperationException(
 162        "AWS profile cannot be used with Azure Key Vault provider.");
 63    }
 64
 165    if (!isAzure && !string.IsNullOrWhiteSpace(vaultUrl))
 66    {
 167      throw new InvalidOperationException(
 168        "Vault URL cannot be used with AWS SSM provider.");
 69    }
 170  }
 71
 72  private static AzureKeyVaultSecretProvider CreateAzureSecretProvider(string? vaultUrl)
 73  {
 174    if (string.IsNullOrWhiteSpace(vaultUrl))
 75    {
 176      throw new InvalidOperationException("Vault URL must be provided for Azure Key Vault provider.");
 77    }
 78
 179    var secretClient = new SecretClient(new Uri(vaultUrl), new DefaultAzureCredential());
 180    return new(secretClient);
 81  }
 82
 83  private static AwsSsmSecretProvider CreateAwsSecretProvider(string? profile)
 84  {
 185    if (!string.IsNullOrWhiteSpace(profile))
 86    {
 187      var profilesLocation = Environment.GetEnvironmentVariable("AWS_SHARED_CREDENTIALS_FILE");
 188      var chain = new CredentialProfileStoreChain(profilesLocation);
 189      if (chain.TryGetAWSCredentials(profile, out var credentials))
 90      {
 191        var region = ResolveProfileRegion(chain, profile!);
 192        return new(new AmazonSimpleSystemsManagementClient(credentials, region));
 93      }
 94
 195      throw new InvalidOperationException(
 196        $"AWS profile '{profile}' was not found in the credential store.");
 97    }
 98
 199    return new(new AmazonSimpleSystemsManagementClient());
 100  }
 101
 102  private static RegionEndpoint ResolveProfileRegion(CredentialProfileStoreChain chain, string profile)
 103  {
 1104    if (chain.TryGetProfile(profile, out var credentialProfile) && credentialProfile.Region != null)
 105    {
 0106      return credentialProfile.Region;
 107    }
 108
 1109    return ResolveRegion();
 110  }
 111
 112  /// <summary>
 113  /// Resolves the AWS region from environment variables (<c>AWS_REGION</c> or
 114  /// <c>AWS_DEFAULT_REGION</c>), falling back to <c>us-east-1</c> when neither is set.
 115  /// </summary>
 116  private static RegionEndpoint ResolveRegion()
 117  {
 1118    var regionName = Environment.GetEnvironmentVariable("AWS_REGION")
 1119      ?? Environment.GetEnvironmentVariable("AWS_DEFAULT_REGION");
 120
 1121    return string.IsNullOrWhiteSpace(regionName)
 1122      ? FallbackRegion
 1123      : RegionEndpoint.GetBySystemName(regionName);
 124  }
 125}