Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ To send us a pull request, please:
5. Send us a pull request, answering any default questions in the pull request interface.
6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.

Please enable the removal of trailing whitespaces in your IDE. In JetBrains IDEs, enable the following settings under `Settings -> Editor -> General`:

![JetBrains settings](https://private-user-images.githubusercontent.com/66992519/430139096-944e54df-a5a3-4655-bf10-93265149ec0b.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDQ3NDg3MjksIm5iZiI6MTc0NDc0ODQyOSwicGF0aCI6Ii82Njk5MjUxOS80MzAxMzkwOTYtOTQ0ZTU0ZGYtYTVhMy00NjU1LWJmMTAtOTMyNjUxNDllYzBiLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA0MTUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNDE1VDIwMjAyOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWEwNzdkNGFkMDljOWUyZmQwN2E2YmMyNzhlYTI3YmQxODQ4ZGY4YzRmMDExM2E4NzFlN2RiYjYzZTNjMTc2ZDQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.SPKnCA3FbZUQAcbedzcos2VJdjL95NuSIUFKrfyH5MY)

GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
[creating a pull request](https://help.github.com/articles/creating-a-pull-request/).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,26 @@ public interface ISecretsManagerCache : IDisposable
SecretCacheItem GetCachedSecret(string secretId);

/// <summary>
/// Asynchronously retrieves the specified SecretBinary after calling <see cref="GetCachedSecret"/>.
/// Asynchronously retrieves the specified <c>SecretBinary</c> after calling <see cref="GetCachedSecret"/>.
/// If both <c>versionId</c> and <c>versionStage</c> are specified, <c>versionId</c> takes precedence.
/// </summary>
Task<byte[]> GetSecretBinary(string secretId, CancellationToken cancellationToken = default);
/// <param name="secretId">The secret identifier. This can be the full ARN or the friendly name for the secret.</param>
/// <param name="versionId">The version identifier.</param>
/// <param name="versionStage">The version stage.</param>
/// <param name="cancellationToken">The cancellation token used for the Secrets Manager API call.</param>
/// <returns>The <c>SecretBinary</c>.</returns>
Task<byte[]> GetSecretBinary(string secretId, string versionId = null, string versionStage = null, CancellationToken cancellationToken = default);

/// <summary>
/// Asynchronously retrieves the specified SecretString after calling <see cref="GetCachedSecret"/>.
/// Asynchronously retrieves the specified <c>SecretString</c> after calling <see cref="GetCachedSecret"/>.
/// If both <c>versionId</c> and <c>versionStage</c> are specified, <c>versionId</c> takes precedence.
/// </summary>
Task<string> GetSecretString(string secretId, CancellationToken cancellationToken = default);
/// <param name="secretId">The secret identifier. This can be the full ARN or the friendly name for the secret.</param>
/// <param name="versionId">The version identifier.</param>
/// <param name="versionStage">The version stage.</param>
/// <param name="cancellationToken">The cancellation token used for the Secrets Manager API call.</param>
/// <returns>The <c>SecretString</c>.</returns>
Task<string> GetSecretString(string secretId, string versionId = null, string versionStage = null, CancellationToken cancellationToken = default);

/// <summary>
/// Requests the secret value from SecretsManager asynchronously and updates the cache entry with any changes.
Expand All @@ -47,4 +59,4 @@ public interface ISecretsManagerCache : IDisposable
/// </summary>
Task<bool> RefreshNowAsync(string secretId, CancellationToken cancellationToken = default);
}
}
}
22 changes: 14 additions & 8 deletions src/Amazon.SecretsManager.Extensions.Caching/SecretCacheItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@
/// </summary>
public class SecretCacheItem : SecretCacheObject<DescribeSecretResponse>
{
/// The cached secret value versions for this cached secret.
/// The cached secret value versions for this cached secret.
private readonly MemoryCache versions = new MemoryCache(new MemoryCacheOptions());
private const ushort MAX_VERSIONS_CACHE_SIZE = 10;

public SecretCacheItem(String secretId, IAmazonSecretsManager client, SecretCacheConfiguration config)

Check warning on line 32 in src/Amazon.SecretsManager.Extensions.Caching/SecretCacheItem.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'SecretCacheItem.SecretCacheItem(string, IAmazonSecretsManager, SecretCacheConfiguration)'
: base(secretId, client, config)
{
}

/// <summary>
/// Asynchronously retrieves the most current DescribeSecretResponse from Secrets Manager
/// as part of the Refresh operation.
Expand All @@ -46,9 +46,9 @@
/// <summary>
/// Asynchronously retrieves the GetSecretValueResponse from the proper SecretCacheVersion.
/// </summary>
protected override async Task<GetSecretValueResponse> GetSecretValueAsync(DescribeSecretResponse result, CancellationToken cancellationToken = default)
protected override async Task<GetSecretValueResponse> GetSecretValueAsync(DescribeSecretResponse result, string versionId = null, string versionStage = null, CancellationToken cancellationToken = default)
{
SecretCacheVersion version = GetVersion(result);
SecretCacheVersion version = GetVersion(result, versionId, versionStage);
if (version == null)
{
return null;
Expand All @@ -56,17 +56,17 @@
return await version.GetSecretValue(cancellationToken);
}

public override int GetHashCode()

Check warning on line 59 in src/Amazon.SecretsManager.Extensions.Caching/SecretCacheItem.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'SecretCacheItem.GetHashCode()'
{
return (secretId ?? string.Empty).GetHashCode();
}

public override string ToString()

Check warning on line 64 in src/Amazon.SecretsManager.Extensions.Caching/SecretCacheItem.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'SecretCacheItem.ToString()'
{
return $"SecretCacheItem: {secretId}";
}

public override bool Equals(object obj)

Check warning on line 69 in src/Amazon.SecretsManager.Extensions.Caching/SecretCacheItem.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'SecretCacheItem.Equals(object)'
{
return obj is SecretCacheItem sci && string.Equals(this.secretId, sci.secretId);
}
Expand All @@ -75,13 +75,19 @@
/// Retrieves the SecretCacheVersion corresponding to the Version Stage
/// specified by the SecretCacheConfiguration.
/// </summary>
private SecretCacheVersion GetVersion(DescribeSecretResponse describeResult)
private SecretCacheVersion GetVersion(DescribeSecretResponse describeResult, string versionId = null, string versionStage = null)
{
if (null == describeResult?.VersionIdsToStages) return null;
String currentVersionId = null;
foreach (KeyValuePair<String, List<String>> entry in describeResult.VersionIdsToStages)
{
if (entry.Value.Contains(config.VersionStage))
if (versionId != null && entry.Key.Equals(versionId))
{
currentVersionId = versionId;
break;
}

if ((versionStage != null && entry.Value.Contains(versionStage)) || entry.Value.Contains(config.VersionStage))
{
currentVersionId = entry.Key;
break;
Expand All @@ -108,4 +114,4 @@
versions.Compact((double)(versions.Count - config.MaxCacheSize) / versions.Count);
}
}
}
}
34 changes: 17 additions & 17 deletions src/Amazon.SecretsManager.Extensions.Caching/SecretCacheObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,22 @@
using System.Threading;
using System.Threading.Tasks;

public abstract class SecretCacheObject<T>

Check warning on line 23 in src/Amazon.SecretsManager.Extensions.Caching/SecretCacheObject.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'SecretCacheObject<T>'
{
/// The number of milliseconds to wait after an exception.
/// The number of milliseconds to wait after an exception.
private const long EXCEPTION_BACKOFF = 1000;

/// The growth factor of the backoff duration.
/// The growth factor of the backoff duration.
private const long EXCEPTION_BACKOFF_GROWTH_FACTOR = 2;

/// The maximum number of milliseconds to wait before retrying a failed
/// request.
private const long BACKOFF_PLATEAU = 128 * EXCEPTION_BACKOFF;

private JitteredDelay EXCEPTION_JITTERED_DELAY = new JitteredDelay(TimeSpan.FromMilliseconds(EXCEPTION_BACKOFF),
TimeSpan.FromMilliseconds(EXCEPTION_BACKOFF),
private JitteredDelay EXCEPTION_JITTERED_DELAY = new JitteredDelay(TimeSpan.FromMilliseconds(EXCEPTION_BACKOFF),
TimeSpan.FromMilliseconds(EXCEPTION_BACKOFF),
TimeSpan.FromMilliseconds(BACKOFF_PLATEAU));

/// When forcing a refresh using the refreshNow method, a random sleep
/// will be performed using this value. This helps prevent code from
/// executing a refreshNow in a continuous loop without waiting.
Expand All @@ -45,39 +45,39 @@
private JitteredDelay FORCE_REFRESH_JITTERED_DELAY = new JitteredDelay(TimeSpan.FromMilliseconds(FORCE_REFRESH_JITTER_BASE_INCREMENT),
TimeSpan.FromMilliseconds(FORCE_REFRESH_JITTER_VARIANCE));

/// The secret identifier for this cached object.
/// The secret identifier for this cached object.
protected String secretId;

/// A private object to synchronize access to certain methods.
/// A private object to synchronize access to certain methods.
protected static readonly SemaphoreSlim Lock = new SemaphoreSlim(1,1);

/// The AWS Secrets Manager client to use for requesting secrets.
/// The AWS Secrets Manager client to use for requesting secrets.
protected IAmazonSecretsManager client;

/// The Secret Cache Configuration.
protected SecretCacheConfiguration config;

/// A flag to indicate a refresh is needed.
/// A flag to indicate a refresh is needed.
private bool refreshNeeded = true;

/// The result of the last AWS Secrets Manager request for this item.
private Object data = null;

/// If the last request to AWS Secrets Manager resulted in an exception,
/// that exception will be thrown back to the caller when requesting
/// secret data.
protected Exception exception = null;


/// The number of exceptions encountered since the last successfully
/// AWS Secrets Manager request. This is used to calculate an exponential
/// backoff.
private long exceptionCount = 0;

/// The time to wait before retrying a failed AWS Secrets Manager request.
private long nextRetryTime = 0;

public static readonly ThreadLocal<Random> random = new ThreadLocal<Random>(() => new Random(Environment.TickCount));

Check warning on line 80 in src/Amazon.SecretsManager.Extensions.Caching/SecretCacheObject.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'SecretCacheObject<T>.random'



Expand All @@ -93,10 +93,10 @@
this.client = client;
this.config = config;
}

protected abstract Task<T> ExecuteRefreshAsync(CancellationToken cancellationToken = default);

Check warning on line 97 in src/Amazon.SecretsManager.Extensions.Caching/SecretCacheObject.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'SecretCacheObject<T>.ExecuteRefreshAsync(CancellationToken)'

protected abstract Task<GetSecretValueResponse> GetSecretValueAsync(T result, CancellationToken cancellationToken = default);
protected abstract Task<GetSecretValueResponse> GetSecretValueAsync(T result, string versionId = null, string versionStage = null, CancellationToken cancellationToken = default);

/// <summary>
/// Return the typed result object.
Expand Down Expand Up @@ -209,7 +209,7 @@
/// If the secret is due for a refresh, the refresh will occur before the result is returned.
/// If the refresh fails, the cached result is returned, or the cached exception is thrown.
/// </summary>
public async Task<GetSecretValueResponse> GetSecretValue(CancellationToken cancellationToken)
public async Task<GetSecretValueResponse> GetSecretValue(CancellationToken cancellationToken, string versionId = null, string versionStage = null)
{
bool success = false;
await Lock.WaitAsync(cancellationToken);
Expand All @@ -226,7 +226,7 @@
{
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(exception).Throw();
}
return await GetSecretValueAsync(GetResult(), cancellationToken);
return await GetSecretValueAsync(GetResult(), versionId, versionStage, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ protected override async Task<GetSecretValueResponse> ExecuteRefreshAsync(Cancel
return await this.client.GetSecretValueAsync(new GetSecretValueRequest { SecretId = this.secretId, VersionId = this.versionId }, cancellationToken);
}

protected override Task<GetSecretValueResponse> GetSecretValueAsync(GetSecretValueResponse result, CancellationToken cancellationToken = default)
protected override Task<GetSecretValueResponse> GetSecretValueAsync(GetSecretValueResponse result, string versionId = null, string versionStage = null, CancellationToken cancellationToken = default)
{
return Task.FromResult(result);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,24 +86,36 @@ public void Dispose()
}

/// <summary>
/// Asynchronously retrieves the specified SecretString after calling <see cref="GetCachedSecret"/>.
/// Asynchronously retrieves the specified <c>SecretString</c> after calling <see cref="GetCachedSecret"/>.
/// If both <c>versionId</c> and <c>versionStage</c> are specified, <c>versionId</c> takes precedence.
/// </summary>
public async Task<String> GetSecretString(String secretId, CancellationToken cancellationToken = default)
/// <param name="secretId">The secret identifier. This can be the full ARN or the friendly name for the secret.</param>
/// <param name="versionId">The version identifier.</param>
/// <param name="versionStage">The version stage.</param>
/// <param name="cancellationToken">The cancellation token used for the Secrets Manager API call.</param>
/// <returns>The <c>SecretString</c>.</returns>
public async Task<String> GetSecretString(String secretId, string versionId = null, string versionStage = null, CancellationToken cancellationToken = default)
{
SecretCacheItem secret = GetCachedSecret(secretId);
GetSecretValueResponse response = null;
response = await secret.GetSecretValue(cancellationToken);
response = await secret.GetSecretValue(cancellationToken, versionId, versionStage);
return response?.SecretString;
}

/// <summary>
/// Asynchronously retrieves the specified SecretBinary after calling <see cref="GetCachedSecret"/>.
/// Asynchronously retrieves the specified <c>SecretBinary</c> after calling <see cref="GetCachedSecret"/>.
/// If both <c>versionId</c> and <c>versionStage</c> are specified, <c>versionId</c> takes precedence.
/// </summary>
public async Task<byte[]> GetSecretBinary(String secretId, CancellationToken cancellationToken = default)
/// <param name="secretId">The secret identifier. This can be the full ARN or the friendly name for the secret.</param>
/// <param name="versionId">The version identifier.</param>
/// <param name="versionStage">The version stage.</param>
/// <param name="cancellationToken">The cancellation token used for the Secrets Manager API call.</param>
/// <returns>The <c>SecretBinary</c>.</returns>
public async Task<byte[]> GetSecretBinary(String secretId, string versionId = null, string versionStage = null, CancellationToken cancellationToken = default)
{
SecretCacheItem secret = GetCachedSecret(secretId);
GetSecretValueResponse response = null;
response = await secret.GetSecretValue(cancellationToken);
response = await secret.GetSecretValue(cancellationToken, versionId, versionStage);
return response?.SecretBinary?.ToArray();
}

Expand Down Expand Up @@ -137,4 +149,4 @@ public SecretCacheItem GetCachedSecret(string secretId)
return secret;
}
}
}
}
Loading
Loading