Skip to content

Commit

Permalink
Add XML doc, target net6.0 as well
Browse files Browse the repository at this point in the history
  • Loading branch information
wdolek committed Jan 15, 2024
1 parent 935f587 commit 5bd496d
Show file tree
Hide file tree
Showing 13 changed files with 192 additions and 31 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

![W4k.Either Build](https://github.com/wdolek/w4k-extensions-configuration-aws-secretsmanager/workflows/Build%20and%20test/badge.svg)
[![NuGet Badge](https://buildstats.info/nuget/W4k.Extensions.Configuration.Aws.SecretsManager?includePreReleases=true)](https://www.nuget.org/packages/W4k.Extensions.Configuration.Aws.SecretsManager/)
[![CodeQL](https://github.com/wdolek/w4k-extensions-configuration-aws-secretsmanager/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/wdolek/w4k-extensions-configuration-aws-secretsmanager/security/code-scanning)

Configuration provider for AWS SecretsManager.

Expand Down Expand Up @@ -90,7 +91,7 @@ builder.Configuration.AddSecretsManager(
```

There's helper class [`SecretsProcessor<T>`](W4k.Extensions.Configuration.Aws.SecretsManager/SecretsProcessor.cs) which
can be used to simplify implementation of custom processor (by providing implementation of `IParser<T>` and `ITokenizer<T>`).
can be used to simplify implementation of custom processor (by providing implementation of `ISecretStringParser<T>` and `IConfigurationTokenizer<T>`).

#### Configuration key transformation

Expand All @@ -110,7 +111,7 @@ builder.Configuration.AddSecretsManager(
});
```

It is also possible to clear even default transofmrer by simply calling `Clear()` method.
It is also possible to clear even default transformer by simply calling `Clear()` method.

```csharp
builder.Configuration.AddSecretsManager(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,24 @@

namespace W4k.Extensions.Configuration.Aws.SecretsManager;

/// <summary>
/// Configuration key transformer to modify tokenized configuration keys.
/// </summary>
public interface IConfigurationKeyTransformer
{
/// <summary>
/// Transforms tokenized configuration key to new value.
/// </summary>
/// <param name="key">Configuration key.</param>
/// <returns>Modified configuration key.</returns>
string Transform(string key);
}

/// <summary>
/// Key-delimiter transformer, replacing <c>__</c> with <see cref="ConfigurationPath.KeyDelimiter"/>.
/// </summary>
public sealed class KeyDelimiterTransformer : IConfigurationKeyTransformer
{
public static readonly IConfigurationKeyTransformer Instance = new KeyDelimiterTransformer();
/// <inheritdoc/>
public string Transform(string key) => key.Replace("__", ConfigurationPath.KeyDelimiter);
}
12 changes: 12 additions & 0 deletions W4k.Extensions.Configuration.Aws.SecretsManager/Exceptions.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,45 @@
namespace W4k.Extensions.Configuration.Aws.SecretsManager;

/// <summary>
/// Thrown when fetching of secrets fails, either value is not set or getting of value failed with exception.
/// </summary>
public class SecretRetrievalException : Exception
{
/// <inheritdoc/>
public SecretRetrievalException()
{
}

/// <inheritdoc/>
public SecretRetrievalException(string message)
: base(message)
{
}

/// <inheritdoc/>
public SecretRetrievalException(string message, Exception inner)
: base(message, inner)
{
}
}

/// <summary>
/// Thrown when requested secret is not found.
/// </summary>
public class SecretNotFoundException : Exception
{
/// <inheritdoc/>
public SecretNotFoundException()
{
}

/// <inheritdoc/>
public SecretNotFoundException(string message)
: base(message)
{
}

/// <inheritdoc/>
public SecretNotFoundException(string message, Exception inner)
: base(message, inner)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace W4k.Extensions.Configuration.Aws.SecretsManager;

/// <summary>
/// Configuration tokenizer.
/// </summary>
/// <typeparam name="T">Type of output data type.</typeparam>
public interface IConfigurationTokenizer<in T>
{
/// <summary>
/// Tokenize input data into key-value pairs.
/// </summary>
/// <param name="input">Input secret value.</param>
/// <param name="prefix">Configuration key prefix.</param>
/// <returns>
/// Enumerable of key-value pairs.
/// </returns>
IEnumerable<KeyValuePair<string, string?>> Tokenize(T input, string prefix);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace W4k.Extensions.Configuration.Aws.SecretsManager;

/// <summary>
/// Secret string parser.
/// </summary>
/// <typeparam name="T">Type of output data type.</typeparam>
public interface ISecretStringParser<T>
{
/// <summary>
/// Try to parse secret string into output data type.
/// </summary>
/// <param name="secret">Secret string value.</param>
/// <param name="secretValue">Secret value.</param>
/// <returns>
/// Returns <c>true</c> if secret string was successfully parsed into output data type; <c>false</c> otherwise.
/// </returns>
bool TryParse(string secret, out T secretValue);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@

namespace W4k.Extensions.Configuration.Aws.SecretsManager.Json;

public interface ISecretStringParser<T>
{
bool TryParse(string secret, out T jsonElement);
}

internal class JsonElementParser : ISecretStringParser<JsonElement>
internal sealed class JsonElementParser : ISecretStringParser<JsonElement>
{
private static readonly JsonDocumentOptions DefaultJsonDocumentOptions = new()
{
Expand All @@ -16,24 +11,24 @@ internal class JsonElementParser : ISecretStringParser<JsonElement>
MaxDepth = 16,
};

public bool TryParse(string secret, out JsonElement jsonElement)
public bool TryParse(string secret, out JsonElement secretValue)
{
if (!IsPossiblyJsonValue(secret))
{
jsonElement = default;
secretValue = default;
return false;
}

try
{
using var document = JsonDocument.Parse(secret, DefaultJsonDocumentOptions);
jsonElement = document.RootElement.Clone();
secretValue = document.RootElement.Clone();

return true;
}
catch (JsonException)
{
jsonElement = default;
secretValue = default;
return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@

namespace W4k.Extensions.Configuration.Aws.SecretsManager.Json;

public interface IConfigurationTokenizer<in T>
{
IEnumerable<KeyValuePair<string, string?>> Tokenize(T input, string prefix);
}

internal sealed class JsonElementTokenizer : IConfigurationTokenizer<JsonElement>
{
[SuppressMessage("ReSharper", "CognitiveComplexity", Justification = "ಠ_ಠ")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@

namespace W4k.Extensions.Configuration.Aws.SecretsManager;

internal sealed class SecretsFetcher(IAmazonSecretsManager secretsManager)
internal sealed class SecretsFetcher
{
private readonly IAmazonSecretsManager _secretsManager = secretsManager;
private readonly IAmazonSecretsManager _secretsManager;

public SecretsFetcher(IAmazonSecretsManager secretsManager)
{
_secretsManager = secretsManager;
}

public async Task<string> GetSecretString(string secretId, SecretVersionBase? version)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@

namespace W4k.Extensions.Configuration.Aws.SecretsManager;

internal sealed class SecretsManagerConfigurationProvider(
IAmazonSecretsManager secretsManager,
SecretsManagerConfigurationProviderOptions options)
: ConfigurationProvider
internal sealed class SecretsManagerConfigurationProvider : ConfigurationProvider
{
private readonly SecretsFetcher _secretsFetcher = new(secretsManager);
private readonly SecretsManagerConfigurationProviderOptions _options = options;
private readonly SecretsFetcher _secretsFetcher;
private readonly SecretsManagerConfigurationProviderOptions _options;

public SecretsManagerConfigurationProvider(IAmazonSecretsManager secretsManager, SecretsManagerConfigurationProviderOptions options)
{
_secretsFetcher = new SecretsFetcher(secretsManager);
_options = options;
}

public override void Load()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
namespace W4k.Extensions.Configuration.Aws.SecretsManager;

/// <summary>
/// Configuration of <see cref="SecretsManagerConfigurationProvider"/>.
/// </summary>
public class SecretsManagerConfigurationProviderOptions
{
private string _keyPrefix = "";
private ISecretsProcessor _processor = SecretsProcessor.Json;


/// <summary>
/// Gets or sets secret ID/name to fetch.
/// </summary>
#if NET8_0_OR_GREATER
public required string SecretId { get; init; }
#else
public string SecretId { get; init; } = null!;
#endif

/// <summary>
/// Gets or sets secret version to fetch, if not provided, latest version of secret is fetched.
/// </summary>
public SecretVersionBase? Version { get; set; }

/// <summary>
/// Configuration key prefix, if not set, secret properties are placed directly in configuration root.
/// </summary>
public string ConfigurationKeyPrefix
{
get { return _keyPrefix; }
Expand All @@ -18,6 +35,9 @@ public string ConfigurationKeyPrefix
}
}

/// <summary>
/// Secrets processor (parsing, tokenizing), default is <see cref="SecretsProcessor.Json"/>.
/// </summary>
public ISecretsProcessor Processor
{
get { return _processor; }
Expand All @@ -28,20 +48,51 @@ public ISecretsProcessor Processor
}
}

public List<IConfigurationKeyTransformer> KeyTransformers { get; } = [KeyDelimiterTransformer.Instance];
/// <summary>
/// Configuration key transformers applied after tokenization. By default, only <see cref="KeyDelimiterTransformer"/> is present.
/// </summary>
public List<IConfigurationKeyTransformer> KeyTransformers { get; } = new() { new KeyDelimiterTransformer() };
}

public abstract class SecretVersionBase;
/// <summary>
/// Representation of secret version.
/// </summary>
public abstract class SecretVersionBase
{
}

/// <inheritdoc/>
public sealed class SecretVersion : SecretVersionBase
{
public required string Id { get; init; }
/// <summary>
/// Gets or sets secret version ID.
/// </summary>
#if NET8_0_OR_GREATER
public required string Id { get; init; }
#else
public string? Id { get; init; }
#endif
}

/// <inheritdoc/>
public sealed class StagedSecretVersion : SecretVersionBase
{
/// <summary>
/// Secret version for <c>AWSCURRENT</c> stage.
/// </summary>
public static readonly StagedSecretVersion Current = new() { Stage = "AWSCURRENT" };

/// <summary>
/// Secret version for <c>AWSPREVIOUS</c> stage.
/// </summary>
public static readonly StagedSecretVersion Previous = new() { Stage = "AWSPREVIOUS" };

/// <summary>
/// Gets or sets custom stage name.
/// </summary>
#if NET8_0_OR_GREATER
public required string Stage { get; init; }
#else
public string? Stage { get; init; }
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,21 @@ public IConfigurationProvider Build(IConfigurationBuilder builder)
}
}

/// <summary>
/// Extensions for <see cref="IConfigurationBuilder"/> to add AWS Secrets Manager as configuration source.
/// </summary>
public static class SecretsManagerConfigurationExtensions
{
/// <summary>
/// Adds secrets manager as configuration source.
/// </summary>
/// <remarks>
/// This extension methods uses default <see cref="AmazonSecretsManagerClient"/> instance.
/// </remarks>
/// <param name="builder">Configuration builder.</param>
/// <param name="secretName">Secret name or ID.</param>
/// <param name="configureOptions">Configure options callback.</param>
/// <returns>Instance of <see cref="IConfigurationBuilder"/></returns>
public static IConfigurationBuilder AddSecretsManager(
this IConfigurationBuilder builder,
string secretName,
Expand All @@ -33,12 +46,23 @@ public static IConfigurationBuilder AddSecretsManager(
return builder.AddSecretsManager(secretName, client, configureOptions);
}

/// <summary>
/// Adds secrets manager as configuration source.
/// </summary>
/// <param name="builder">Configuration builder.</param>
/// <param name="secretName">Secret name or ID.</param>
/// <param name="client">AWS Secrets Manager client.</param>
/// <param name="configureOptions">Configure options callback.</param>
/// <returns>Instance of <see cref="IConfigurationBuilder"/></returns>
public static IConfigurationBuilder AddSecretsManager(
this IConfigurationBuilder builder,
string secretName,
IAmazonSecretsManager client,
Action<SecretsManagerConfigurationProviderOptions>? configureOptions = null)
{
#if !NET8_0_OR_GREATER
ArgumentNullException.ThrowIfNull(secretName);
#endif
var providerOptions = new SecretsManagerConfigurationProviderOptions { SecretId = secretName };
configureOptions?.Invoke(providerOptions);

Expand Down
Loading

0 comments on commit 5bd496d

Please sign in to comment.