From 6196292a8e21087247b6c1680780ef601689c795 Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Sat, 17 Feb 2024 12:18:07 +0000
Subject: [PATCH 01/16] Update batch examples
Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com>
---
examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj b/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj
index 0589010c1..ddbd7f4e3 100644
--- a/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj
+++ b/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj
@@ -7,8 +7,8 @@
-
-
+
+
From 21ea1f4357c1e4a0ce8a6e2dd92298e822eccce9 Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Sat, 17 Feb 2024 12:19:55 +0000
Subject: [PATCH 02/16] Update idempotency
Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com>
---
examples/Idempotency/src/HelloWorld/HelloWorld.csproj | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj
index 1b62c992d..645f82af7 100644
--- a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj
+++ b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj
@@ -8,7 +8,7 @@
-
-
+
+
From 7381be500da01e8c4c56e82c4a35cfb1704a7fef Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Sat, 17 Feb 2024 12:20:22 +0000
Subject: [PATCH 03/16] Update Logging
Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com>
---
examples/Logging/src/HelloWorld/HelloWorld.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/Logging/src/HelloWorld/HelloWorld.csproj b/examples/Logging/src/HelloWorld/HelloWorld.csproj
index 53323ac8d..57b2016b8 100644
--- a/examples/Logging/src/HelloWorld/HelloWorld.csproj
+++ b/examples/Logging/src/HelloWorld/HelloWorld.csproj
@@ -8,7 +8,7 @@
-
+
From 2c18aec9168aaae8119d6bd7a8001e9563d9aab0 Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Sat, 17 Feb 2024 12:20:48 +0000
Subject: [PATCH 04/16] Update Metrics
Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com>
---
examples/Metrics/src/HelloWorld/HelloWorld.csproj | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/examples/Metrics/src/HelloWorld/HelloWorld.csproj b/examples/Metrics/src/HelloWorld/HelloWorld.csproj
index b3262af29..a3bd77225 100644
--- a/examples/Metrics/src/HelloWorld/HelloWorld.csproj
+++ b/examples/Metrics/src/HelloWorld/HelloWorld.csproj
@@ -8,8 +8,8 @@
-
-
+
+
From 726033d8df348420ade9254f46f908d76c39a22a Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Sat, 17 Feb 2024 12:21:09 +0000
Subject: [PATCH 05/16] Update HelloWorld.csproj
Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com>
---
examples/Parameters/src/HelloWorld/HelloWorld.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/Parameters/src/HelloWorld/HelloWorld.csproj b/examples/Parameters/src/HelloWorld/HelloWorld.csproj
index d9709c681..8d258c524 100644
--- a/examples/Parameters/src/HelloWorld/HelloWorld.csproj
+++ b/examples/Parameters/src/HelloWorld/HelloWorld.csproj
@@ -8,6 +8,6 @@
-
+
From ba11561a3888e6ebac8774451196b6ca8df80616 Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Sat, 17 Feb 2024 12:21:49 +0000
Subject: [PATCH 06/16] Update Api example
Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com>
---
.../src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj b/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj
index 3609e4422..e3ad28df6 100644
--- a/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj
+++ b/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj
@@ -13,8 +13,8 @@
-
-
-
+
+
+
From 3ffc13992998b7251ff69ca7e6c43b3afb0201bb Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Sat, 17 Feb 2024 12:22:17 +0000
Subject: [PATCH 07/16] Update HelloWorld.csproj
Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com>
---
examples/Tracing/src/HelloWorld/HelloWorld.csproj | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/examples/Tracing/src/HelloWorld/HelloWorld.csproj b/examples/Tracing/src/HelloWorld/HelloWorld.csproj
index 32beb88bb..e7338a4a7 100644
--- a/examples/Tracing/src/HelloWorld/HelloWorld.csproj
+++ b/examples/Tracing/src/HelloWorld/HelloWorld.csproj
@@ -8,8 +8,8 @@
-
-
+
+
From 1a9e11dea5f291d2c9c520296aceb6b1fb4e07da Mon Sep 17 00:00:00 2001
From: Amir Khairalomoum
Date: Wed, 28 Feb 2024 12:54:54 +0000
Subject: [PATCH 08/16] add AppConfig provider
---
.../AWS.Lambda.Powertools.Parameters.csproj | 10 +-
.../AppConfig/AppConfigProvider.cs | 482 +++++++
.../AppConfigProviderConfigurationBuilder.cs | 167 +++
.../AppConfig/IAppConfigProvider.cs | 81 ++
.../AppConfigDictionaryTransformer.cs | 62 +
.../AppConfigJsonConfigurationParser.cs | 164 +++
.../AppConfig/AppConfigProviderCacheHelper.cs | 42 +
.../AppConfigProviderConfiguration.cs | 39 +
.../Internal/AppConfig/AppConfigResult.cs | 37 +
.../ParametersManager.cs | 35 +
libraries/src/Directory.Packages.props | 4 +-
.../AppConfig/AppConfigProviderTest.cs | 1157 +++++++++++++++++
12 files changed, 2275 insertions(+), 5 deletions(-)
create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProviderConfigurationBuilder.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/IAppConfigProvider.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigDictionaryTransformer.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigJsonConfigurationParser.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderCacheHelper.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderConfiguration.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigResult.cs
create mode 100644 libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/AWS.Lambda.Powertools.Parameters.csproj b/libraries/src/AWS.Lambda.Powertools.Parameters/AWS.Lambda.Powertools.Parameters.csproj
index ba1020ce4..fda9cc023 100644
--- a/libraries/src/AWS.Lambda.Powertools.Parameters/AWS.Lambda.Powertools.Parameters.csproj
+++ b/libraries/src/AWS.Lambda.Powertools.Parameters/AWS.Lambda.Powertools.Parameters.csproj
@@ -13,10 +13,12 @@
-
-
-
-
+
+
+
+
+
+
diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs
new file mode 100644
index 000000000..951e7e299
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs
@@ -0,0 +1,482 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System.Collections.Concurrent;
+using Amazon;
+using Amazon.AppConfigData;
+using Amazon.AppConfigData.Model;
+using Amazon.Runtime;
+using AWS.Lambda.Powertools.Parameters.Internal.AppConfig;
+using AWS.Lambda.Powertools.Parameters.Cache;
+using AWS.Lambda.Powertools.Parameters.Configuration;
+using AWS.Lambda.Powertools.Parameters.Internal.Cache;
+using AWS.Lambda.Powertools.Parameters.Provider;
+
+namespace AWS.Lambda.Powertools.Parameters.AppConfig;
+
+///
+/// The AppConfigProvider to retrieve parameter values from a AWS AppConfig.
+///
+public class AppConfigProvider : ParameterProvider, IAppConfigProvider
+{
+ ///
+ /// The default application Id.
+ ///
+ private string _defaultApplicationId = string.Empty;
+
+ ///
+ /// The default environment Id.
+ ///
+ private string _defaultEnvironmentId = string.Empty;
+
+ ///
+ /// The default configuration profile Id.
+ ///
+ private string _defaultConfigProfileId = string.Empty;
+
+ ///
+ /// Instance of datetime wrapper.
+ ///
+ private readonly IDateTimeWrapper _dateTimeWrapper;
+
+ ///
+ /// Thread safe dictionary to store results.
+ ///
+ private readonly ConcurrentDictionary _results = new(StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// The client instance.
+ ///
+ private IAmazonAppConfigData? _client;
+
+ ///
+ /// Gets the client instance.
+ ///
+ private IAmazonAppConfigData Client => _client ??= new AmazonAppConfigDataClient();
+
+ ///
+ /// AppConfigProvider constructor.
+ ///
+ public AppConfigProvider()
+ {
+ _dateTimeWrapper = DateTimeWrapper.Instance;
+ }
+
+ ///
+ /// AppConfigProvider constructor for test.
+ ///
+ internal AppConfigProvider(
+ IDateTimeWrapper dateTimeWrapper,
+ string? appConfigResultKey = null,
+ AppConfigResult? appConfigResult = null)
+ {
+ _dateTimeWrapper = dateTimeWrapper;
+ if (appConfigResultKey is not null && appConfigResult is not null)
+ _results.TryAdd(appConfigResultKey, appConfigResult);
+ }
+
+ #region IParameterProviderConfigurableClient implementation
+
+ ///
+ /// Use a custom client
+ ///
+ /// The custom client
+ /// Provider instance
+ public IAppConfigProvider UseClient(IAmazonAppConfigData client)
+ {
+ _client = client;
+ return this;
+ }
+
+ ///
+ /// Configure client with the credentials loaded from the application's default configuration.
+ ///
+ /// The region to connect.
+ /// Provider instance
+ public IAppConfigProvider ConfigureClient(RegionEndpoint region)
+ {
+ _client = new AmazonAppConfigDataClient(region);
+ return this;
+ }
+
+ ///
+ /// Configure client with the credentials loaded from the application's default configuration.
+ ///
+ /// The client configuration object.
+ /// Provider instance
+ public IAppConfigProvider ConfigureClient(AmazonAppConfigDataConfig config)
+ {
+ _client = new AmazonAppConfigDataClient(config);
+ return this;
+ }
+
+ ///
+ /// Configure client with AWS credentials.
+ ///
+ /// AWS credentials.
+ /// Provider instance
+ public IAppConfigProvider ConfigureClient(AWSCredentials credentials)
+ {
+ _client = new AmazonAppConfigDataClient(credentials);
+ return this;
+ }
+
+ ///
+ /// Configure client with AWS credentials.
+ ///
+ /// AWS credentials.
+ /// The region to connect.
+ /// Provider instance
+ public IAppConfigProvider ConfigureClient(AWSCredentials credentials, RegionEndpoint region)
+ {
+ _client = new AmazonAppConfigDataClient(credentials, region);
+ return this;
+ }
+
+ ///
+ /// Configure client with AWS credentials and a client configuration object.
+ ///
+ /// AWS credentials.
+ /// The client configuration object.
+ /// Provider instance
+ public IAppConfigProvider ConfigureClient(AWSCredentials credentials, AmazonAppConfigDataConfig config)
+ {
+ _client = new AmazonAppConfigDataClient(credentials, config);
+ return this;
+ }
+
+ ///
+ /// Configure client with AWS Access Key ID and AWS Secret Key.
+ ///
+ /// AWS Access Key ID
+ /// AWS Secret Access Key
+ /// Provider instance
+ public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecretAccessKey)
+ {
+ _client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey);
+ return this;
+ }
+
+ ///
+ /// Configure client with AWS Access Key ID and AWS Secret Key.
+ ///
+ /// AWS Access Key ID
+ /// AWS Secret Access Key
+ /// The region to connect.
+ /// Provider instance
+ public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecretAccessKey, RegionEndpoint region)
+ {
+ _client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey, region);
+ return this;
+ }
+
+ ///
+ /// Configure client with AWS Access Key ID and AWS Secret Key and a client configuration object.
+ ///
+ /// AWS Access Key ID
+ /// AWS Secret Access Key
+ /// The client configuration object.
+ /// Provider instance
+ public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecretAccessKey,
+ AmazonAppConfigDataConfig config)
+ {
+ _client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey, config);
+ return this;
+ }
+
+ ///
+ /// Configure client with AWS Access Key ID and AWS Secret Key.
+ ///
+ /// AWS Access Key ID
+ /// AWS Secret Access Key
+ /// AWS Session Token
+ /// Provider instance
+ public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecretAccessKey, string awsSessionToken)
+ {
+ _client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey, awsSessionToken);
+ return this;
+ }
+
+ ///
+ /// Configure client with AWS Access Key ID and AWS Secret Key.
+ ///
+ /// AWS Access Key ID
+ /// AWS Secret Access Key
+ /// AWS Session Token
+ /// The region to connect.
+ /// Provider instance
+ public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecretAccessKey, string awsSessionToken,
+ RegionEndpoint region)
+ {
+ _client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey, awsSessionToken, region);
+ return this;
+ }
+
+ ///
+ /// Configure client with AWS Access Key ID and AWS Secret Key and a client configuration object.
+ ///
+ /// AWS Access Key ID
+ /// AWS Secret Access Key
+ /// AWS Session Token
+ /// The client configuration object.
+ /// Provider instance
+ public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecretAccessKey, string awsSessionToken,
+ AmazonAppConfigDataConfig config)
+ {
+ _client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey, awsSessionToken, config);
+ return this;
+ }
+
+ #endregion
+
+ ///
+ /// Sets the default application ID or name.
+ ///
+ /// The application ID or name.
+ /// The AppConfigProvider instance.
+ public IAppConfigProvider DefaultApplication(string applicationId)
+ {
+ if (string.IsNullOrWhiteSpace(applicationId))
+ throw new ArgumentNullException(nameof(applicationId));
+ _defaultApplicationId = applicationId;
+ return this;
+ }
+
+ ///
+ /// Sets the default environment ID or name.
+ ///
+ /// The environment ID or name.
+ /// The AppConfigProvider instance.
+ public IAppConfigProvider DefaultEnvironment(string environmentId)
+ {
+ if (string.IsNullOrWhiteSpace(environmentId))
+ throw new ArgumentNullException(nameof(environmentId));
+ _defaultEnvironmentId = environmentId;
+ return this;
+ }
+
+ ///
+ /// Sets the default configuration profile ID or name.
+ ///
+ /// The configuration profile ID or name.
+ /// The AppConfigProvider instance.
+ public IAppConfigProvider DefaultConfigProfile(string configProfileId)
+ {
+ _defaultConfigProfileId = configProfileId;
+ return this;
+ }
+
+ ///
+ /// Sets the application ID or name.
+ ///
+ /// The application ID or name.
+ /// The AppConfigProvider configuration builder.
+ public AppConfigProviderConfigurationBuilder WithApplication(string applicationId)
+ {
+ return NewConfigurationBuilder().WithApplication(applicationId);
+ }
+
+ ///
+ /// Sets the environment ID or name.
+ ///
+ /// The environment ID or name.
+ /// The AppConfigProvider configuration builder.
+ public AppConfigProviderConfigurationBuilder WithEnvironment(string environmentId)
+ {
+ return NewConfigurationBuilder().WithEnvironment(environmentId);
+ }
+
+ ///
+ /// Sets the configuration profile ID or name.
+ ///
+ /// The configuration profile ID or name.
+ /// The AppConfigProvider configuration builder.
+ public AppConfigProviderConfigurationBuilder WithConfigProfile(string configProfileId)
+ {
+ return NewConfigurationBuilder().WithConfigProfile(configProfileId);
+ }
+
+ ///
+ /// Creates and configures a new AppConfigProviderConfigurationBuilder
+ ///
+ ///
+ protected override AppConfigProviderConfigurationBuilder NewConfigurationBuilder()
+ {
+ return new AppConfigProviderConfigurationBuilder(this)
+ .WithApplication(_defaultApplicationId)
+ .WithEnvironment(_defaultEnvironmentId)
+ .WithConfigProfile(_defaultConfigProfileId);
+
+ }
+
+ ///
+ /// Get AppConfig transformed value for the provided key.
+ ///
+ /// The parameter key.
+ /// Target transformation type.
+ /// The AppConfig transformed value.
+ public override async Task GetAsync(string key) where T : class
+ {
+ return await NewConfigurationBuilder()
+ .GetAsync(key)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Get last AppConfig value.
+ ///
+ /// Application Configuration.
+ public IDictionary Get()
+ {
+ return GetAsync().GetAwaiter().GetResult();
+ }
+
+ ///
+ /// Get last AppConfig value.
+ ///
+ /// The AppConfig value.
+ public async Task> GetAsync()
+ {
+ return await NewConfigurationBuilder()
+ .GetAsync()
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Get last AppConfig value and transform it to JSON value.
+ ///
+ /// JSON value type.
+ /// The AppConfig JSON value.
+ public T? Get() where T: class
+ {
+ return GetAsync().GetAwaiter().GetResult();
+ }
+
+ ///
+ /// Get last AppConfig value and transform it to JSON value.
+ ///
+ /// JSON value type.
+ /// The AppConfig JSON value.
+ public async Task GetAsync() where T: class
+ {
+ return await NewConfigurationBuilder()
+ .GetAsync()
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Get parameter value for the provided key.
+ ///
+ /// The parameter key.
+ /// The parameter provider configuration
+ /// The parameter value.
+ protected override async Task GetAsync(string key, ParameterProviderConfiguration? config)
+ {
+ if(config is not AppConfigProviderConfiguration configuration)
+ throw new ArgumentNullException(nameof(config));
+
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey
+ (
+ configuration.ApplicationId,
+ configuration.EnvironmentId,
+ configuration.ConfigProfileId
+ );
+
+ var result = GetAppConfigResult(cacheKey);
+
+ if (_dateTimeWrapper.UtcNow < result.NextAllowedPollTime)
+ {
+ if (!config.ForceFetch)
+ return result.LastConfig;
+
+ result.PollConfigurationToken = string.Empty;
+ result.NextAllowedPollTime = DateTime.MinValue;
+ }
+
+ if (string.IsNullOrWhiteSpace(result.PollConfigurationToken))
+ result.PollConfigurationToken =
+ await GetInitialConfigurationTokenAsync(configuration)
+ .ConfigureAwait(false);
+
+ var request = new GetLatestConfigurationRequest
+ {
+ ConfigurationToken = result.PollConfigurationToken
+ };
+
+ var response =
+ await Client.GetLatestConfigurationAsync(request)
+ .ConfigureAwait(false);
+
+ result.PollConfigurationToken = response.NextPollConfigurationToken;
+ result.NextAllowedPollTime = _dateTimeWrapper.UtcNow.AddSeconds(response.NextPollIntervalInSeconds);
+
+ if (!string.Equals(response.ContentType, "application/json", StringComparison.CurrentCultureIgnoreCase))
+ throw new NotImplementedException($"Not implemented AppConfig type: {response.ContentType}");
+
+ using (var reader = new StreamReader(response.Configuration))
+ {
+ result.LastConfig =
+ await reader.ReadToEndAsync()
+ .ConfigureAwait(false);
+ }
+
+ return result.LastConfig;
+ }
+
+ ///
+ /// Get multiple parameter values for the provided key.
+ ///
+ /// The parameter key.
+ /// The parameter provider configuration
+ /// Returns a collection parameter key/value pairs.
+ protected override Task> GetMultipleAsync(string key,
+ ParameterProviderConfiguration? config)
+ {
+ throw new NotSupportedException("Impossible to get multiple values from AWS AppConfig");
+ }
+
+ ///
+ /// Gets Or Adds AppConfigResult with provided key
+ ///
+ /// The cache key
+ /// AppConfigResult
+ private AppConfigResult GetAppConfigResult(string cacheKey)
+ {
+ if (_results.TryGetValue(cacheKey, out var cachedResult))
+ return cachedResult;
+
+ cachedResult = new AppConfigResult();
+ _results.TryAdd(cacheKey, cachedResult);
+
+ return cachedResult;
+ }
+
+ ///
+ /// Starts a configuration session used to retrieve a deployed configuration.
+ ///
+ /// Teh AppConfig provider configuration
+ /// The initial configuration token
+ private async Task GetInitialConfigurationTokenAsync(AppConfigProviderConfiguration config)
+ {
+ var request = new StartConfigurationSessionRequest
+ {
+ ApplicationIdentifier = config.ApplicationId,
+ EnvironmentIdentifier = config.EnvironmentId,
+ ConfigurationProfileIdentifier = config.ConfigProfileId
+ };
+
+ return (await Client.StartConfigurationSessionAsync(request).ConfigureAwait(false)).InitialConfigurationToken;
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProviderConfigurationBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProviderConfigurationBuilder.cs
new file mode 100644
index 000000000..87df95152
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProviderConfigurationBuilder.cs
@@ -0,0 +1,167 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using AWS.Lambda.Powertools.Parameters.Internal.AppConfig;
+using AWS.Lambda.Powertools.Parameters.Configuration;
+using AWS.Lambda.Powertools.Parameters.Provider;
+using AWS.Lambda.Powertools.Parameters.Transform;
+
+namespace AWS.Lambda.Powertools.Parameters.AppConfig;
+
+///
+/// AppConfigProviderConfigurationBuilder class.
+///
+public class AppConfigProviderConfigurationBuilder : ParameterProviderConfigurationBuilder
+{
+ ///
+ /// The application Id.
+ ///
+ private string? _applicationId;
+
+ ///
+ /// The environment Id.
+ ///
+ private string? _environmentId;
+
+ ///
+ /// The configuration profile Id.
+ ///
+ private string? _configProfileId;
+
+ ///
+ /// AppConfigProviderConfigurationBuilder constructor
+ ///
+ /// The AppConfigProvider instance
+ public AppConfigProviderConfigurationBuilder(ParameterProvider parameterProvider) :
+ base(parameterProvider)
+ {
+ }
+
+ ///
+ /// Sets the application ID or name.
+ ///
+ /// The application ID or name.
+ /// The AppConfigProvider configuration builder.
+ public AppConfigProviderConfigurationBuilder WithApplication(string applicationId)
+ {
+ _applicationId = applicationId;
+ return this;
+ }
+
+ ///
+ /// Sets the environment ID or name.
+ ///
+ /// The environment ID or name.
+ /// The AppConfigProvider configuration builder.
+ public AppConfigProviderConfigurationBuilder WithEnvironment(string environmentId)
+ {
+ _environmentId = environmentId;
+ return this;
+ }
+
+ ///
+ /// Sets the configuration profile ID or name.
+ ///
+ /// The configuration profile ID or name.
+ /// The AppConfigProvider configuration builder.
+ public AppConfigProviderConfigurationBuilder WithConfigProfile(string configProfileId)
+ {
+ _configProfileId = configProfileId;
+ return this;
+ }
+
+ ///
+ /// Creates and configures new AppConfigProviderConfiguration instance.
+ ///
+ ///
+ protected override ParameterProviderConfiguration NewConfiguration()
+ {
+ return new AppConfigProviderConfiguration
+ {
+ EnvironmentId = _environmentId,
+ ApplicationId = _applicationId,
+ ConfigProfileId = _configProfileId
+ };
+ }
+
+ ///
+ /// Get AppConfig transformed value for the provided key.
+ ///
+ /// The parameter key.
+ /// Target transformation type.
+ /// The AppConfig transformed value.
+ public override async Task GetAsync(string key) where T : class
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ return default;
+
+ if (typeof(T) != typeof(string))
+ return default;
+
+ var dictionary = await GetAsync().ConfigureAwait(false);
+ if (dictionary.TryGetValue(key, out var value) && value != null)
+ return (T)(object)value;
+
+ return default;
+ }
+
+ ///
+ /// Get last AppConfig value.
+ ///
+ /// Application Configuration.
+ public IDictionary Get()
+ {
+ return GetAsync().GetAwaiter().GetResult();
+ }
+
+ ///
+ /// Get last AppConfig value.
+ ///
+ /// The AppConfig value.
+ public async Task> GetAsync()
+ {
+ return await GetAsync>().ConfigureAwait(false) ??
+ new Dictionary();
+ }
+
+ ///
+ /// Get last AppConfig value and transform it to JSON value.
+ ///
+ /// JSON value type.
+ /// The AppConfig JSON value.
+ public T? Get() where T : class
+ {
+ return GetAsync().GetAwaiter().GetResult();
+ }
+
+ ///
+ /// Get last AppConfig value and transform it to JSON value.
+ ///
+ /// JSON value type.
+ /// The AppConfig JSON value.
+ public async Task GetAsync() where T : class
+ {
+ if (!HasTransformation)
+ {
+ if (typeof(T) == typeof(IDictionary))
+ SetTransformer(AppConfigDictionaryTransformer.Instance);
+ else
+ SetTransformation(Transformation.Json);
+ }
+
+ return await base.GetAsync(AppConfigProviderCacheHelper.GetCacheKey(_applicationId, _environmentId,
+ _configProfileId)).ConfigureAwait(false);
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/IAppConfigProvider.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/IAppConfigProvider.cs
new file mode 100644
index 000000000..32debbea3
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/IAppConfigProvider.cs
@@ -0,0 +1,81 @@
+using Amazon.AppConfigData;
+using AWS.Lambda.Powertools.Parameters.Internal.Provider;
+using AWS.Lambda.Powertools.Parameters.Provider;
+
+namespace AWS.Lambda.Powertools.Parameters.AppConfig;
+
+///
+/// Represents a type used to retrieve parameter values from a AWS AppConfig.
+///
+public interface IAppConfigProvider : IParameterProvider,
+ IParameterProviderConfigurableClient
+{
+ ///
+ /// Sets the default application ID or name.
+ ///
+ /// The application ID or name.
+ /// The AppConfigProvider instance.
+ IAppConfigProvider DefaultApplication(string applicationId);
+
+ ///
+ /// Sets the default environment ID or name.
+ ///
+ /// The environment ID or name.
+ /// The AppConfigProvider instance.
+ IAppConfigProvider DefaultEnvironment(string environmentId);
+
+ ///
+ /// Sets the default configuration profile ID or name.
+ ///
+ /// The configuration profile ID or name.
+ /// The AppConfigProvider instance.
+ IAppConfigProvider DefaultConfigProfile(string configProfileId);
+
+ ///
+ /// Sets the application ID or name.
+ ///
+ /// The application ID or name.
+ /// The AppConfigProvider configuration builder.
+ AppConfigProviderConfigurationBuilder WithApplication(string applicationId);
+
+ ///
+ /// Sets the environment ID or name.
+ ///
+ /// The environment ID or name.
+ /// The AppConfigProvider configuration builder.
+ AppConfigProviderConfigurationBuilder WithEnvironment(string environmentId);
+
+ ///
+ /// Sets the configuration profile ID or name.
+ ///
+ /// The configuration profile ID or name.
+ /// The AppConfigProvider configuration builder.
+ AppConfigProviderConfigurationBuilder WithConfigProfile(string configProfileId);
+
+ ///
+ /// Get last AppConfig value.
+ ///
+ /// Application Configuration.
+ IDictionary Get();
+
+ ///
+ /// Get last AppConfig value.
+ ///
+ /// The AppConfig value.
+ Task> GetAsync();
+
+ ///
+ /// Get last AppConfig value and transform it to JSON value.
+ ///
+ /// JSON value type.
+ /// The AppConfig JSON value.
+ T? Get() where T : class;
+
+ ///
+ /// Get last AppConfig value and transform it to JSON value.
+ ///
+ /// JSON value type.
+ /// The AppConfig JSON value.
+ Task GetAsync() where T : class;
+}
+
diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigDictionaryTransformer.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigDictionaryTransformer.cs
new file mode 100644
index 000000000..021cc5725
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigDictionaryTransformer.cs
@@ -0,0 +1,62 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using AWS.Lambda.Powertools.Parameters.Transform;
+
+namespace AWS.Lambda.Powertools.Parameters.Internal.AppConfig;
+
+///
+/// Transformer to deserialize dictionary from JSON string.
+///
+internal class AppConfigDictionaryTransformer : ITransformer
+{
+ ///
+ /// The transformer instance.
+ ///
+ private static AppConfigDictionaryTransformer? _instance;
+
+ ///
+ /// Gets the transformer instance.
+ ///
+ internal static AppConfigDictionaryTransformer Instance => _instance ??= new AppConfigDictionaryTransformer();
+
+ ///
+ /// AppConfigDictionaryTransformer constructor.
+ ///
+ private AppConfigDictionaryTransformer()
+ {
+
+ }
+
+ ///
+ /// Deserialize a dictionary from a JSON string.
+ ///
+ /// JSON string.
+ /// JSON value type.
+ /// Key/value pair collection.
+ public T? Transform(string value)
+ {
+ if (typeof(T) == typeof(string))
+ return (T)(object)value;
+
+ if (string.IsNullOrWhiteSpace(value))
+ return default;
+
+ if (typeof(T) != typeof(IDictionary))
+ return default;
+
+ return (T)AppConfigJsonConfigurationParser.Parse(value);
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigJsonConfigurationParser.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigJsonConfigurationParser.cs
new file mode 100644
index 000000000..b84ce03d7
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigJsonConfigurationParser.cs
@@ -0,0 +1,164 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System.Globalization;
+using System.Text.Json;
+using Microsoft.Extensions.Configuration;
+
+namespace AWS.Lambda.Powertools.Parameters.Internal.AppConfig;
+
+///
+/// AppConfigJsonConfigurationParser class
+///
+internal class AppConfigJsonConfigurationParser
+{
+ ///
+ /// The processed data.
+ ///
+ private readonly IDictionary _data =
+ new SortedDictionary(StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// Stack for processing the document.
+ ///
+ private readonly Stack _context = new();
+
+ ///
+ /// Pointer to the current path.
+ ///
+ private string _currentPath = string.Empty;
+
+ ///
+ /// Parse dictionary from AppConfig JSON stream.
+ ///
+ /// AppConfig JSON stream.
+ /// JSON Dictionary.
+ public static IDictionary Parse(Stream input)
+ {
+ using var doc = JsonDocument.Parse(input);
+ var parser = new AppConfigJsonConfigurationParser();
+ parser.VisitElement(doc.RootElement);
+ return parser._data;
+ }
+
+ ///
+ /// Parse dictionary from AppConfig JSON string.
+ ///
+ /// AppConfig JSON string.
+ /// JSON Dictionary.
+ public static IDictionary Parse(string input)
+ {
+ using var doc = JsonDocument.Parse(input);
+ var parser = new AppConfigJsonConfigurationParser();
+ parser.VisitElement(doc.RootElement);
+ return parser._data;
+ }
+
+ ///
+ /// Process single JSON element.
+ ///
+ /// The JSON element
+ private void VisitElement(JsonElement element)
+ {
+ switch (element.ValueKind)
+ {
+ case JsonValueKind.Undefined:
+ break;
+ case JsonValueKind.Object:
+ foreach (var property in element.EnumerateObject())
+ {
+ EnterContext(property.Name);
+ VisitElement(property.Value);
+ ExitContext();
+ }
+
+ break;
+ case JsonValueKind.Array:
+ VisitArray(element);
+ break;
+ case JsonValueKind.String:
+ case JsonValueKind.Number:
+ case JsonValueKind.True:
+ case JsonValueKind.False:
+ VisitPrimitive(element);
+ break;
+ case JsonValueKind.Null:
+ VisitNull();
+ break;
+ }
+ }
+
+ ///
+ /// Process array JSON element.
+ ///
+ /// The JSON array
+ private void VisitArray(JsonElement array)
+ {
+ var index = 0;
+ foreach (var item in array.EnumerateArray())
+ {
+ EnterContext(index.ToString(CultureInfo.InvariantCulture));
+ VisitElement(item);
+ ExitContext();
+
+ index++;
+ }
+ }
+
+ ///
+ /// Process JSON null element.
+ ///
+ private void VisitNull()
+ {
+ var key = _currentPath;
+ _data[key] = null;
+ }
+
+ ///
+ /// Process JSON primitive element.
+ ///
+ /// The JSON element.
+ ///
+ private void VisitPrimitive(JsonElement data)
+ {
+ var key = _currentPath;
+
+ if (_data.ContainsKey(key))
+ {
+ throw new FormatException($"A duplicate key '{key}' was found.");
+ }
+
+ _data[key] = data.ToString();
+ }
+
+ ///
+ /// Enter into a context of a new element to process.
+ ///
+ /// The context
+ private void EnterContext(string context)
+ {
+ _context.Push(context);
+ _currentPath = ConfigurationPath.Combine(_context.Reverse());
+ }
+
+ ///
+ /// Enter from context of an element which is processed.
+ ///
+ private void ExitContext()
+ {
+ _context.Pop();
+ _currentPath = ConfigurationPath.Combine(_context.Reverse());
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderCacheHelper.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderCacheHelper.cs
new file mode 100644
index 000000000..acbfe0b19
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderCacheHelper.cs
@@ -0,0 +1,42 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+namespace AWS.Lambda.Powertools.Parameters.Internal.AppConfig;
+
+///
+/// AppConfigProviderCacheHelper class.
+///
+internal static class AppConfigProviderCacheHelper
+{
+ ///
+ /// Gets a new key for caching from provided inputs.
+ ///
+ /// The application Id.
+ /// The environment Id.
+ /// the configuration profile Id.
+ /// The cache key
+ ///
+ internal static string GetCacheKey(string? applicationId, string? environmentId, string? configProfileId)
+ {
+ if (string.IsNullOrWhiteSpace(applicationId))
+ throw new ArgumentNullException(nameof(applicationId));
+ if (string.IsNullOrWhiteSpace(environmentId))
+ throw new ArgumentNullException(nameof(environmentId));
+ if (string.IsNullOrWhiteSpace(configProfileId))
+ throw new ArgumentNullException(nameof(configProfileId));
+
+ return $"{applicationId}_{environmentId}_{configProfileId}";
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderConfiguration.cs
new file mode 100644
index 000000000..9d6380581
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderConfiguration.cs
@@ -0,0 +1,39 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using AWS.Lambda.Powertools.Parameters.Configuration;
+
+namespace AWS.Lambda.Powertools.Parameters.Internal.AppConfig;
+
+///
+/// AppConfigProviderConfiguration class.
+///
+internal class AppConfigProviderConfiguration : ParameterProviderConfiguration
+{
+ ///
+ /// The application Id.
+ ///
+ internal string? ApplicationId { get; set; }
+
+ ///
+ /// The environment Id.
+ ///
+ internal string? EnvironmentId { get; set; }
+
+ ///
+ /// The configuration profile Id.
+ ///
+ internal string? ConfigProfileId { get; set; }
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigResult.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigResult.cs
new file mode 100644
index 000000000..61467e19c
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigResult.cs
@@ -0,0 +1,37 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+namespace AWS.Lambda.Powertools.Parameters.Internal.AppConfig;
+
+///
+/// AppConfigResult class.
+///
+internal class AppConfigResult
+{
+ ///
+ /// Token for polling the configuration.
+ ///
+ internal string PollConfigurationToken { get; set; } = string.Empty;
+
+ ///
+ /// Next time poll is allowed.
+ ///
+ internal DateTime NextAllowedPollTime { get; set; } = DateTime.MinValue;
+
+ ///
+ /// Last configuration value
+ ///
+ internal string? LastConfig { get; set; } = null;
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/ParametersManager.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/ParametersManager.cs
index 999152108..456de66db 100644
--- a/libraries/src/AWS.Lambda.Powertools.Parameters/ParametersManager.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Parameters/ParametersManager.cs
@@ -13,6 +13,8 @@
* permissions and limitations under the License.
*/
+
+using AWS.Lambda.Powertools.Parameters.AppConfig;
using AWS.Lambda.Powertools.Parameters.Cache;
using AWS.Lambda.Powertools.Parameters.DynamoDB;
using AWS.Lambda.Powertools.Parameters.Internal.Cache;
@@ -43,6 +45,11 @@ public static class ParametersManager
/// The DynamoDBProvider instance
///
private static IDynamoDBProvider? _dynamoDBProvider;
+
+ ///
+ /// The AppConfigProvider instance
+ ///
+ private static IAppConfigProvider? _appConfigProvider;
///
/// The CacheManager instance
@@ -89,6 +96,12 @@ public static class ParametersManager
/// The DynamoDBProvider instance.
public static IDynamoDBProvider DynamoDBProvider => _dynamoDBProvider ??= CreateDynamoDBProvider();
+ ///
+ /// Gets the AppConfigProvider instance.
+ ///
+ /// The AppConfigProvider instance.
+ public static IAppConfigProvider AppConfigProvider => _appConfigProvider ??= CreateAppConfigProvider();
+
///
/// Set the caching default maximum age for all providers.
///
@@ -104,6 +117,7 @@ public static void DefaultMaxAge(TimeSpan maxAge)
_ssmProvider?.DefaultMaxAge(maxAge);
_secretsProvider?.DefaultMaxAge(maxAge);
_dynamoDBProvider?.DefaultMaxAge(maxAge);
+ _appConfigProvider?.DefaultMaxAge(maxAge);
}
///
@@ -116,6 +130,7 @@ public static void UseCacheManager(ICacheManager cacheManager)
_ssmProvider?.UseCacheManager(cacheManager);
_secretsProvider?.UseCacheManager(cacheManager);
_dynamoDBProvider?.UseCacheManager(cacheManager);
+ _appConfigProvider?.UseCacheManager(cacheManager);
}
///
@@ -128,6 +143,7 @@ public static void UseTransformerManager(ITransformerManager transformerManager)
_ssmProvider?.UseTransformerManager(transformerManager);
_secretsProvider?.UseTransformerManager(transformerManager);
_dynamoDBProvider?.UseTransformerManager(transformerManager);
+ _appConfigProvider?.UseTransformerManager(transformerManager);
}
///
@@ -141,6 +157,7 @@ public static void AddTransformer(string name, ITransformer transformer)
_ssmProvider?.AddTransformer(name, transformer);
_secretsProvider?.AddTransformer(name, transformer);
_dynamoDBProvider?.AddTransformer(name, transformer);
+ _appConfigProvider?.AddTransformer(name, transformer);
}
///
@@ -151,6 +168,7 @@ public static void RaiseTransformationError()
_ssmProvider?.RaiseTransformationError();
_secretsProvider?.RaiseTransformationError();
_dynamoDBProvider?.RaiseTransformationError();
+ _appConfigProvider?.RaiseTransformationError();
}
///
@@ -162,6 +180,7 @@ public static void RaiseTransformationError(bool raiseError)
_ssmProvider?.RaiseTransformationError(raiseError);
_secretsProvider?.RaiseTransformationError(raiseError);
_dynamoDBProvider?.RaiseTransformationError(raiseError);
+ _appConfigProvider?.RaiseTransformationError(raiseError);
}
///
@@ -211,4 +230,20 @@ public static IDynamoDBProvider CreateDynamoDBProvider()
return provider;
}
+
+ ///
+ /// Create a new instance of AppConfigProvider.
+ ///
+ /// The AppConfigProvider instance.
+ public static IAppConfigProvider CreateAppConfigProvider()
+ {
+ var provider = new AppConfigProvider()
+ .UseCacheManager(Cache)
+ .UseTransformerManager(TransformManager);
+
+ if (_defaultMaxAge.HasValue)
+ provider = provider.DefaultMaxAge(_defaultMaxAge.Value);
+
+ return provider;
+ }
}
\ No newline at end of file
diff --git a/libraries/src/Directory.Packages.props b/libraries/src/Directory.Packages.props
index ca1d81f48..cb1ab08f9 100644
--- a/libraries/src/Directory.Packages.props
+++ b/libraries/src/Directory.Packages.props
@@ -3,9 +3,11 @@
true
-
+
+
+
diff --git a/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs b/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs
new file mode 100644
index 000000000..ee9b2c46c
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs
@@ -0,0 +1,1157 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System.Text;
+using System.Text.Json;
+using Amazon.AppConfigData;
+using Amazon.AppConfigData.Model;
+using AWS.Lambda.Powertools.Parameters.AppConfig;
+using AWS.Lambda.Powertools.Parameters.Cache;
+using AWS.Lambda.Powertools.Parameters.Configuration;
+using AWS.Lambda.Powertools.Parameters.Internal.AppConfig;
+using AWS.Lambda.Powertools.Parameters.Internal.Cache;
+using AWS.Lambda.Powertools.Parameters.Provider;
+using AWS.Lambda.Powertools.Parameters.Internal.Provider;
+using AWS.Lambda.Powertools.Parameters.Transform;
+using NSubstitute;
+using NSubstitute.ReturnsExtensions;
+using Xunit;
+
+namespace AWS.Lambda.Powertools.Parameters.Tests.AppConfig;
+
+public class AppConfigProviderTest
+{
+ [Fact]
+ public async Task GetAsync_SetupProvider_CallsHandler()
+ {
+ var key = Guid.NewGuid().ToString();
+ var value = Guid.NewGuid().ToString();
+ var transformerName = Guid.NewGuid().ToString();
+ var duration = CacheManager.DefaultMaxAge.Add(TimeSpan.FromHours(10));
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var transformer = Substitute.For();
+ var providerHandler = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+ var appConfig = new Dictionary { { key, value } };
+
+ providerHandler
+ .GetAsync>(cacheKey, Arg.Any(), null, null)
+ .Returns(appConfig);
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper);
+ appConfigProvider.SetHandler(providerHandler);
+ appConfigProvider.UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager);
+ appConfigProvider.DefaultMaxAge(duration);
+ appConfigProvider.AddTransformer(transformerName, transformer);
+ appConfigProvider.DefaultApplication(applicationId);
+ appConfigProvider.DefaultEnvironment(environmentId);
+ appConfigProvider.DefaultConfigProfile(configProfileId);
+
+ // Act
+ var result = await appConfigProvider.GetAsync(key);
+
+ // Assert
+ await providerHandler.Received(1).GetAsync>(cacheKey,
+ Arg.Is(x =>
+ x != null && x.ApplicationId == applicationId && x.EnvironmentId == environmentId &&
+ x.ConfigProfileId == configProfileId), null, null);
+ providerHandler.Received(1).SetCacheManager(cacheManager);
+ providerHandler.Received(1).SetTransformerManager(transformerManager);
+ providerHandler.Received(1).SetDefaultMaxAge(duration);
+ providerHandler.Received(1).AddCustomTransformer(transformerName, transformer);
+ Assert.NotNull(result);
+ Assert.Equal(value, result);
+ }
+
+ [Fact]
+ public async Task GetAsync_WhenForceFetch_CallsHandlerWithConfiguredParameters()
+ {
+ // Arrange
+ var key = Guid.NewGuid().ToString();
+ var value = Guid.NewGuid().ToString();
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var providerHandler = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+ var appConfig = new Dictionary { { key, value } };
+
+ providerHandler
+ .GetAsync>(cacheKey, Arg.Any(), null, null)
+ .Returns(appConfig);
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper);
+ appConfigProvider.SetHandler(providerHandler);
+ appConfigProvider.UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager);
+ appConfigProvider.DefaultApplication(applicationId);
+ appConfigProvider.DefaultEnvironment(environmentId);
+ appConfigProvider.DefaultConfigProfile(configProfileId);
+
+ // Act
+ var result = await appConfigProvider
+ .ForceFetch()
+ .GetAsync(key);
+
+ // Assert
+ await providerHandler.Received(1).GetAsync>(cacheKey,
+ Arg.Is(x =>
+ x != null && x.ForceFetch
+ ), null,
+ null);
+ Assert.NotNull(result);
+ Assert.Equal(value, result);
+ }
+
+ [Fact]
+ public async Task GetAsync_WithMaxAge_CallsHandlerWithConfiguredParameters()
+ {
+ // Arrange
+ var key = Guid.NewGuid().ToString();
+ var value = Guid.NewGuid().ToString();
+ var duration = CacheManager.DefaultMaxAge.Add(TimeSpan.FromHours(10));
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var providerHandler = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+ var appConfig = new Dictionary { { key, value } };
+
+ providerHandler
+ .GetAsync>(cacheKey, Arg.Any(), null, null)
+ .Returns(appConfig);
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper);
+ appConfigProvider.SetHandler(providerHandler);
+ appConfigProvider.UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager);
+ appConfigProvider.DefaultApplication(applicationId);
+ appConfigProvider.DefaultEnvironment(environmentId);
+ appConfigProvider.DefaultConfigProfile(configProfileId);
+
+ // Act
+ var result = await appConfigProvider
+ .WithMaxAge(duration)
+ .GetAsync(key);
+
+ // Assert
+ await providerHandler.Received(1).GetAsync>(cacheKey,
+ Arg.Is(x =>
+ x != null && x.MaxAge == duration
+ ), null,
+ null);
+ Assert.NotNull(result);
+ Assert.Equal(value, result);
+ }
+
+ [Fact]
+ public async Task GetAsync_WhenCachedObjectExists_ReturnsCachedObject()
+ {
+ // Arrange
+ var key = Guid.NewGuid().ToString();
+ var valueFromCache = Guid.NewGuid().ToString();
+ var appConfig = new Dictionary { { key, valueFromCache } };
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+
+ client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any())
+ .Returns(new GetLatestConfigurationResponse());
+
+ cacheManager.Get(cacheKey).Returns(appConfig);
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager);
+
+ appConfigProvider.DefaultApplication(applicationId);
+ appConfigProvider.DefaultEnvironment(environmentId);
+ appConfigProvider.DefaultConfigProfile(configProfileId);
+
+ // Act
+ var result = await appConfigProvider.GetAsync(key);
+
+ // Assert
+ await client.DidNotReceiveWithAnyArgs().GetLatestConfigurationAsync(null);
+ Assert.NotNull(result);
+ Assert.Equal(valueFromCache, result);
+ }
+
+ [Fact]
+ public async Task GetAsync_WhenForceFetch_IgnoresCachedObject()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var configurationToken = Guid.NewGuid().ToString();
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+
+ var value = new
+ {
+ Config1 = Guid.NewGuid().ToString(),
+ Config2 = Guid.NewGuid().ToString()
+ };
+
+ var valueFromCache = new Dictionary
+ {
+ { value.Config1, Guid.NewGuid().ToString() },
+ { value.Config2, Guid.NewGuid().ToString() }
+ };
+
+ var response1 = new StartConfigurationSessionResponse
+ {
+ InitialConfigurationToken = configurationToken
+ };
+
+ var contentType = "application/json";
+ var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value));
+ var response2 = new GetLatestConfigurationResponse
+ {
+ Configuration = new MemoryStream(content),
+ ContentType = contentType,
+ ContentLength = content.Length
+ };
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any())
+ .Returns(response1);
+
+ client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any())
+ .Returns(response2);
+
+ cacheManager.Get(cacheKey).Returns(valueFromCache);
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .WithApplication(applicationId)
+ .WithEnvironment(environmentId)
+ .WithConfigProfile(configProfileId);
+
+ // Act
+ var result = await appConfigProvider.ForceFetch().GetAsync();
+
+ // Assert
+ cacheManager.DidNotReceive().Get(cacheKey);
+ await client.Received(1).StartConfigurationSessionAsync(Arg.Is(
+ x => x.ApplicationIdentifier == applicationId &&
+ x.EnvironmentIdentifier == environmentId &&
+ x.ConfigurationProfileIdentifier == configProfileId), Arg.Any());
+ await client.Received(1).GetLatestConfigurationAsync(Arg.Is(
+ x => x.ConfigurationToken == configurationToken), Arg.Any());
+ Assert.NotNull(result);
+ Assert.Equal("Config1", result.First().Key);
+ Assert.Equal(value.Config1, result.First().Value);
+ Assert.Equal("Config2", result.Last().Key);
+ Assert.Equal(value.Config2, result.Last().Value);
+ }
+
+ [Fact]
+ public async Task GetAsync_WhenMaxAgeNotSet_StoresCachedObjectWithDefaultMaxAge()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var configurationToken = Guid.NewGuid().ToString();
+ var duration = CacheManager.DefaultMaxAge;
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+
+ var value = new
+ {
+ Config1 = Guid.NewGuid().ToString(),
+ Config2 = Guid.NewGuid().ToString()
+ };
+
+ var response1 = new StartConfigurationSessionResponse
+ {
+ InitialConfigurationToken = configurationToken
+ };
+
+ var contentType = "application/json";
+ var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value));
+ var response2 = new GetLatestConfigurationResponse
+ {
+ Configuration = new MemoryStream(content),
+ ContentType = contentType,
+ ContentLength = content.Length
+ };
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any())
+ .Returns(response1);
+
+ client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any())
+ .Returns(response2);
+
+ cacheManager.Get(cacheKey).ReturnsNull();
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .WithApplication(applicationId)
+ .WithEnvironment(environmentId)
+ .WithConfigProfile(configProfileId);
+
+ // Act
+ var result = await appConfigProvider.GetAsync();
+
+ // Assert
+ cacheManager.Received(1).Get(cacheKey);
+ cacheManager.Received(1).Set(cacheKey, Arg.Is>(d =>
+ d.First().Key == "Config1" &&
+ d.First().Value == value.Config1 &&
+ d.Last().Key == "Config2" &&
+ d.Last().Value == value.Config2
+ ), duration);
+ Assert.NotNull(result);
+ Assert.Equal("Config1", result.First().Key);
+ Assert.Equal(value.Config1, result.First().Value);
+ Assert.Equal("Config2", result.Last().Key);
+ Assert.Equal(value.Config2, result.Last().Value);
+ }
+
+ [Fact]
+ public async Task GetAsync_WhenMaxAgeClientSet_StoresCachedObjectWithDefaultMaxAge()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var configurationToken = Guid.NewGuid().ToString();
+ var duration = CacheManager.DefaultMaxAge.Add(TimeSpan.FromHours(10));
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+
+ var value = new
+ {
+ Config1 = Guid.NewGuid().ToString(),
+ Config2 = Guid.NewGuid().ToString()
+ };
+
+ var response1 = new StartConfigurationSessionResponse
+ {
+ InitialConfigurationToken = configurationToken
+ };
+
+ var contentType = "application/json";
+ var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value));
+ var response2 = new GetLatestConfigurationResponse
+ {
+ Configuration = new MemoryStream(content),
+ ContentType = contentType,
+ ContentLength = content.Length
+ };
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any())
+ .Returns(response1);
+
+ client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any())
+ .Returns(response2);
+
+ cacheManager.Get(cacheKey).ReturnsNull();
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .WithApplication(applicationId)
+ .WithEnvironment(environmentId)
+ .WithConfigProfile(configProfileId)
+ .WithMaxAge(duration);
+
+ // Act
+ var result = await appConfigProvider.GetAsync();
+
+ // Assert
+ cacheManager.Received(1).Get(cacheKey);
+ cacheManager.Received(1).Set(cacheKey, Arg.Is>(d =>
+ d.First().Key == "Config1" &&
+ d.First().Value == value.Config1 &&
+ d.Last().Key == "Config2" &&
+ d.Last().Value == value.Config2
+ ), duration);
+ Assert.NotNull(result);
+ Assert.Equal("Config1", result.First().Key);
+ Assert.Equal(value.Config1, result.First().Value);
+ Assert.Equal("Config2", result.Last().Key);
+ Assert.Equal(value.Config2, result.Last().Value);
+ }
+
+ [Fact]
+ public async Task GetAsync_WhenMaxAgeSet_StoresCachedObjectWithMaxAge()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var configurationToken = Guid.NewGuid().ToString();
+ var defaultMaxAge = CacheManager.DefaultMaxAge;
+ var duration = defaultMaxAge.Add(TimeSpan.FromHours(10));
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+
+ var value = new
+ {
+ Config1 = Guid.NewGuid().ToString(),
+ Config2 = Guid.NewGuid().ToString()
+ };
+
+ var response1 = new StartConfigurationSessionResponse
+ {
+ InitialConfigurationToken = configurationToken
+ };
+
+ var contentType = "application/json";
+ var jsonStr = JsonSerializer.Serialize(value);
+ var content = Encoding.UTF8.GetBytes(jsonStr);
+ var response2 = new GetLatestConfigurationResponse
+ {
+ Configuration = new MemoryStream(content),
+ ContentType = contentType,
+ ContentLength = content.Length
+ };
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any())
+ .Returns(response1);
+
+ client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any())
+ .Returns(response2);
+
+ cacheManager.Get(cacheKey).ReturnsNull();
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .DefaultMaxAge(defaultMaxAge)
+ .WithApplication(applicationId)
+ .WithEnvironment(environmentId)
+ .WithConfigProfile(configProfileId);
+
+ // Act
+ var result = await appConfigProvider.WithMaxAge(duration).GetAsync();
+
+ // Assert
+ cacheManager.Received(1).Get(cacheKey);
+ cacheManager.Received(1).Set(cacheKey, Arg.Is>(d =>
+ d.First().Key == "Config1" &&
+ d.First().Value == value.Config1 &&
+ d.Last().Key == "Config2" &&
+ d.Last().Value == value.Config2
+ ), duration);
+ Assert.NotNull(result);
+ Assert.Equal("Config1", result.First().Key);
+ Assert.Equal(value.Config1, result.First().Value);
+ Assert.Equal("Config2", result.Last().Key);
+ Assert.Equal(value.Config2, result.Last().Value);
+ }
+
+ [Fact]
+ public async Task GetAsync_WhenKeyExists_ReturnsKeyValue()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var configurationToken = Guid.NewGuid().ToString();
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+
+ var value = new
+ {
+ Config1 = Guid.NewGuid().ToString(),
+ Config2 = Guid.NewGuid().ToString()
+ };
+
+ var response1 = new StartConfigurationSessionResponse
+ {
+ InitialConfigurationToken = configurationToken
+ };
+
+ var contentType = "application/json";
+ var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value));
+ var response2 = new GetLatestConfigurationResponse
+ {
+ Configuration = new MemoryStream(content),
+ ContentType = contentType,
+ ContentLength = content.Length
+ };
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any())
+ .Returns(response1);
+
+ client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any())
+ .Returns(response2);
+
+ cacheManager.Get(cacheKey).ReturnsNull();
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .WithApplication(applicationId)
+ .WithEnvironment(environmentId)
+ .WithConfigProfile(configProfileId);
+
+ // Act
+ var result = await appConfigProvider.GetAsync("Config1");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(value.Config1, result);
+ }
+
+ [Fact]
+ public async Task GetAsync_WhenKeyDoesNotExist_ReturnsNull()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var configurationToken = Guid.NewGuid().ToString();
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+
+ var value = new
+ {
+ Config1 = Guid.NewGuid().ToString(),
+ Config2 = Guid.NewGuid().ToString()
+ };
+
+ var response1 = new StartConfigurationSessionResponse
+ {
+ InitialConfigurationToken = configurationToken
+ };
+
+ var contentType = "application/json";
+ var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value));
+ var response2 = new GetLatestConfigurationResponse
+ {
+ Configuration = new MemoryStream(content),
+ ContentType = contentType,
+ ContentLength = content.Length
+ };
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any())
+ .Returns(response1);
+
+ client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any())
+ .Returns(response2);
+
+ cacheManager.Get(cacheKey).ReturnsNull();
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .WithApplication(applicationId)
+ .WithEnvironment(environmentId)
+ .WithConfigProfile(configProfileId);
+
+ // Act
+ var result = await appConfigProvider.GetAsync("Config3");
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public async Task GetAsync_DefaultApplicationIdDoesNotSet_ThrowsException()
+ {
+ // Arrange
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .DefaultEnvironment(environmentId)
+ .DefaultConfigProfile(configProfileId);
+
+ // Act
+ Task> Act() => appConfigProvider.GetAsync();
+
+ // Assert
+ await Assert.ThrowsAsync(Act);
+ }
+
+ [Fact]
+ public async Task GetAsync_DefaultEnvironmentIdDoesNotSet_ThrowsException()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .DefaultApplication(applicationId)
+ .DefaultConfigProfile(configProfileId);
+
+ // Act
+ Task> Act() => appConfigProvider.GetAsync();
+
+ // Assert
+ await Assert.ThrowsAsync(Act);
+ }
+
+ [Fact]
+ public async Task GetAsync_DefaultConfigProfileIdDoesNotSet_ThrowsException()
+ {
+ // Arrange
+ var environmentId = Guid.NewGuid().ToString();
+ var applicationId = Guid.NewGuid().ToString();
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .DefaultApplication(applicationId)
+ .DefaultEnvironment(environmentId);
+
+ // Act
+ Task> Act() => appConfigProvider.GetAsync();
+
+ // Assert
+ await Assert.ThrowsAsync(Act);
+ }
+
+ [Fact]
+ public async Task GetAsync_WhenApplicationIdDoesNotSet_ThrowsException()
+ {
+ // Arrange
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .WithEnvironment(environmentId)
+ .WithConfigProfile(configProfileId);
+
+ // Act
+ Task> Act() => appConfigProvider.GetAsync();
+
+ // Assert
+ await Assert.ThrowsAsync(Act);
+ }
+
+ [Fact]
+ public async Task GetAsync_WhenEnvironmentIdDoesNotSet_ThrowsException()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .WithApplication(applicationId)
+ .WithConfigProfile(configProfileId);
+
+ // Act
+ Task> Act() => appConfigProvider.GetAsync();
+
+ // Assert
+ await Assert.ThrowsAsync(Act);
+ }
+
+ [Fact]
+ public async Task GetAsync_WhenConfigProfileIdDoesNotSet_ThrowsException()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .WithApplication(applicationId)
+ .WithEnvironment(environmentId);
+
+ // Act
+ Task> Act() => appConfigProvider.GetAsync();
+
+ // Assert
+ await Assert.ThrowsAsync(Act);
+ }
+
+ [Fact]
+ public async Task GetMultipleAsync_ThrowsException()
+ {
+ // Arrange
+ var key = Guid.NewGuid().ToString();
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager);
+
+ // Act
+ Task> Act() => appConfigProvider.GetMultipleAsync(key);
+
+ // Assert
+ await Assert.ThrowsAsync(Act);
+ }
+
+ [Fact]
+ public async Task GetAsync_PriorToNextAllowedPollTime_ReturnsLastConfig()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var configurationToken = Guid.NewGuid().ToString();
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+
+ var dateTimeNow = DateTime.UtcNow;
+ var nextAllowedPollTime = dateTimeNow.AddSeconds(10);
+ var lastConfig = new Dictionary
+ {
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() },
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }
+ };
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ cacheManager.Get(cacheKey).Returns(null);
+ dateTimeWrapper.UtcNow.Returns(dateTimeNow);
+
+ var appConfigResult = new AppConfigResult
+ {
+ PollConfigurationToken = configurationToken,
+ NextAllowedPollTime = nextAllowedPollTime,
+ LastConfig = JsonSerializer.Serialize(lastConfig)
+ };
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper,
+ AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId),
+ appConfigResult)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .WithApplication(applicationId)
+ .WithEnvironment(environmentId)
+ .WithConfigProfile(configProfileId);
+
+ // Act
+ var currentConfig = await appConfigProvider.GetAsync();
+
+ // Assert
+ cacheManager.Received(1).Get(cacheKey);
+ await client.DidNotReceiveWithAnyArgs().StartConfigurationSessionAsync(null);
+ await client.DidNotReceiveWithAnyArgs().GetLatestConfigurationAsync(null);
+ Assert.NotNull(lastConfig);
+ Assert.NotNull(currentConfig);
+ Assert.Equal(lastConfig, currentConfig);
+ }
+
+ [Fact]
+ public async Task GetAsync_AfterNextAllowedPollTime_RetrieveNewConfig()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var configurationToken = Guid.NewGuid().ToString();
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+
+ var dateTimeNow = DateTime.UtcNow;
+ var nextAllowedPollTime = dateTimeNow.AddSeconds(-1);
+ var nextPollInterval = TimeSpan.FromHours(24);
+ var nextPollConfigurationToken = Guid.NewGuid().ToString();
+
+ var lastConfig = new Dictionary
+ {
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() },
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }
+ };
+
+ var value = new Dictionary
+ {
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() },
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }
+ };
+
+ var contentType = "application/json";
+ var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value));
+ var response2 = new GetLatestConfigurationResponse
+ {
+ Configuration = new MemoryStream(content),
+ ContentType = contentType,
+ ContentLength = content.Length,
+ NextPollConfigurationToken = nextPollConfigurationToken,
+ NextPollIntervalInSeconds = Convert.ToInt32(nextPollInterval.TotalSeconds)
+ };
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any())
+ .Returns(response2);
+
+ cacheManager.Get(cacheKey).Returns(null);
+ dateTimeWrapper.UtcNow.Returns(dateTimeNow);
+
+ var appConfigResult = new AppConfigResult
+ {
+ PollConfigurationToken = configurationToken,
+ NextAllowedPollTime = nextAllowedPollTime,
+ LastConfig = JsonSerializer.Serialize(lastConfig)
+ };
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper,
+ AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId),
+ appConfigResult)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .WithApplication(applicationId)
+ .WithEnvironment(environmentId)
+ .WithConfigProfile(configProfileId);
+
+ // Act
+ var currentConfig = await appConfigProvider.GetAsync();
+
+ // Assert
+ cacheManager.Received(1).Get(cacheKey);
+ await client.DidNotReceiveWithAnyArgs().StartConfigurationSessionAsync(null);
+ await client.Received(1).GetLatestConfigurationAsync(
+ Arg.Is(x => x.ConfigurationToken == configurationToken),
+ Arg.Any());
+ Assert.NotNull(lastConfig);
+ Assert.NotNull(currentConfig);
+ Assert.NotEqual(lastConfig, currentConfig);
+ }
+
+ [Fact]
+ public async Task GetAsync_WhenNoToken_StartsASessionAndRetrieveNewConfig()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var configurationToken = string.Empty;
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+
+ var dateTimeNow = DateTime.UtcNow;
+ var nextAllowedPollTime = dateTimeNow.AddSeconds(-1);
+ var nextPollInterval = TimeSpan.FromHours(24);
+ var nextPollConfigurationToken = Guid.NewGuid().ToString();
+
+ var lastConfig = new Dictionary
+ {
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() },
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }
+ };
+
+ var value = new Dictionary
+ {
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() },
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }
+ };
+
+ var response1 = new StartConfigurationSessionResponse
+ {
+ InitialConfigurationToken = configurationToken
+ };
+
+ var contentType = "application/json";
+ var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value));
+ var response2 = new GetLatestConfigurationResponse
+ {
+ Configuration = new MemoryStream(content),
+ ContentType = contentType,
+ ContentLength = content.Length,
+ NextPollConfigurationToken = nextPollConfigurationToken,
+ NextPollIntervalInSeconds = Convert.ToInt32(nextPollInterval.TotalSeconds)
+ };
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any())
+ .Returns(response1);
+
+ client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any())
+ .Returns(response2);
+
+ cacheManager.Get(cacheKey).Returns(null);
+ dateTimeWrapper.UtcNow.Returns(dateTimeNow);
+
+ var appConfigResult = new AppConfigResult
+ {
+ PollConfigurationToken = configurationToken,
+ NextAllowedPollTime = nextAllowedPollTime,
+ LastConfig = JsonSerializer.Serialize(lastConfig)
+ };
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper,
+ AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId),
+ appConfigResult)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .WithApplication(applicationId)
+ .WithEnvironment(environmentId)
+ .WithConfigProfile(configProfileId);
+
+ // Act
+ var currentConfig = await appConfigProvider.GetAsync();
+
+ // Assert
+ cacheManager.Received(1).Get(cacheKey);
+ await client.Received(1).StartConfigurationSessionAsync(
+ Arg.Is(x =>
+ x.ApplicationIdentifier == applicationId &&
+ x.EnvironmentIdentifier == environmentId &&
+ x.ConfigurationProfileIdentifier == configProfileId),
+ Arg.Any());
+ await client.Received(1).GetLatestConfigurationAsync(
+ Arg.Is(x => x.ConfigurationToken == configurationToken),
+ Arg.Any());
+ Assert.NotNull(lastConfig);
+ Assert.NotNull(currentConfig);
+ Assert.NotEqual(lastConfig, currentConfig);
+ }
+
+ [Fact]
+ public async Task GetAsync_WhenForceFetch_RetrieveNewConfig()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+ var configurationToken = Guid.NewGuid().ToString();
+ var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId);
+
+ var dateTimeNow = DateTime.UtcNow;
+ var nextAllowedPollTime = dateTimeNow.AddSeconds(10);
+ var nextPollInterval = TimeSpan.FromHours(24);
+ var nextPollConfigurationToken = Guid.NewGuid().ToString();
+
+ var lastConfig = new Dictionary
+ {
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() },
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }
+ };
+
+ var value = new Dictionary
+ {
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() },
+ { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }
+ };
+
+ var response1 = new StartConfigurationSessionResponse
+ {
+ InitialConfigurationToken = configurationToken
+ };
+
+ var contentType = "application/json";
+ var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value));
+ var response2 = new GetLatestConfigurationResponse
+ {
+ Configuration = new MemoryStream(content),
+ ContentType = contentType,
+ ContentLength = content.Length,
+ NextPollConfigurationToken = nextPollConfigurationToken,
+ NextPollIntervalInSeconds = Convert.ToInt32(nextPollInterval.TotalSeconds)
+ };
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any())
+ .Returns(response1);
+
+ client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any())
+ .Returns(response2);
+
+ cacheManager.Get(cacheKey).Returns(null);
+
+ dateTimeWrapper.UtcNow.Returns(dateTimeNow);
+
+ var appConfigResult = new AppConfigResult
+ {
+ PollConfigurationToken = configurationToken,
+ NextAllowedPollTime = nextAllowedPollTime,
+ LastConfig = JsonSerializer.Serialize(lastConfig)
+ };
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper,
+ AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId),
+ appConfigResult)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .WithApplication(applicationId)
+ .WithEnvironment(environmentId)
+ .WithConfigProfile(configProfileId)
+ .ForceFetch();
+
+ // Act
+ var currentConfig = await appConfigProvider.GetAsync();
+
+ // Assert
+ await client.Received(1).StartConfigurationSessionAsync(
+ Arg.Is(x =>
+ x.ApplicationIdentifier == applicationId &&
+ x.EnvironmentIdentifier == environmentId &&
+ x.ConfigurationProfileIdentifier == configProfileId),
+ Arg.Any());
+ await client.Received(1).GetLatestConfigurationAsync(
+ Arg.Is(x => x.ConfigurationToken == configurationToken),
+ Arg.Any());
+ Assert.NotNull(lastConfig);
+ Assert.NotNull(currentConfig);
+ Assert.NotEqual(lastConfig, currentConfig);
+ }
+
+ [Fact]
+ public async Task GetMultipleAsync_WithArguments_ThrowsException()
+ {
+ // Arrange
+ var key = Guid.NewGuid().ToString();
+ var applicationId = Guid.NewGuid().ToString();
+ var environmentId = Guid.NewGuid().ToString();
+ var configProfileId = Guid.NewGuid().ToString();
+
+ var cacheManager = Substitute.For();
+ var client = Substitute.For();
+ var transformerManager = Substitute.For();
+ var dateTimeWrapper = Substitute.For();
+
+ var appConfigProvider = new AppConfigProvider(dateTimeWrapper)
+ .UseClient(client)
+ .UseCacheManager(cacheManager)
+ .UseTransformerManager(transformerManager)
+ .WithApplication(applicationId)
+ .WithEnvironment(environmentId)
+ .WithConfigProfile(configProfileId);
+
+ // Act
+ Task> Act() => appConfigProvider.GetMultipleAsync(key);
+
+ await Assert.ThrowsAsync(Act);
+ }
+}
\ No newline at end of file
From b538e1e1cf75c04eac8e56a66df71ccf266b94b7 Mon Sep 17 00:00:00 2001
From: Amir Khairalomoum
Date: Wed, 28 Feb 2024 13:18:37 +0000
Subject: [PATCH 09/16] add AppConfig provider documentation
---
docs/utilities/parameters.md | 62 +++++++++++++++++++++++++++++++++++-
1 file changed, 61 insertions(+), 1 deletion(-)
diff --git a/docs/utilities/parameters.md b/docs/utilities/parameters.md
index c958bf9d4..ee89c7629 100644
--- a/docs/utilities/parameters.md
+++ b/docs/utilities/parameters.md
@@ -4,7 +4,7 @@ description: Utility
---
-The Parameters utility provides high-level functionality to retrieve one or multiple parameter values from [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html){target="_blank"}, [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/){target="_blank"}, or [Amazon DynamoDB](https://aws.amazon.com/dynamodb/){target="_blank"}. We also provide extensibility to bring your own providers.
+The Parameters utility provides high-level functionality to retrieve one or multiple parameter values from [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html){target="_blank"}, [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/){target="_blank"}, [Amazon DynamoDB](https://aws.amazon.com/dynamodb/){target="_blank"}, or [AWS AppConfig](https://docs.aws.amazon.com/appconfig/latest/userguide/what-is-appconfig.html){target="_blank"}. We also provide extensibility to bring your own providers.
## Key features
@@ -33,6 +33,7 @@ This utility requires additional permissions to work as expected. See the table
| Secrets Manager | `SecretsProvider.Get(string)` `SecretsProvider.Get(string)` | `secretsmanager:GetSecretValue` |
| DynamoDB | `DynamoDBProvider.Get(string)` `DynamoDBProvider.Get(string)` | `dynamodb:GetItem` |
| DynamoDB | `DynamoDBProvider.GetMultiple(string)` `DynamoDBProvider.GetMultiple(string)` | `dynamodb:Query` |
+| App Config | `AppConfigProvider.Get()` | `appconfig:StartConfigurationSession` `appconfig:GetLatestConfiguration` |
## SSM Parameter Store
@@ -132,6 +133,65 @@ in order to get data from other regions or use specific credentials.
}
```
+## App Configurations
+
+For application configurations in AWS AppConfig, use `AppConfigProvider`.
+
+Alternatively, you can retrieve the instance of provider and configure its underlying SDK client,
+in order to get data from other regions or use specific credentials.
+
+
+=== "AppConfigProvider"
+
+ ```c# hl_lines="10-13 16-18"
+ using AWS.Lambda.Powertools.Parameters;
+ using AWS.Lambda.Powertools.Parameters.AppConfig;
+
+ public class Function
+ {
+ public async Task FunctionHandler
+ (APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
+ {
+ // Get AppConfig Provider instance
+ IAppConfigProvider appConfigProvider = ParametersManager.AppConfigProvider
+ .DefaultApplication("MyApplicationId")
+ .DefaultEnvironment("MyEnvironmentId")
+ .DefaultConfigProfile("MyConfigProfileId");
+
+ // Retrieve a single configuration, latest version
+ IDictionary value = await appConfigProvider
+ .GetAsync()
+ .ConfigureAwait(false);
+ }
+ }
+ ```
+
+=== "AppConfigProvider with an explicit region"
+
+ ```c# hl_lines="10-14"
+ using AWS.Lambda.Powertools.Parameters;
+ using AWS.Lambda.Powertools.Parameters.AppConfig;
+
+ public class Function
+ {
+ public async Task FunctionHandler
+ (APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
+ {
+ // Get AppConfig Provider instance
+ IAppConfigProvider appConfigProvider = ParametersManager.AppConfigProvider
+ .ConfigureClient(RegionEndpoint.EUCentral1)
+ .DefaultApplication("MyApplicationId")
+ .DefaultEnvironment("MyEnvironmentId")
+ .DefaultConfigProfile("MyConfigProfileId");
+
+ // Retrieve a single configuration, latest version
+ IDictionary value = await appConfigProvider
+ .GetAsync()
+ .ConfigureAwait(false);
+ }
+ }
+ ```
+
### Additional arguments
The AWS Systems Manager Parameter Store provider supports two additional arguments for the `Get()` and `GetMultiple()` methods:
From 4593f424d2576d40d2b81b1ecf25525a88670e28 Mon Sep 17 00:00:00 2001
From: Amir Khairalomoum
Date: Thu, 29 Feb 2024 17:06:47 +0000
Subject: [PATCH 10/16] add feature flags helper methods
---
.../AppConfig/AppConfigProvider.cs | 96 +++++++++++++++----
.../AppConfigProviderConfigurationBuilder.cs | 53 ++++++++++
.../AppConfig/IAppConfigProvider.cs | 36 +++++++
.../AppConfig/AppConfigFeatureFlagHelper.cs | 52 ++++++++++
4 files changed, 216 insertions(+), 21 deletions(-)
create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigFeatureFlagHelper.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs
index 951e7e299..ea5979398 100644
--- a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs
@@ -14,6 +14,7 @@
*/
using System.Collections.Concurrent;
+using System.Text.Json.Nodes;
using Amazon;
using Amazon.AppConfigData;
using Amazon.AppConfigData.Model;
@@ -35,32 +36,32 @@ public class AppConfigProvider : ParameterProvider
private string _defaultApplicationId = string.Empty;
-
+
///
/// The default environment Id.
///
private string _defaultEnvironmentId = string.Empty;
-
+
///
/// The default configuration profile Id.
///
private string _defaultConfigProfileId = string.Empty;
-
+
///
/// Instance of datetime wrapper.
///
private readonly IDateTimeWrapper _dateTimeWrapper;
-
+
///
/// Thread safe dictionary to store results.
///
private readonly ConcurrentDictionary _results = new(StringComparer.OrdinalIgnoreCase);
-
+
///
/// The client instance.
///
private IAmazonAppConfigData? _client;
-
+
///
/// Gets the client instance.
///
@@ -88,7 +89,7 @@ internal AppConfigProvider(
}
#region IParameterProviderConfigurableClient implementation
-
+
///
/// Use a custom client
///
@@ -238,7 +239,7 @@ public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecre
_client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey, awsSessionToken, config);
return this;
}
-
+
#endregion
///
@@ -253,7 +254,7 @@ public IAppConfigProvider DefaultApplication(string applicationId)
_defaultApplicationId = applicationId;
return this;
}
-
+
///
/// Sets the default environment ID or name.
///
@@ -266,7 +267,7 @@ public IAppConfigProvider DefaultEnvironment(string environmentId)
_defaultEnvironmentId = environmentId;
return this;
}
-
+
///
/// Sets the default configuration profile ID or name.
///
@@ -318,7 +319,7 @@ protected override AppConfigProviderConfigurationBuilder NewConfigurationBuilder
.WithApplication(_defaultApplicationId)
.WithEnvironment(_defaultEnvironmentId)
.WithConfigProfile(_defaultConfigProfileId);
-
+
}
///
@@ -353,29 +354,29 @@ protected override AppConfigProviderConfigurationBuilder NewConfigurationBuilder
.GetAsync()
.ConfigureAwait(false);
}
-
+
///
/// Get last AppConfig value and transform it to JSON value.
///
/// JSON value type.
/// The AppConfig JSON value.
- public T? Get() where T: class
+ public T? Get() where T : class
{
return GetAsync().GetAwaiter().GetResult();
}
-
+
///
/// Get last AppConfig value and transform it to JSON value.
///
/// JSON value type.
/// The AppConfig JSON value.
- public async Task GetAsync() where T: class
+ public async Task GetAsync