< 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: 299_25910610327
Line coverage
97%
Covered lines: 40
Uncovered lines: 1
Coverable lines: 41
Total lines: 126
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 Envilder.Domain;
 7using Envilder.Domain.Ports;
 8using Envilder.Infrastructure.Aws;
 9using Envilder.Infrastructure.Azure;
 10using global::Azure.Identity;
 11using global::Azure.Security.KeyVault.Secrets;
 12using System;
 13
 14/// <summary>
 15/// Creates the appropriate <see cref="ISecretProvider"/> implementation
 16/// based on the map file configuration and optional runtime overrides.
 17/// </summary>
 18internal static class SecretProviderFactory
 19{
 120  private static readonly RegionEndpoint FallbackRegion = RegionEndpoint.USEast1;
 21
 22  /// <summary>
 23  /// Creates an <see cref="ISecretProvider"/> for the provider specified in
 24  /// <paramref name="config"/>. When <paramref name="options"/> is provided,
 25  /// its values take precedence over <paramref name="config"/>.
 26  /// </summary>
 27  /// <param name="config">Configuration from the <c>$config</c> section of a map file.</param>
 28  /// <param name="options">Optional runtime overrides (e.g. CLI flags).</param>
 29  /// <returns>A ready-to-use secret provider.</returns>
 30  /// <exception cref="InvalidOperationException">
 31  /// Thrown when Azure is selected but no Vault URL is provided.
 32  /// </exception>
 33  public static ISecretProvider Create(MapFileConfig config, EnvilderOptions? options = null)
 34  {
 135    if (config is null)
 36    {
 137      throw new ArgumentNullException(nameof(config));
 38    }
 39
 140    var provider = options?.Provider ?? config.Provider;
 141    var profile = options?.Profile ?? config.Profile;
 142    var vaultUrl = options?.VaultUrl ?? config.VaultUrl;
 43
 144    ValidateCrossProviderConfig(provider, profile, vaultUrl);
 45
 146    return provider switch
 147    {
 148      SecretProviderType.Azure => CreateAzureSecretProvider(vaultUrl),
 149      _ => CreateAwsSecretProvider(profile),
 150    };
 51  }
 52
 53  private static void ValidateCrossProviderConfig(
 54    SecretProviderType? provider,
 55    string? profile,
 56    string? vaultUrl)
 57  {
 158    var isAzure = provider == SecretProviderType.Azure;
 59
 160    if (isAzure && !string.IsNullOrWhiteSpace(profile))
 61    {
 162      throw new InvalidOperationException(
 163        "AWS profile cannot be used with Azure Key Vault provider.");
 64    }
 65
 166    if (!isAzure && !string.IsNullOrWhiteSpace(vaultUrl))
 67    {
 168      throw new InvalidOperationException(
 169        "Vault URL cannot be used with AWS SSM provider.");
 70    }
 171  }
 72
 73  private static AzureKeyVaultSecretProvider CreateAzureSecretProvider(string? vaultUrl)
 74  {
 175    if (string.IsNullOrWhiteSpace(vaultUrl))
 76    {
 177      throw new InvalidOperationException("Vault URL must be provided for Azure Key Vault provider.");
 78    }
 79
 180    var secretClient = new SecretClient(new Uri(vaultUrl), new DefaultAzureCredential());
 181    return new(secretClient);
 82  }
 83
 84  private static AwsSsmSecretProvider CreateAwsSecretProvider(string? profile)
 85  {
 186    if (!string.IsNullOrWhiteSpace(profile))
 87    {
 188      var profilesLocation = Environment.GetEnvironmentVariable("AWS_SHARED_CREDENTIALS_FILE");
 189      var chain = new CredentialProfileStoreChain(profilesLocation);
 190      if (chain.TryGetAWSCredentials(profile, out var credentials))
 91      {
 192        var region = ResolveProfileRegion(chain, profile!);
 193        return new(new AmazonSimpleSystemsManagementClient(credentials, region));
 94      }
 95
 196      throw new InvalidOperationException(
 197        $"AWS profile '{profile}' was not found in the credential store.");
 98    }
 99
 1100    return new(new AmazonSimpleSystemsManagementClient());
 101  }
 102
 103  private static RegionEndpoint ResolveProfileRegion(CredentialProfileStoreChain chain, string profile)
 104  {
 1105    if (chain.TryGetProfile(profile, out var credentialProfile) && credentialProfile.Region != null)
 106    {
 0107      return credentialProfile.Region;
 108    }
 109
 1110    return ResolveRegion();
 111  }
 112
 113  /// <summary>
 114  /// Resolves the AWS region from environment variables (<c>AWS_REGION</c> or
 115  /// <c>AWS_DEFAULT_REGION</c>), falling back to <c>us-east-1</c> when neither is set.
 116  /// </summary>
 117  private static RegionEndpoint ResolveRegion()
 118  {
 1119    var regionName = Environment.GetEnvironmentVariable("AWS_REGION")
 1120      ?? Environment.GetEnvironmentVariable("AWS_DEFAULT_REGION");
 121
 1122    return string.IsNullOrWhiteSpace(regionName)
 1123      ? FallbackRegion
 1124      : RegionEndpoint.GetBySystemName(regionName);
 125  }
 126}