diff --git a/docs/utilities/parameters.md b/docs/utilities/parameters.md index 85d30d77e..f72c7704e 100644 --- a/docs/utilities/parameters.md +++ b/docs/utilities/parameters.md @@ -4,29 +4,46 @@ description: Utility --- -The parameters utility provides a way to retrieve parameter values from +The parameters utilities provide a way to retrieve parameter values from [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html), -[AWS Secrets Manager](https://aws.amazon.com/secrets-manager/), or [Amazon DynamoDB](https://aws.amazon.com/dynamodb/). -It also provides a base class to create your parameter provider implementation. +[AWS Secrets Manager](https://aws.amazon.com/secrets-manager/), [Amazon DynamoDB](https://aws.amazon.com/dynamodb/), +or [AWS AppConfig](https://aws.amazon.com/systems-manager/features/appconfig/). + +## Key features -**Key features** - -* Retrieve one or multiple parameters from the underlying provider +* Retrieve one or multiple parameters from an underlying provider in a standard way * Cache parameter values for a given amount of time (defaults to 5 seconds) * Transform parameter values from JSON or base 64 encoded strings ## Install -Depending on your version of Java (either Java 1.8 or 11+), the configuration slightly changes. +In order to provide lightweight dependencies, each parameters module is available as its own +package: + +* **Secrets Manager** - `powertools-parameters-secrets` +* **SSM Parameter Store** - `powertools-parameters-ssm` +* **Amazon DynamoDB** -`powertools-parameters-dynamodb` +* **AWS AppConfig** - `powertools-parameters-appconfig` + +You can easily mix and match parameter providers within the same project for different needs. + +Depending on which Java version you are using, you configuration will differ. Note that you must also provide +the concrete parameters module you want to use below - see the TODOs! === "Maven Java 11+" - ```xml hl_lines="3-7 16 18 24-27" + ```xml hl_lines="4-12 17 24 30-34" ... software.amazon.lambda - powertools-parameters - {{ powertools.version }} + + + powertools-parameters-secrets + powertools-parameters-ssm + powertools-parameters-dynamodb + powertools-parameters-appconfig + + {{ powertools.version }} ... @@ -44,9 +61,10 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl 11 11 + software.amazon.lambda - powertools-parameters + powertools-parameters-secrets @@ -65,13 +83,19 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl === "Maven Java 1.8" - ```xml hl_lines="3-7 16 18 24-27" + ```xml hl_lines="4-12 17 24 30-34" ... software.amazon.lambda - powertools-parameters - {{ powertools.version }} + + + powertools-parameters-secrets + powertools-parameters-ssm + powertools-parameters-dynamodb + powertools-parameters-appconfig + + {{ powertools.version }} ... @@ -89,9 +113,10 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl 1.8 1.8 + software.amazon.lambda - powertools-parameters + powertools-parameters-secrets @@ -110,7 +135,7 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl === "Gradle Java 11+" - ```groovy hl_lines="3 11" + ```groovy hl_lines="3 11 12" plugins { id 'java' id 'io.freefair.aspectj.post-compile-weaving' version '8.1.0' @@ -121,7 +146,8 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl } dependencies { - aspect 'software.amazon.lambda:powertools-parameters:{{ powertools.version }}' + // TODO! Provide the parameters module you want to use here + aspect 'software.amazon.lambda:powertools-parameters-secrets:{{ powertools.version }}' } sourceCompatibility = 11 // or higher @@ -130,7 +156,7 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl === "Gradle Java 1.8" - ```groovy hl_lines="3 11" + ```groovy hl_lines="3 11 12" plugins { id 'java' id 'io.freefair.aspectj.post-compile-weaving' version '6.6.3' @@ -140,8 +166,9 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl mavenCentral() } + // TODO! Provide an aspectLibrary for each of the parameters module(s) you want to use here dependencies { - aspect 'software.amazon.lambda:powertools-parameters:{{ powertools.version }}' + aspect 'software.amazon.lambda:powertools-parameters-secrets:{{ powertools.version }}' } sourceCompatibility = 1.8 @@ -152,63 +179,78 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl This utility requires additional permissions to work as expected. See the table below: -Provider | Function/Method | IAM Permission -------------------------------------------------- |----------------------------------------------------------------------| --------------------------------------------------------------------------------- -SSM Parameter Store | `SSMProvider.get(String)` `SSMProvider.get(String, Class)` | `ssm:GetParameter` -SSM Parameter Store | `SSMProvider.getMultiple(String)` | `ssm:GetParametersByPath` -Secrets Manager | `SecretsProvider.get(String)` `SecretsProvider.get(String, Class)` | `secretsmanager:GetSecretValue` -DynamoDB | `DynamoDBProvider.get(String)` `DynamoDBProvider.getMultiple(string)` | `dynamodb:GetItem` `dynamoDB:Query` +| Provider | Function/Method | IAM Permission | +|-----------|-------------------------------------------------------------------------|---------------------------------------------------------------------------| +| SSM | `SSMProvider.get(String)` `SSMProvider.get(String, Class)` | `ssm:GetParameter` | +| SSM | `SSMProvider.getMultiple(String)` | `ssm:GetParametersByPath` | +| SSM | If using `withDecryption(true)` | You must add an additional permission `kms:Decrypt` | +| Secrets | `SecretsProvider.get(String)` `SecretsProvider.get(String, Class)` | `secretsmanager:GetSecretValue` | +| DynamoDB | `DynamoDBProvider.get(String)` `DynamoDBProvider.getMultiple(string)` | `dynamodb:GetItem` `dynamoDB:Query` | +| AppConfig | `AppConfigProvider.get(String)` `AppConfigProvider.getMultiple(string)` | `appconfig:StartConfigurationSession`, `appConfig:GetLatestConfiguration` | -## SSM Parameter Store +## Retrieving Parameters +You can retrieve parameters either using annotations or by using the `xParamProvider` class for each parameter +provider directly. The latter is useful if you need to configure the underlying SDK client, for example to use +a different region or credentials, the former is simpler to use. -You can retrieve a single parameter using SSMProvider.get() and pass the key of the parameter. -For multiple parameters, you can use SSMProvider.getMultiple() and pass the path to retrieve them all. +## Built-in provider classes -Alternatively, you can retrieve an instance of a provider and configure its underlying SDK client, -in order to get data from other regions or use specific credentials. +This section describes the built-in provider classes for each parameter store, providing +examples showing how to inject parameters using annotations, and how to use the provider +interface. In cases where a provider supports extra features, these will also be described. -=== "SSMProvider" +### Secrets Manager - ```java hl_lines="6" - import software.amazon.lambda.powertools.parameters.SSMProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; +=== "Secrets Manager: @SecretsParam" - public class AppWithSSM implements RequestHandler { - // Get an instance of the SSM Provider - SSMProvider ssmProvider = ParamManager.getSsmProvider(); - - // Retrieve a single parameter - String value = ssmProvider.get("/my/parameter"); - - // Retrieve multiple parameters from a path prefix - // This returns a Map with the parameter name as key - Map values = ssmProvider.getMultiple("/my/path/prefix"); + ```java hl_lines="8 9" + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import software.amazon.lambda.powertools.parameters.secrets.SecretsParam; + public class ParametersFunction implements RequestHandler { + + // Annotation-style injection from secrets manager + @SecretsParam(key = "/powertools-java/userpwd") + String secretParam; + + public string handleRequest(String request, Context context) { + // ... do something with the secretParam here + return "something"; + } } ``` -=== "SSMProvider with a custom client" +=== "Secrets Manager: SecretsProvider" - ```java hl_lines="5 7" - import software.amazon.lambda.powertools.parameters.SSMProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; + ```java hl_lines="12-15 19" + import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; + + import com.amazonaws.services.lambda.runtime.Context; + import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; + import software.amazon.lambda.powertools.parameters.secrets.SecretsProvider; + import com.amazonaws.services.lambda.runtime.RequestHandler; - public class AppWithSSM implements RequestHandler { - SsmClient client = SsmClient.builder().region(Region.EU_CENTRAL_1).build(); - // Get an instance of the SSM Provider - SSMProvider ssmProvider = ParamManager.getSsmProvider(client); + public class RequestHandlerWithParams implements RequestHandler { - // Retrieve a single parameter - String value = ssmProvider.get("/my/parameter"); + // Get an instance of the SecretsProvider. We can provide a custom client here if we want, + // for instance to use a particular region. + SecretsProvider secretsProvider = SecretsProvider + .builder() + .withClient(SecretsManagerClient.builder().build()) + .build(); - // Retrieve multiple parameters from a path prefix - // This returns a Map with the parameter name as key - Map values = ssmProvider.getMultiple("/my/path/prefix"); + public String handleRequest(String input, Context context) { + // Retrieve a single secret + String value = secretsProvider.get("/my/secret"); + // ... do something with the secretParam here + return "something"; + } } ``` -### Additional arguments +### SSM Parameter Store The AWS Systems Manager Parameter Store provider supports two additional arguments for the `get()` and `getMultiple()` methods: @@ -217,9 +259,59 @@ The AWS Systems Manager Parameter Store provider supports two additional argumen | **withDecryption()** | `False` | Will automatically decrypt the parameter. | | **recursive()** | `False` | For `getMultiple()` only, will fetch all parameter values recursively based on a path prefix. | -**Example:** -=== "AppWithSSM.java" +=== "SSM Parameter Store: @SSMParam" + + ```java hl_lines="8 9" + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import software.amazon.lambda.powertools.parameters.ssm.SSMParam; + + public class ParametersFunction implements RequestHandler { + + // Annotation-style injection from SSM Parameter Store + @SSMParam(key = "/powertools-java/param") + String ssmParam; + + public string handleRequest(String request, Context context) { + return ssmParam; // Request handler simply returns our configuration value + } + } + ``` + +=== "SSM Parameter Store: SSMProvider" + + ```java hl_lines="12-15 19-20 22" + import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import software.amazon.awssdk.services.ssm.SsmClient; + import software.amazon.lambda.powertools.parameters.ssm.SSMProvider; + + public class RequestHandlerWithParams implements RequestHandler { + + // Get an instance of the SSMProvider. We can provide a custom client here if we want, + // for instance to use a particular region. + SSMProvider ssmProvider = SSMProvider + .builder() + .withClient(SsmClient.builder().build()) + .build(); + + public String handleRequest(String input, Context context) { + // Retrieve a single param + String value = ssmProvider + .get("/my/secret"); + // We might instead want to retrieve multiple parameters at once, returning a Map of key/value pairs + // .getMultiple("/my/secret/path"); + + // Return the result + return value; + } + } + ``` + +=== "SSM Parameter Store: Additional Options" ```java hl_lines="9 12" import software.amazon.lambda.powertools.parameters.SSMProvider; @@ -238,183 +330,159 @@ The AWS Systems Manager Parameter Store provider supports two additional argumen } ``` -## Secrets Manager +### DynamoDB -For secrets stored in Secrets Manager, use `getSecretsProvider`. +=== "DynamoDB: @DyanmoDbParam" -Alternatively, you can retrieve an instance of a provider and configure its underlying SDK client, -in order to get data from other regions or use specific credentials. + ```java hl_lines="8 9" + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import software.amazon.lambda.powertools.parameters.dynamodb.DynamoDBParam; + public class ParametersFunction implements RequestHandler { -=== "SecretsProvider" + // Annotation-style injection from DynamoDB + @DynamoDbParam(table = "my-test-tablename", key = "myKey") + String ddbParam; - ```java hl_lines="9" - import software.amazon.lambda.powertools.parameters.SecretsProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; - - public class AppWithSecrets implements RequestHandler { - // Get an instance of the Secrets Provider - SecretsProvider secretsProvider = ParamManager.getSecretsProvider(); - - // Retrieve a single secret - String value = secretsProvider.get("/my/secret"); - + public string handleRequest(String request, Context context) { + return ddbParam; // Request handler simply returns our configuration value + } } ``` -=== "SecretsProvider with a custom client" +=== "DynamoDB: DynamoDbProvider" - ```java hl_lines="5 7" - import software.amazon.lambda.powertools.parameters.SecretsProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; + ```java hl_lines="12-15 19-20 22" + import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; + + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + import software.amazon.lambda.powertools.parameters.dynamodb.DynamoDbProvider; - public class AppWithSecrets implements RequestHandler { - SecretsManagerClient client = SecretsManagerClient.builder().region(Region.EU_CENTRAL_1).build(); - // Get an instance of the Secrets Provider - SecretsProvider secretsProvider = ParamManager.getSecretsProvider(client); + public class RequestHandlerWithParams implements RequestHandler { - // Retrieve a single secret - String value = secretsProvider.get("/my/secret"); + // Get an instance of the DynamoDbProvider. We can provide a custom client here if we want, + // for instance to use a particular region. + DynamoDbProvider ddbProvider = DynamoDbProvider + .builder() + .withClient(DynamoDbClient.builder().build()) + .build(); + public String handleRequest(String input, Context context) { + // Retrieve a single param + String value = ddbProvider + .get("/my/secret"); + // We might instead want to retrieve multiple values at once, returning a Map of key/value pairs + // .getMultiple("my-partition-key-value"); + + // Return the result + return value; + } } ``` -## DynamoDB -To get secrets stored in DynamoDB, use `getDynamoDbProvider`, providing the name of the table that -contains the secrets. As with the other providers, an overloaded methods allows you to retrieve -a `DynamoDbProvider` providing a client if you need to configure it yourself. +### AppConfig -=== "DynamoDbProvider" +=== "AppConfig: @AppConfigParam" - ```java hl_lines="6 9" - import software.amazon.lambda.powertools.parameters.DynamoDbProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; + ```java hl_lines="8 9" + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; + import software.amazon.lambda.powertools.parameters.appconfig.AppConfigParam; - public class AppWithDynamoDbParameters implements RequestHandler { - // Get an instance of the DynamoDbProvider - DynamoDbProvider ddbProvider = ParamManager.getDynamoDbProvider("my-parameters-table"); + public class ParametersFunction implements RequestHandler { - // Retrieve a single parameter - String value = ddbProvider.get("my-key"); - } - ``` - -=== "DynamoDbProvider with a custom client" + // Annotation-style injection from AppConfig + @AppConfigParam(application = "my-app", environment = "my-env", key = "myKey") + String appConfigParam; - ```java hl_lines="9 10 11 12 15 18" - import software.amazon.lambda.powertools.parameters.DynamoDbProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; - import software.amazon.awssdk.services.dynamodb.DynamoDbClient; - import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; - import software.amazon.awssdk.regions.Region; - - public class AppWithDynamoDbParameters implements RequestHandler { - // Get a DynamoDB Client with an explicit region - DynamoDbClient ddbClient = DynamoDbClient.builder() - .httpClientBuilder(UrlConnectionHttpClient.builder()) - .region(Region.EU_CENTRAL_2) - .build(); - - // Get an instance of the DynamoDbProvider - DynamoDbProvider provider = ParamManager.getDynamoDbProvider(ddbClient, "test-table"); - - // Retrieve a single parameter - String value = ddbProvider.get("my-key"); - } + public string handleRequest(String request, Context context) { + return appConfigParam; // Request handler simply returns our configuration value + } + } ``` -## AppConfig -To get parameters stored in AppConfig, use `getAppConfigProvider`, providing the application and environment -name to retrieve configuration from. As with the other providers, an overloaded method allows you to retrieve -an `AppConfigProvider` providing a client if you need to configure it yourself. - -=== "AppConfigProvider" +=== "AppConfig: AppConfigProvider" - ```java hl_lines="6 9" - import software.amazon.lambda.powertools.parameters.AppConfigProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; - - public class AppWitAppConfigParameters implements RequestHandler { - // Get an instance of the AppConfigProvider - AppConfigProvider appConfigProvider = ParamManager.getAppConfigProvider("my-environment", "my-app"); + ```java hl_lines="12-15 19-20" + import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; - // Retrieve a single parameter - String value = appConfigProvider.get("my-key"); - } - ``` - -=== "AppConfigProvider with a custom client" - - ```java hl_lines="9 10 11 12 15 18" - import software.amazon.lambda.powertools.parameters.AppConfigProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; + import com.amazonaws.services.lambda.runtime.Context; + import com.amazonaws.services.lambda.runtime.RequestHandler; import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; - import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; - import software.amazon.awssdk.regions.Region; - - public class AppWithDynamoDbParameters implements RequestHandler { - // Get an AppConfig Client with an explicit region - AppConfigDataClient appConfigDataClient = AppConfigDataClient.builder() - .httpClientBuilder(UrlConnectionHttpClient.builder()) - .region(Region.EU_CENTRAL_2) - .build(); + import software.amazon.lambda.powertools.parameters.appconfig.AppConfigProvider; - // Get an instance of the DynamoDbProvider - AppConfigProvider appConfigProvider = ParamManager.getAppConfigProvider(appConfigDataClient, "my-environment", "my-app"); + public class RequestHandlerWithParams implements RequestHandler { + + // Get an instance of the AppConfigProvider. We can provide a custom client here if we want, + // for instance to use a particular region. + AppConfigProvider appConfigProvider = AppConfigProvider + .builder() + .withClient(AppConfigDataClient.builder().build()) + .build(); - // Retrieve a single parameter - String value = appConfigProvider.get("my-key"); - } + public String handleRequest(String input, Context context) { + // Retrieve a single param + String value = appConfigProvider + .get("/my/secret"); + + // Return the result + return value; + } + } ``` - ## Advanced configuration ### Caching +Each provider uses the `CacheManager` to cache parameter values. When a value is retrieved using from the provider, a +custom cache duration can be provided using `withMaxAge(duration, unit)`. -By default, all parameters and their corresponding values are cached for 5 seconds. - -You can customize this default value using `defaultMaxAge`. You can also customize this value for each parameter using -`withMaxAge`. - -=== "Provider with default Max age" - - ```java hl_lines="9" - import software.amazon.lambda.powertools.parameters.SecretsProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; +If this is not specified, the default value set on the `CacheManager` itself will be used. This default can be customized +by calling `setDefaultExpirationTime(duration, unit)` on the `CacheManager`. - public class AppWithSecrets implements RequestHandler { - // Get an instance of the Secrets Provider - SecretsProvider secretsProvider = ParamManager.getSecretsProvider() - .defaultMaxAge(10, ChronoUnit.SECONDS); +=== "Customize Cache" - String value = secretsProvider.get("/my/secret"); + ```java hl_lines="9 10 14 19 22-25" + import java.time.Duration; + import software.amazon.lambda.powertools.parameters.appconfig.AppConfigProvider; + import software.amazon.lambda.powertools.parameters.cache.CacheManager; - } - ``` - -=== "Provider with age for each param" - - ```java hl_lines="8" - import software.amazon.lambda.powertools.parameters.SecretsProvider; - import software.amazon.lambda.powertools.parameters.ParamManager; - - public class AppWithSecrets implements RequestHandler { - SecretsManagerClient client = SecretsManagerClient.builder().region(Region.EU_CENTRAL_1).build(); + public class CustomizeCache { - SecretsProvider secretsProvider = ParamManager.getSecretsProvider(client); - - String value = secretsProvider.withMaxAge(10, ChronoUnit.SECONDS).get("/my/secret"); + public void CustomizeCache() { + + CacheManager cacheManager = new CacheManager(); + cacheManager.setDefaultExpirationTime(Duration.ofSeconds(10)); + AppConfigProvider paramProvider = AppConfigProvider + .builder() + .withCacheManager(cacheManager) + .withClient(AppConfigDataClient.builder().build()) + .build(); + + // Will use the default specified above - 10 seconds + String myParam1 = paramProvider.get("myParam1"); + + // Will override the default above + String myParam2 = paramProvider + .withMaxAge(20, ChronoUnit.SECONDS) + .get("myParam2"); + + return myParam2; + } } ``` + ### Transform values Parameter values can be transformed using ```withTransformation(transformerClass)```. -Base64 and JSON transformations are provided. For more complex transformation, you need to specify how to deserialize- +Base64 and JSON transformations are provided. For more complex transformation, you need to specify how to deserialize. -!!! warning "`SSMProvider.getMultiple()` does not support transformation and will return simple Strings." +!!! warning "`getMultiple()` does not support transformation and will return simple Strings." === "Base64 Transformation" ```java @@ -431,14 +499,16 @@ Base64 and JSON transformations are provided. For more complex transformation, y .get("/my/parameter/json", MyObj.class); ``` -## Write your own Transformer + + +#### Create your own Transformer You can write your own transformer, by implementing the `Transformer` interface and the `applyTransformation()` method. For example, if you wish to deserialize XML into an object. === "XmlTransformer.java" - ```java hl_lines="1" + ```java public class XmlTransformer implements Transformer { private final XmlMapper mapper = new XmlMapper(); @@ -470,172 +540,141 @@ To simplify the use of the library, you can chain all method calls before a get. ```java ssmProvider - .defaultMaxAge(10, SECONDS) // will set 10 seconds as the default cache TTL .withMaxAge(1, MINUTES) // will set the cache TTL for this value at 1 minute .withTransformation(json) // json is a static import from Transformer.json .withDecryption() // enable decryption of the parameter value .get("/my/param", MyObj.class); // finally get the value - ``` - -## Create your own provider -You can create your own custom parameter store provider by inheriting the ```BaseProvider``` class and implementing the -```String getValue(String key)``` method to retrieve data from your underlying store. All transformation and caching logic is handled by the get() methods in the base class. +### Create your own Provider +You can create your own custom parameter store provider by implementing a handful of classes: -=== "Example implementation using S3 as a custom parameter" +=== "CustomProvider.java" ```java - public class S3Provider extends BaseProvider { - - private final S3Client client; - private String bucket; - - S3Provider(CacheManager cacheManager) { - this(cacheManager, S3Client.create()); - } - - S3Provider(CacheManager cacheManager, S3Client client) { - super(cacheManager); - this.client = client; + import java.util.Map; + import software.amazon.lambda.powertools.parameters.BaseProvider; + import software.amazon.lambda.powertools.parameters.cache.CacheManager; + import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + + /** + * Our custom parameter provider itself. This does the heavy lifting of retrieving + * parameters from whatever our underlying parameter store might be. + **/ + public class CustomProvider extends BaseProvider { + + public CustomProvider(CacheManager cacheManager, TransformationManager transformationManager) { + super(cacheManager, transformationManager); } - - public S3Provider withBucket(String bucket) { - this.bucket = bucket; - return this; + + public CustomProviderBuilder builder() { + return new CustomProviderBuilder(); } @Override protected String getValue(String key) { - if (bucket == null) { - throw new IllegalStateException("A bucket must be specified, using withBucket() method"); - } - - GetObjectRequest request = GetObjectRequest.builder().bucket(bucket).key(key).build(); - ResponseBytes response = client.getObject(request, ResponseTransformer.toBytes()); - return response.asUtf8String(); + throw new RuntimeException("TODO - return a single value"); } @Override protected Map getMultipleValues(String path) { - if (bucket == null) { - throw new IllegalStateException("A bucket must be specified, using withBucket() method"); - } - - ListObjectsV2Request listRequest = ListObjectsV2Request.builder().bucket(bucket).prefix(path).build(); - List s3Objects = client.listObjectsV2(listRequest).contents(); - - Map result = new HashMap<>(); - s3Objects.forEach(s3Object -> { - result.put(s3Object.key(), getValue(s3Object.key())); - }); - - return result; + throw new RuntimeException("TODO - Optional - return multiple values"); } - - @Override - protected void resetToDefaults() { - super.resetToDefaults(); - bucket = null; - } - } ``` -=== "Using custom parameter store" - - ```java hl_lines="3" - S3Provider provider = new S3Provider(ParamManager.getCacheManager()); - - provider.setTransformationManager(ParamManager.getTransformationManager()); - - String value = provider.withBucket("myBucket").get("myKey"); - ``` - -## Annotation - -You can make use of the annotation `@Param` to inject a parameter value in a variable. +=== "CustomProviderBuilder.java" -By default, it will use `SSMProvider` to retrieve the value from AWS System Manager Parameter Store. -You could specify a different provider as long as it extends `BaseProvider` and/or a `Transformer`. + ```java + /** + * Provides a builder-style interface to configure our @{link CustomProvider}. + **/ + public class CustomProviderBuilder { + private CacheManager cacheManager; + private TransformationManager transformationManager; + + /** + * Create a {@link CustomProvider} instance. + * + * @return a {@link CustomProvider} + */ + public CustomProvider build() { + if (cacheManager == null) { + cacheManager = new CacheManager(); + } + return new CustomProvider(cacheManager, transformationManager); + } -=== "Param Annotation" + /** + * Provide a CacheManager to the {@link CustomProvider} + * + * @param cacheManager the manager that will handle the cache of parameters + * @return the builder to chain calls (eg.
builder.withCacheManager().build()
) + */ + public CustomProviderBuilder withCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + return this; + } - ```java hl_lines="3" - public class AppWithAnnotation implements RequestHandler { - - @Param(key = "/my/parameter/json") - ObjectToDeserialize value; - + /** + * Provide a transformationManager to the {@link CustomProvider} + * + * @param transformationManager the manager that will handle transformation of parameters + * @return the builder to chain calls (eg.
builder.withTransformationManager().build()
) + */ + public CustomProviderBuilder withTransformationManager(TransformationManager transformationManager) { + this.transformationManager = transformationManager; + return this; + } } ``` -=== "Custom Provider Usage" - - ```java hl_lines="3" - public class AppWithAnnotation implements RequestHandler { - - @Param(key = "/my/parameter/json" provider = SecretsProvider.class, transformer = JsonTransformer.class) - ObjectToDeserialize value; - +=== "CustomProviderParam.java" + + ```java + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + import software.amazon.lambda.powertools.parameters.transform.Transformer; + + /** + * Aspect to inject a parameter from our custom provider. Note that if you + * want to implement a provider _without_ an Aspect and field injection, you can + * skip implementing both this and the {@link CustomProviderAspect} class. + **/ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface CustomProviderParam { + // The parameter key + String key(); + + // The transformer to use + Class transformer() default Transformer.class; } ``` - In this case ```SecretsProvider``` will be used to retrieve a raw value that is then trasformed into the target Object by using ```JsonTransformer```. - To show the convenience of the annotation compare the following two code snippets. - - -### Install +=== "CustomProviderAspect.java" -If you want to use the ```@Param``` annotation in your project add configuration to compile-time weave (CTW) the powertools-parameters aspects into your project. - -=== "Maven" + ```java - ```xml - - - ... - - dev.aspectj - aspectj-maven-plugin - 1.13.1 - - ... - - ... - - software.amazon.lambda - powertools-parameters - - - - - - - compile - - - - - ... - - - ``` + /** + * Aspect to inject a parameter from our custom provider where the {@link CustomProviderParam} + * annotation is used. + **/ + @Aspect + public class CustomProviderAspect extends BaseParamAspect { -=== "Gradle" - - ```groovy - plugins{ - id 'java' - id 'io.freefair.aspectj.post-compile-weaving' version '6.3.0' - } - - repositories { - mavenCentral() - } + @Pointcut("get(* *) && @annotation(ddbConfigParam)") + public void getParam(CustomProviderParam customConfigParam) { + } + + @Around("getParam(customConfigParam)") + public Object injectParam(final ProceedingJoinPoint joinPoint, final CustomProviderParam customConfigParam) { + BaseProvider provider = CustomProvider.builder().build(); - dependencies { - ... - aspect 'software.amazon.lambda:powertools-parameters:{{ powertools.version }}' - implementation 'org.aspectj:aspectjrt:1.9.19' + return getAndTransform(customConfigParam.key(), ddbConfigParam.transformer(), provider, + (FieldSignature) joinPoint.getSignature()); + } + } ``` \ No newline at end of file diff --git a/examples/powertools-examples-parameters/pom.xml b/examples/powertools-examples-parameters/pom.xml index 7a4af628c..8d7fbd4f4 100644 --- a/examples/powertools-examples-parameters/pom.xml +++ b/examples/powertools-examples-parameters/pom.xml @@ -21,9 +21,15 @@ software.amazon.lambda - powertools-parameters + powertools-parameters-ssm ${project.version} + + software.amazon.lambda + powertools-parameters-secrets + ${project.version} + + com.amazonaws aws-lambda-java-core diff --git a/examples/powertools-examples-parameters/src/main/java/org/demo/parameters/ParametersFunction.java b/examples/powertools-examples-parameters/src/main/java/org/demo/parameters/ParametersFunction.java index 5b691cfd9..9a1d3636b 100644 --- a/examples/powertools-examples-parameters/src/main/java/org/demo/parameters/ParametersFunction.java +++ b/examples/powertools-examples-parameters/src/main/java/org/demo/parameters/ParametersFunction.java @@ -31,17 +31,30 @@ import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import software.amazon.lambda.powertools.parameters.ParamManager; -import software.amazon.lambda.powertools.parameters.SSMProvider; -import software.amazon.lambda.powertools.parameters.SecretsProvider; +import software.amazon.lambda.powertools.parameters.secrets.SecretsParam; +import software.amazon.lambda.powertools.parameters.secrets.SecretsProvider; +import software.amazon.lambda.powertools.parameters.ssm.SSMParam; +import software.amazon.lambda.powertools.parameters.ssm.SSMProvider; public class ParametersFunction implements RequestHandler { private final static Logger log = LogManager.getLogger(ParametersFunction.class); - SSMProvider ssmProvider = ParamManager.getSsmProvider(); - SecretsProvider secretsProvider = ParamManager.getSecretsProvider(); + // Annotation-style injection from secrets manager + @SecretsParam(key = "/powertools-java/userpwd") + String secretParamInjected; - String simpleValue = ssmProvider.defaultMaxAge(30, SECONDS).get("/powertools-java/sample/simplekey"); + // Annotation-style injection from Systems Manager + @SSMParam(key = "/powertools-java/sample/simplekey") + String ssmParamInjected; + + SSMProvider ssmProvider = SSMProvider + .builder() + .build(); + SecretsProvider secretsProvider = SecretsProvider + .builder() + .build(); + + String simpleValue = ssmProvider.withMaxAge(30, SECONDS).get("/powertools-java/sample/simplekey"); String listValue = ssmProvider.withMaxAge(60, SECONDS).get("/powertools-java/sample/keylist"); MyObject jsonObj = ssmProvider.withTransformation(json).get("/powertools-java/sample/keyjson", MyObject.class); Map allValues = ssmProvider.getMultiple("/powertools-java/sample"); diff --git a/mkdocs.yml b/mkdocs.yml index a8569f664..b7f793e18 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -85,7 +85,7 @@ extra_javascript: extra: powertools: - version: 1.18.0 # to update after each release (we do not want snapshot version here) + version: 2.0.0 # to update after each release (we do not want snapshot version here) repo_url: https://github.com/aws-powertools/powertools-lambda-java edit_uri: edit/main/docs diff --git a/pom.xml b/pom.xml index cad72dee1..1d35a990f 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,11 @@ powertools-e2e-tests powertools-batch examples + powertools-parameters/powertools-parameters-ssm + powertools-parameters/powertools-parameters-secrets + powertools-parameters/powertools-parameters-dynamodb + powertools-parameters/powertools-parameters-appconfig + powertools-parameters/powertools-parameters-tests @@ -600,35 +605,6 @@ - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.3.0 - - checkstyle.xml - UTF-8 - true - true - false - - - - - - com.puppycrawl.tools - checkstyle - 10.12.3 - - - - - - check - - - - diff --git a/powertools-e2e-tests/handlers/batch/pom.xml b/powertools-e2e-tests/handlers/batch/pom.xml index aee9bf3dd..8740dcb0b 100644 --- a/powertools-e2e-tests/handlers/batch/pom.xml +++ b/powertools-e2e-tests/handlers/batch/pom.xml @@ -5,7 +5,7 @@ software.amazon.lambda e2e-test-handlers-parent - 1.0.0 + 2.0.0-SNAPSHOT e2e-test-handler-batch diff --git a/powertools-e2e-tests/handlers/idempotency/pom.xml b/powertools-e2e-tests/handlers/idempotency/pom.xml index 22b6a1c53..a0e6bd4fc 100644 --- a/powertools-e2e-tests/handlers/idempotency/pom.xml +++ b/powertools-e2e-tests/handlers/idempotency/pom.xml @@ -5,7 +5,7 @@ software.amazon.lambda e2e-test-handlers-parent - 1.0.0 + 2.0.0-SNAPSHOT e2e-test-handler-idempotency diff --git a/powertools-e2e-tests/handlers/largemessage/pom.xml b/powertools-e2e-tests/handlers/largemessage/pom.xml index 8302624ef..277e76fc1 100644 --- a/powertools-e2e-tests/handlers/largemessage/pom.xml +++ b/powertools-e2e-tests/handlers/largemessage/pom.xml @@ -5,7 +5,7 @@ software.amazon.lambda e2e-test-handlers-parent - 1.0.0 + 2.0.0-SNAPSHOT e2e-test-handler-largemessage diff --git a/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml b/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml index 1fe9092ef..d887341c5 100644 --- a/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml +++ b/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml @@ -5,7 +5,7 @@ software.amazon.lambda e2e-test-handlers-parent - 1.0.0 + 2.0.0-SNAPSHOT e2e-test-handler-large-msg-idempotent diff --git a/powertools-e2e-tests/handlers/logging/pom.xml b/powertools-e2e-tests/handlers/logging/pom.xml index 05e91d8e3..222c5ab2e 100644 --- a/powertools-e2e-tests/handlers/logging/pom.xml +++ b/powertools-e2e-tests/handlers/logging/pom.xml @@ -5,7 +5,7 @@ software.amazon.lambda e2e-test-handlers-parent - 1.0.0 + 2.0.0-SNAPSHOT e2e-test-handler-logging diff --git a/powertools-e2e-tests/handlers/metrics/pom.xml b/powertools-e2e-tests/handlers/metrics/pom.xml index 2f3cabd16..68059e67e 100644 --- a/powertools-e2e-tests/handlers/metrics/pom.xml +++ b/powertools-e2e-tests/handlers/metrics/pom.xml @@ -5,12 +5,12 @@ software.amazon.lambda e2e-test-handlers-parent - 1.0.0 + 2.0.0-SNAPSHOT e2e-test-handler-metrics jar - A Lambda function using Powertools for AWS Lambda (Java) Parameters + A Lambda function using Powertools for AWS Lambda (Java) Metrics diff --git a/powertools-e2e-tests/handlers/parameters/pom.xml b/powertools-e2e-tests/handlers/parameters/pom.xml index 4d5330da0..328d30485 100644 --- a/powertools-e2e-tests/handlers/parameters/pom.xml +++ b/powertools-e2e-tests/handlers/parameters/pom.xml @@ -5,12 +5,12 @@ software.amazon.lambda e2e-test-handlers-parent - 1.0.0 + 2.0.0-SNAPSHOT e2e-test-handler-parameters jar - A Lambda function using powertools logging + A Lambda function using powertools parameters @@ -19,7 +19,7 @@ software.amazon.lambda - powertools-parameters + powertools-parameters-appconfig com.amazonaws diff --git a/powertools-e2e-tests/handlers/parameters/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/parameters/src/main/java/software/amazon/lambda/powertools/e2e/Function.java index 3a83a1b05..1e151825e 100644 --- a/powertools-e2e-tests/handlers/parameters/src/main/java/software/amazon/lambda/powertools/e2e/Function.java +++ b/powertools-e2e-tests/handlers/parameters/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -17,14 +17,18 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import software.amazon.lambda.powertools.logging.Logging; -import software.amazon.lambda.powertools.parameters.AppConfigProvider; -import software.amazon.lambda.powertools.parameters.ParamManager; +import software.amazon.lambda.powertools.parameters.appconfig.AppConfigProvider; public class Function implements RequestHandler { @Logging public String handleRequest(Input input, Context context) { - AppConfigProvider provider = ParamManager.getAppConfigProvider(input.getEnvironment(), input.getApp()); + AppConfigProvider provider = AppConfigProvider.builder() + .withApplication(input.getApp()) + .withEnvironment(input.getEnvironment()) + .build(); + + //(input.getEnvironment(), input.getApp()); return provider.get(input.getKey()); } diff --git a/powertools-e2e-tests/handlers/pom.xml b/powertools-e2e-tests/handlers/pom.xml index 7c1208470..2c73de977 100644 --- a/powertools-e2e-tests/handlers/pom.xml +++ b/powertools-e2e-tests/handlers/pom.xml @@ -4,7 +4,7 @@ software.amazon.lambda e2e-test-handlers-parent - 1.0.0 + 2.0.0-SNAPSHOT pom Handlers for End-to-End tests Fake handlers that use Powertools for AWS Lambda (Java). @@ -27,12 +27,16 @@ + batch + largemessage + largemessage_idempotent logging tracing metrics idempotency parameters - validation + validation-alb-event + validation-apigw-event @@ -71,7 +75,7 @@ software.amazon.lambda - powertools-parameters + powertools-parameters-appconfig ${lambda.powertools.version} diff --git a/powertools-e2e-tests/handlers/tracing/pom.xml b/powertools-e2e-tests/handlers/tracing/pom.xml index b9240c356..b96fcef0a 100644 --- a/powertools-e2e-tests/handlers/tracing/pom.xml +++ b/powertools-e2e-tests/handlers/tracing/pom.xml @@ -5,7 +5,7 @@ software.amazon.lambda e2e-test-handlers-parent - 1.0.0 + 2.0.0-SNAPSHOT e2e-test-handler-tracing diff --git a/powertools-e2e-tests/handlers/validation-alb-event/pom.xml b/powertools-e2e-tests/handlers/validation-alb-event/pom.xml index 31570fe4e..be50094c1 100644 --- a/powertools-e2e-tests/handlers/validation-alb-event/pom.xml +++ b/powertools-e2e-tests/handlers/validation-alb-event/pom.xml @@ -5,7 +5,7 @@ software.amazon.lambda e2e-test-handlers-parent - 1.0.0 + 2.0.0-SNAPSHOT e2e-test-handler-validation-alb-event diff --git a/powertools-e2e-tests/handlers/validation-apigw-event/pom.xml b/powertools-e2e-tests/handlers/validation-apigw-event/pom.xml index 9129abc7d..f204a8a9f 100644 --- a/powertools-e2e-tests/handlers/validation-apigw-event/pom.xml +++ b/powertools-e2e-tests/handlers/validation-apigw-event/pom.xml @@ -5,7 +5,7 @@ software.amazon.lambda e2e-test-handlers-parent - 1.0.0 + 2.0.0-SNAPSHOT e2e-test-handler-validation-apigw-event diff --git a/powertools-parameters/pom.xml b/powertools-parameters/pom.xml index eb73962e1..1bc662457 100644 --- a/powertools-parameters/pom.xml +++ b/powertools-parameters/pom.xml @@ -28,77 +28,17 @@ Powertools for AWS Lambda (Java) library Parameters - - Set of utilities to retrieve parameters from Secrets Manager or SSM Parameter Store - - https://aws.amazon.com/lambda/ - - GitHub Issues - https://github.com/aws-powertools/powertools-lambda-java/issues - - - https://github.com/aws-powertools/powertools-lambda-java.git - - - - Powertools for AWS Lambda team - Amazon Web Services - https://aws.amazon.com/ - - - - - - ossrh - https://aws.oss.sonatype.org/content/repositories/snapshots - - + Set of utilities to retrieve parameters - common interface software.amazon.lambda powertools-common - - software.amazon.awssdk - ssm - - - software.amazon.awssdk - apache-client - - - software.amazon.awssdk - netty-nio-client - - - - - software.amazon.awssdk - secretsmanager - - - software.amazon.awssdk - apache-client - - - software.amazon.awssdk - netty-nio-client - - - software.amazon.awssdk url-connection-client - - software.amazon.awssdk - dynamodb - - - software.amazon.awssdk - appconfigdata - com.fasterxml.jackson.core diff --git a/powertools-parameters/powertools-parameters-appconfig/pom.xml b/powertools-parameters/powertools-parameters-appconfig/pom.xml new file mode 100644 index 000000000..3a3018c7c --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + + software.amazon.lambda + powertools-parent + 2.0.0-SNAPSHOT + ../../pom.xml + + + powertools-parameters-appconfig + Powertools for AWS Lambda (Java) library Parameters - AppConfig + AppConfig implementation for the Parameters module + + + + software.amazon.lambda + powertools-parameters + ${project.version} + + + + software.amazon.awssdk + appconfigdata + + + software.amazon.awssdk + apache-client + + + software.amazon.awssdk + netty-nio-client + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.apache.commons + commons-lang3 + test + + + org.assertj + assertj-core + test + + + org.aspectj + aspectjweaver + test + + + + + + + maven-surefire-plugin + 3.1.2 + + + eu-central-1 + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + \ No newline at end of file diff --git a/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParam.java b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParam.java new file mode 100644 index 000000000..eb959be76 --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParam.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.appconfig; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import software.amazon.lambda.powertools.parameters.transform.Transformer; + +/** + * Use this annotation to inject AWS AppConfig parameters into fields in your application. You + * can also use {@code AppConfigProviderBuilder} to obtain AppConfig values directly, rather than + * injecting them implicitly. + * Both {@code environment} and {@code application} fields are necessary. + * + * @see AppConfigProviderBuilder + * @see Powertools for AWS Lambda (Java) parameters documentation + * + *
+ * @AppConfigParam(key = "my-param", environment = "my-env", application = "my-app")
+ * String appConfigParam;
+ * 
+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface AppConfigParam { + String key(); + + /** + * Mandatory. Provide an environment to the {@link AppConfigProvider} + */ + String environment(); + + /** + * Mandatory. Provide an application to the {@link AppConfigProvider} + */ + String application(); + + /** + * Optional Provide a Transformer to transform the returned parameter values. + */ + Class transformer() default Transformer.class; +} diff --git a/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParametersAspect.java b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParametersAspect.java new file mode 100644 index 000000000..7ab4cf23e --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParametersAspect.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.appconfig; + +import java.util.function.BiFunction; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.FieldSignature; +import software.amazon.lambda.powertools.parameters.BaseParamAspect; + +/** + * Provides the AppConfig parameter aspect. This aspect is responsible for injecting + * parameters from AWS AppConfig into fields annotated with @AppConfigParam. See the + * README and Powertools for Lambda (Java) documentation for information on using this feature. + */ +@Aspect +public class AppConfigParametersAspect extends BaseParamAspect { + + private static BiFunction providerBuilder = + (String env, String app) -> AppConfigProvider.builder() + .withEnvironment(env) + .withApplication(app) + .build(); + + + @Pointcut("get(* *) && @annotation(appConfigParamAnnotation)") + public void getParam(AppConfigParam appConfigParamAnnotation) { + } + + @Around("getParam(appConfigParamAnnotation)") + public Object injectParam(final ProceedingJoinPoint joinPoint, final AppConfigParam appConfigParamAnnotation) { + + AppConfigProvider provider = providerBuilder.apply + (appConfigParamAnnotation.environment(), appConfigParamAnnotation.application()); + + return getAndTransform(appConfigParamAnnotation.key(), appConfigParamAnnotation.transformer(), provider, + (FieldSignature) joinPoint.getSignature()); + } + +} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/AppConfigProvider.java b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProvider.java similarity index 52% rename from powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/AppConfigProvider.java rename to powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProvider.java index 5a1e575dd..5fd272c9b 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/AppConfigProvider.java +++ b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProvider.java @@ -12,20 +12,16 @@ * */ -package software.amazon.lambda.powertools.parameters; +package software.amazon.lambda.powertools.parameters.appconfig; import java.util.HashMap; import java.util.Map; -import software.amazon.awssdk.core.SdkSystemSetting; -import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; -import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; -import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; -import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationRequest; import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationResponse; import software.amazon.awssdk.services.appconfigdata.model.StartConfigurationSessionRequest; -import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.ParamProvider; import software.amazon.lambda.powertools.parameters.cache.CacheManager; import software.amazon.lambda.powertools.parameters.transform.TransformationManager; @@ -50,8 +46,9 @@ public class AppConfigProvider extends BaseProvider { private final String environment; private final HashMap establishedSessions = new HashMap<>(); - AppConfigProvider(CacheManager cacheManager, AppConfigDataClient client, String environment, String application) { - super(cacheManager); + AppConfigProvider(CacheManager cacheManager, TransformationManager transformationManager, + AppConfigDataClient client, String environment, String application) { + super(cacheManager, transformationManager); this.client = client; this.application = application; this.environment = environment; @@ -60,12 +57,13 @@ public class AppConfigProvider extends BaseProvider { /** * Create a builder that can be used to configure and create a {@link AppConfigProvider}. * - * @return a new instance of {@link AppConfigProvider.Builder} + * @return a new instance of {@link AppConfigProviderBuilder} */ - public static AppConfigProvider.Builder builder() { - return new AppConfigProvider.Builder(); + public static AppConfigProviderBuilder builder() { + return new AppConfigProviderBuilder(); } + /** * Retrieve the parameter value from the AppConfig parameter store.
* @@ -128,102 +126,4 @@ private EstablishedSession(String nextSessionToken, String value) { } } - static class Builder { - private AppConfigDataClient client; - private CacheManager cacheManager; - private TransformationManager transformationManager; - private String environment; - private String application; - - /** - * Create a {@link AppConfigProvider} instance. - * - * @return a {@link AppConfigProvider} - */ - public AppConfigProvider build() { - if (cacheManager == null) { - throw new IllegalStateException("No CacheManager provided; please provide one"); - } - if (environment == null) { - throw new IllegalStateException("No environment provided; please provide one"); - } - if (application == null) { - throw new IllegalStateException("No application provided; please provide one"); - } - - // Create a AppConfigDataClient if we haven't been given one - if (client == null) { - client = AppConfigDataClient.builder() - .httpClientBuilder(UrlConnectionHttpClient.builder()) - .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) - .overrideConfiguration(ClientOverrideConfiguration.builder() - .putAdvancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX, - UserAgentConfigurator.getUserAgent(PARAMETERS)).build()) - .build(); - } - - AppConfigProvider provider = new AppConfigProvider(cacheManager, client, environment, application); - - if (transformationManager != null) { - provider.setTransformationManager(transformationManager); - } - return provider; - } - - /** - * Set custom {@link AppConfigProvider} to pass to the {@link AppConfigDataClient}.
- * Use it if you want to customize the region or any other part of the client. - * - * @param client Custom client - * @return the builder to chain calls (eg.
builder.withClient().build()
) - */ - public AppConfigProvider.Builder withClient(AppConfigDataClient client) { - this.client = client; - return this; - } - - /** - * Mandatory. Provide an environment to the {@link AppConfigProvider} - * - * @param environment the AppConfig environment - * @return the builder to chain calls (eg.
builder.withCacheManager().build()
) - */ - public AppConfigProvider.Builder withEnvironment(String environment) { - this.environment = environment; - return this; - } - - /** - * Mandatory. Provide an application to the {@link AppConfigProvider} - * - * @param application the application to pull configuration from - * @return the builder to chain calls (eg.
builder.withCacheManager().build()
) - */ - public AppConfigProvider.Builder withApplication(String application) { - this.application = application; - return this; - } - - /** - * Mandatory. Provide a CacheManager to the {@link AppConfigProvider} - * - * @param cacheManager the manager that will handle the cache of parameters - * @return the builder to chain calls (eg.
builder.withCacheManager().build()
) - */ - public AppConfigProvider.Builder withCacheManager(CacheManager cacheManager) { - this.cacheManager = cacheManager; - return this; - } - - /** - * Provide a transformationManager to the {@link AppConfigProvider} - * - * @param transformationManager the manager that will handle transformation of parameters - * @return the builder to chain calls (eg.
builder.withTransformationManager().build()
) - */ - public AppConfigProvider.Builder withTransformationManager(TransformationManager transformationManager) { - this.transformationManager = transformationManager; - return this; - } - } } diff --git a/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProviderBuilder.java b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProviderBuilder.java new file mode 100644 index 000000000..dadacb843 --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProviderBuilder.java @@ -0,0 +1,124 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.appconfig; + +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.ParamProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +/** + * Implements a {@link ParamProvider} on top of the AppConfig service. AppConfig provides + */ +public class AppConfigProviderBuilder { + private AppConfigDataClient client; + private CacheManager cacheManager; + private TransformationManager transformationManager; + private String environment; + private String application; + + /** + * Create a {@link AppConfigProvider} instance. + * + * @return a {@link AppConfigProvider} + */ + public AppConfigProvider build() { + if (cacheManager == null) { + cacheManager = new CacheManager(); + } + if (environment == null) { + throw new IllegalStateException("No environment provided; please provide one"); + } + if (application == null) { + throw new IllegalStateException("No application provided; please provide one"); + } + + // Create a AppConfigDataClient if we haven't been given one + if (client == null) { + client = AppConfigDataClient.builder() + .httpClientBuilder(UrlConnectionHttpClient.builder()) + .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .putAdvancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX, + UserAgentConfigurator.getUserAgent(BaseProvider.PARAMETERS)).build()) + .build(); + } + + return new AppConfigProvider(cacheManager, transformationManager, client, environment, application); + } + + /** + * Set custom {@link AppConfigDataClient} to pass to the {@link AppConfigProvider}.
+ * Use it if you want to customize the region or any other part of the client. + * + * @param client Custom client + * @return the builder to chain calls (eg.
builder.withClient().build()
) + */ + public AppConfigProviderBuilder withClient(AppConfigDataClient client) { + this.client = client; + return this; + } + + /** + * Mandatory. Provide an environment to the {@link AppConfigProvider} + * + * @param environment the AppConfig environment + * @return the builder to chain calls (eg.
builder.withCacheManager().build()
) + */ + public AppConfigProviderBuilder withEnvironment(String environment) { + this.environment = environment; + return this; + } + + /** + * Mandatory. Provide an application to the {@link AppConfigProvider} + * + * @param application the application to pull configuration from + * @return the builder to chain calls (eg.
builder.withCacheManager().build()
) + */ + public AppConfigProviderBuilder withApplication(String application) { + this.application = application; + return this; + } + + /** + * Provide a CacheManager to the {@link AppConfigProvider} + * + * @param cacheManager the manager that will handle the cache of parameters + * @return the builder to chain calls (eg.
builder.withCacheManager().build()
) + */ + public AppConfigProviderBuilder withCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + return this; + } + + /** + * Provide a transformationManager to the {@link AppConfigProvider} + * + * @param transformationManager the manager that will handle transformation of parameters + * @return the builder to chain calls (eg.
builder.withTransformationManager().build()
) + */ + public AppConfigProviderBuilder withTransformationManager(TransformationManager transformationManager) { + this.transformationManager = transformationManager; + return this; + } +} diff --git a/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParamAspectTest.java b/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParamAspectTest.java new file mode 100644 index 000000000..f50e88ec5 --- /dev/null +++ b/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigParamAspectTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.appconfig; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.BiFunction; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class AppConfigParamAspectTest { + + @Test + public void parameterInjectedByProvider() throws Exception { + // Setup our aspect to return a mocked AppConfigProvider + String environment = "myEnvironment"; + String appName = "myApp"; + String key = "myKey"; + String value = "myValue"; + AppConfigProvider provider = Mockito.mock(AppConfigProvider.class); + BiFunction providerBuilder = (String env, String app) -> { + if (env.equals(environment) && app.equals(appName)) { + return provider; + } + throw new RuntimeException("Whoops! Asked for an app/env that we weren't configured for"); + }; + writeStaticField(AppConfigParametersAspect.class, "providerBuilder", providerBuilder, true); + + // Setup our mocked AppConfigProvider to return a value for our test data + Mockito.when(provider.get(key)).thenReturn(value); + + // Create an instance of a class and let the AppConfigParametersAspect inject it + MyInjectedClass obj = new MyInjectedClass(); + assertThat(obj.myParameter).isEqualTo(value); + } + + class MyInjectedClass { + @AppConfigParam(application = "myApp", environment = "myEnvironment", key = "myKey") + public String myParameter; + } + +} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/AppConfigProviderTest.java b/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProviderTest.java similarity index 95% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/AppConfigProviderTest.java rename to powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProviderTest.java index f467dca72..ded568e8d 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/AppConfigProviderTest.java +++ b/powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProviderTest.java @@ -12,11 +12,11 @@ * */ -package software.amazon.lambda.powertools.parameters; +package software.amazon.lambda.powertools.parameters.appconfig; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatRuntimeException; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.MockitoAnnotations.openMocks; import org.junit.jupiter.api.BeforeEach; @@ -193,18 +193,6 @@ public void getMultipleValuesThrowsException() { .withMessage("Retrieving multiple parameter values is not supported with the AWS App Config Provider"); } - @Test - public void testAppConfigProviderBuilderMissingCacheManager_throwsException() { - - // Act & Assert - assertThatIllegalStateException().isThrownBy(() -> AppConfigProvider.builder() - .withEnvironment(environmentName) - .withApplication(applicationName) - .withClient(client) - .build()) - .withMessage("No CacheManager provided; please provide one"); - } - @Test public void testAppConfigProviderBuilderMissingEnvironment_throwsException() { diff --git a/powertools-parameters/powertools-parameters-dynamodb/pom.xml b/powertools-parameters/powertools-parameters-dynamodb/pom.xml new file mode 100644 index 000000000..ad32bdff8 --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + + software.amazon.lambda + powertools-parent + 2.0.0-SNAPSHOT + ../../pom.xml + + + powertools-parameters-dynamodb + Powertools for AWS Lambda (Java) library Parameters - DynamoDB + DynamoDB implementation for the Parameters module + + + + software.amazon.lambda + powertools-parameters + ${project.version} + + + + software.amazon.awssdk + dynamodb + + + software.amazon.awssdk + apache-client + + + software.amazon.awssdk + netty-nio-client + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.apache.commons + commons-lang3 + test + + + org.assertj + assertj-core + test + + + org.aspectj + aspectjweaver + test + + + + + + + maven-surefire-plugin + 3.1.2 + + + eu-central-1 + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + \ No newline at end of file diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParam.java similarity index 52% rename from powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java rename to powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParam.java index 7ffb0310c..946786cb4 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/Param.java +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParam.java @@ -12,7 +12,7 @@ * */ -package software.amazon.lambda.powertools.parameters; +package software.amazon.lambda.powertools.parameters.dynamodb; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -21,25 +21,31 @@ import software.amazon.lambda.powertools.parameters.transform.Transformer; /** - * {@code Param} is used to signal that the annotated field should be - * populated with a value retrieved from a parameter store through a {@link ParamProvider}. + * Inject a parameter from the DynamoDB Parameter Store into a field. You can also use + * {@code DynamoDbProviderBuilder} to obtain DynamoDB values directly, rather than injecting them implicitly. * - *

By default {@code Param} use {@link SSMProvider} as parameter provider. This can be overridden specifying - * the annotation variable {@code Param(provider = )}.
- * The library provide a provider for AWS System Manager Parameters Store ({@link SSMProvider}) and a provider - * for AWS Secrets Manager ({@link SecretsProvider}). - * The user can implement a custom provider by extending the abstract class {@link BaseProvider}.

- * - *

If the parameter value requires transformation before being assigned to the annotated field - * users can specify a {@link Transformer} - *

+ * Usage: + *
+ * @DynamoDbParam(key = "my-param", table = "my-table")
+ * String myParameter;
+ * 
*/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) -public @interface Param { +public @interface DynamoDbParam { + /** + * Mandatory. Partition key from the DynamoDB table + */ String key(); - Class provider() default SSMProvider.class; + /** + * Mandatory. Table name for the DynamoDB table + * @return + */ + String table(); + /** + * Optional Provide a Transformer to transform the returned parameter values. + */ Class transformer() default Transformer.class; } diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParamAspect.java b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParamAspect.java new file mode 100644 index 000000000..1aa022cbd --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParamAspect.java @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.dynamodb; + +import java.util.function.Function; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.FieldSignature; +import software.amazon.lambda.powertools.parameters.BaseParamAspect; +import software.amazon.lambda.powertools.parameters.BaseProvider; + +/** + * Provides the Amazon DynamoDB parameter aspect. This aspect is responsible for injecting + * parameters from DynamoDB into fields annotated with @DynamoDbParam. See the + * README and Powertools for Lambda (Java) documentation for information on using this feature. + */ +@Aspect +public class DynamoDbParamAspect extends BaseParamAspect { + + private static Function providerBuilder = + (String table) -> DynamoDbProvider.builder() + .withTable(table) + .build(); + + @Pointcut("get(* *) && @annotation(ddbConfigParam)") + public void getParam(DynamoDbParam ddbConfigParam) { + } + + @Around("getParam(ddbConfigParam)") + public Object injectParam(final ProceedingJoinPoint joinPoint, final DynamoDbParam ddbConfigParam) { + + BaseProvider provider = providerBuilder.apply(ddbConfigParam.table()); + return getAndTransform(ddbConfigParam.key(), ddbConfigParam.transformer(), provider, + (FieldSignature) joinPoint.getSignature()); + } + +} diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProvider.java b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProvider.java new file mode 100644 index 000000000..4a1476e38 --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProvider.java @@ -0,0 +1,121 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.dynamodb; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.QueryResponse; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.ParamProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.dynamodb.exception.DynamoDbProviderSchemaException; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +/** + * Implements a {@link ParamProvider} on top of Amazon DynamoDB. The schema of the table + * is described in the Powertools for AWS Lambda (Java) documentation. + * + * @see Parameters provider documentation + */ +public class DynamoDbProvider extends BaseProvider { + + private final DynamoDbClient client; + private final String tableName; + + DynamoDbProvider(CacheManager cacheManager, TransformationManager transformationManager, DynamoDbClient client, + String tableName) { + super(cacheManager, transformationManager); + this.client = client; + this.tableName = tableName; + } + + /** + * Create a builder that can be used to configure and create a {@link DynamoDbProvider}. + * + * @return a new instance of {@link DynamoDbProviderBuilder} + */ + public static DynamoDbProviderBuilder builder() { + return new DynamoDbProviderBuilder(); + } + + /** + * Return a single value from the DynamoDB parameter provider. + * + * @param key key of the parameter + * @return The value, if it exists, null if it doesn't. Throws if the row exists but doesn't match the schema. + */ + @Override + protected String getValue(String key) { + GetItemResponse resp = client.getItem(GetItemRequest.builder() + .tableName(tableName) + .key(Collections.singletonMap("id", AttributeValue.fromS(key))) + .attributesToGet("value") + .build()); + + // If we have an item at the key, we should be able to get a 'val' out of it. If not it's + // exceptional. + // If we don't have an item at the key, we should return null. + if (resp.hasItem() && !resp.item().values().isEmpty()) { + if (!resp.item().containsKey("value")) { + throw new DynamoDbProviderSchemaException("Missing 'value': " + resp.item()); + } + return resp.item().get("value").s(); + } + + return null; + } + + /** + * Returns multiple values from the DynamoDB parameter provider. + * + * @param path Parameter store path + * @return All values matching the given path, and an empty map if none do. Throws if any records exist that don't match the schema. + */ + @Override + protected Map getMultipleValues(String path) { + + QueryResponse resp = client.query(QueryRequest.builder() + .tableName(tableName) + .keyConditionExpression("id = :v_id") + .expressionAttributeValues(Collections.singletonMap(":v_id", AttributeValue.fromS(path))) + .build()); + + return resp + .items() + .stream() + .peek((i) -> + { + if (!i.containsKey("sk")) { + throw new DynamoDbProviderSchemaException("Missing 'sk': " + i); + } + if (!i.containsKey("value")) { + throw new DynamoDbProviderSchemaException("Missing 'value': " + i); + } + }) + .collect( + Collectors.toMap( + (i) -> i.get("sk").s(), + (i) -> i.get("value").s())); + + + } + +} diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderBuilder.java b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderBuilder.java new file mode 100644 index 000000000..6b6610ba1 --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderBuilder.java @@ -0,0 +1,113 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.dynamodb; + +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.ParamProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +/** + * Implements a {@link ParamProvider} on top of the DynamoDB service. DynamoDB provides + */ +public class DynamoDbProviderBuilder { + private DynamoDbClient client; + private String table; + private CacheManager cacheManager; + private TransformationManager transformationManager; + + static DynamoDbClient createClient() { + return DynamoDbClient.builder() + .httpClientBuilder(UrlConnectionHttpClient.builder()) + .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .putAdvancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX, + UserAgentConfigurator.getUserAgent(BaseProvider.PARAMETERS)).build()) + .build(); + } + + /** + * Create a {@link DynamoDbProvider} instance. + * + * @return a {@link DynamoDbProvider} + */ + public DynamoDbProvider build() { + if (cacheManager == null) { + cacheManager = new CacheManager(); + } + if (table == null) { + throw new IllegalStateException("No DynamoDB table name provided; please provide one"); + } + DynamoDbProvider provider; + if (client == null) { + client = createClient(); + } + provider = new DynamoDbProvider(cacheManager, transformationManager, client, table); + + return provider; + } + + /** + * Set custom {@link DynamoDbClient} to pass to the {@link DynamoDbClient}.
+ * Use it if you want to customize the region or any other part of the client. + * + * @param client Custom client + * @return the builder to chain calls (eg.
builder.withClient().build()
) + */ + public DynamoDbProviderBuilder withClient(DynamoDbClient client) { + this.client = client; + return this; + } + + /** + * Provide a CacheManager to the {@link DynamoDbProvider} + * + * @param cacheManager the manager that will handle the cache of parameters + * @return the builder to chain calls (eg.
builder.withCacheManager().build()
) + */ + public DynamoDbProviderBuilder withCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + return this; + } + + /** + * Mandatory. Provide a DynamoDB table to the {@link DynamoDbProvider} + * + * @param table the table that parameters will be retrieved from. + * @return the builder to chain calls (eg.
builder.withTable().build()
) + */ + public DynamoDbProviderBuilder withTable(String table) { + this.table = table; + return this; + } + + /** + * Provide a transformationManager to the {@link DynamoDbProvider} + * + * @param transformationManager the manager that will handle transformation of parameters + * @return the builder to chain calls (eg.
builder.withTransformationManager().build()
) + */ + public DynamoDbProviderBuilder withTransformationManager(TransformationManager transformationManager) { + this.transformationManager = transformationManager; + return this; + } +} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/DynamoDbProviderSchemaException.java b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/exception/DynamoDbProviderSchemaException.java similarity index 92% rename from powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/DynamoDbProviderSchemaException.java rename to powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/exception/DynamoDbProviderSchemaException.java index 77df6e3d3..4a22dbc99 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/exception/DynamoDbProviderSchemaException.java +++ b/powertools-parameters/powertools-parameters-dynamodb/src/main/java/software/amazon/lambda/powertools/parameters/dynamodb/exception/DynamoDbProviderSchemaException.java @@ -12,7 +12,7 @@ * */ -package software.amazon.lambda.powertools.parameters.exception; +package software.amazon.lambda.powertools.parameters.dynamodb.exception; /** * Thrown when the DynamoDbProvider comes across parameter data that diff --git a/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParamAspectTest.java b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParamAspectTest.java new file mode 100644 index 000000000..07e93a7c1 --- /dev/null +++ b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbParamAspectTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.dynamodb; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Function; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class DynamoDbParamAspectTest { + + @Test + public void parameterInjectedByProvider() throws Exception { + // Setup our aspect to return a mocked DynamoDbProvider + String tableName = "my-test-tablename"; + String key = "myKey"; + String value = "myValue"; + DynamoDbProvider provider = Mockito.mock(DynamoDbProvider.class); + + Function providerBuilder = (String table) -> { + if (table.equals(tableName)) { + return provider; + } + throw new RuntimeException("Whoops! Asked for an app/env that we weren't configured for"); + }; + writeStaticField(DynamoDbParamAspect.class, "providerBuilder", providerBuilder, true); + + // Setup our mocked DynamoDbProvider to return a value for our test data + Mockito.when(provider.get(key)).thenReturn(value); + + // Create an instance of a class and let the AppConfigParametersAspect inject it + MyInjectedClass obj = new MyInjectedClass(); + assertThat(obj.myParameter).isEqualTo(value); + } + + class MyInjectedClass { + @DynamoDbParam(table = "my-test-tablename", key = "myKey") + public String myParameter; + } + +} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/DynamoDbProviderE2ETest.java b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderE2ETest.java similarity index 96% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/DynamoDbProviderE2ETest.java rename to powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderE2ETest.java index 18212b45c..2695938d8 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/DynamoDbProviderE2ETest.java +++ b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderE2ETest.java @@ -12,7 +12,7 @@ * */ -package software.amazon.lambda.powertools.parameters; +package software.amazon.lambda.powertools.parameters.dynamodb; import static org.assertj.core.api.Assertions.assertThat; @@ -104,7 +104,7 @@ public void TestGetValues() { } private DynamoDbProvider makeProvider(String tableName) { - return new DynamoDbProvider(new CacheManager(), DynamoDbClient.builder() + return new DynamoDbProvider(new CacheManager(), null, DynamoDbClient.builder() .httpClientBuilder(UrlConnectionHttpClient.builder()).build(), tableName); } diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/DynamoDbProviderTest.java b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderTest.java similarity index 94% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/DynamoDbProviderTest.java rename to powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderTest.java index 2cf84dad4..68bfd7cdb 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/DynamoDbProviderTest.java +++ b/powertools-parameters/powertools-parameters-dynamodb/src/test/java/software/amazon/lambda/powertools/parameters/dynamodb/DynamoDbProviderTest.java @@ -12,7 +12,7 @@ * */ -package software.amazon.lambda.powertools.parameters; +package software.amazon.lambda.powertools.parameters.dynamodb; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -35,7 +35,7 @@ import software.amazon.awssdk.services.dynamodb.model.QueryRequest; import software.amazon.awssdk.services.dynamodb.model.QueryResponse; import software.amazon.lambda.powertools.parameters.cache.CacheManager; -import software.amazon.lambda.powertools.parameters.exception.DynamoDbProviderSchemaException; +import software.amazon.lambda.powertools.parameters.dynamodb.exception.DynamoDbProviderSchemaException; import software.amazon.lambda.powertools.parameters.transform.TransformationManager; public class DynamoDbProviderTest { @@ -55,7 +55,7 @@ public class DynamoDbProviderTest { public void init() { openMocks(this); CacheManager cacheManager = new CacheManager(); - provider = new DynamoDbProvider(cacheManager, client, tableName); + provider = new DynamoDbProvider(cacheManager, transformationManager, client, tableName); } @@ -217,15 +217,6 @@ public void getValuesWithMalformedRowThrows() { }); } - @Test - public void testDynamoDBBuilderMissingCacheManager_throwsException() { - - // Act & Assert - assertThatIllegalStateException().isThrownBy(() -> DynamoDbProvider.builder() - .withTable("table") - .build()); - } - @Test public void testDynamoDBBuilderMissingTable_throwsException() { diff --git a/powertools-parameters/powertools-parameters-secrets/pom.xml b/powertools-parameters/powertools-parameters-secrets/pom.xml new file mode 100644 index 000000000..6c4501ca3 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + + software.amazon.lambda + powertools-parent + 2.0.0-SNAPSHOT + ../../pom.xml + + + powertools-parameters-secrets + Powertools for AWS Lambda (Java) library Parameters - Secrets Manager + Secrets Manager implementation for the Parameters module + + + + software.amazon.lambda + powertools-parameters + ${project.version} + + + + software.amazon.awssdk + secretsmanager + + + software.amazon.awssdk + apache-client + + + software.amazon.awssdk + netty-nio-client + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.apache.commons + commons-lang3 + test + + + org.assertj + assertj-core + test + + + org.aspectj + aspectjweaver + test + + + + + + + maven-surefire-plugin + 3.1.2 + + + eu-central-1 + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + \ No newline at end of file diff --git a/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParam.java b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParam.java new file mode 100644 index 000000000..f9c110c49 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParam.java @@ -0,0 +1,46 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.secrets; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import software.amazon.lambda.powertools.parameters.transform.Transformer; + +/** + * Inject a parameter from the Secrets Manager into a field. You can also use + * {@code SecretsProviderBuilder} to obtain Secrets Manager values directly, rather than + * injecting them implicitly. + * + *
+ * @SecretsParam(key = "my-secret")
+ * String mySecret;
+ * 
+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SecretsParam { + /** + * Mandatory. key from the secrets manager store. + * @return + */ + String key(); + + /** + * Optional. a transfer to apply to the value + */ + Class transformer() default Transformer.class; +} diff --git a/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParamAspect.java b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParamAspect.java new file mode 100644 index 000000000..748c88cc9 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParamAspect.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.secrets; + +import java.util.function.Supplier; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.FieldSignature; +import software.amazon.lambda.powertools.parameters.BaseParamAspect; + +/** + * Provides the Secrets parameter aspect. This aspect is responsible for injecting + * parameters from AWS Secrets Manager into fields annotated with @SecretsParam. See the + * README and Powertools for Lambda (Java) documentation for information on using this feature. + */ +@Aspect +public class SecretsParamAspect extends BaseParamAspect { + + private static Supplier providerBuilder = () -> SecretsProvider.builder() + .build(); + + @Pointcut("get(* *) && @annotation(secretsParam)") + public void getParam(SecretsParam secretsParam) { + } + + @Around("getParam(secretsParam)") + public Object injectParam(final ProceedingJoinPoint joinPoint, final SecretsParam secretsParam) { + + SecretsProvider provider = providerBuilder.get(); + return getAndTransform(secretsParam.key(), secretsParam.transformer(), provider, + (FieldSignature) joinPoint.getSignature()); + } + +} diff --git a/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProvider.java b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProvider.java new file mode 100644 index 000000000..9087c1ad6 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProvider.java @@ -0,0 +1,115 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.secrets; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.util.Base64; +import java.util.Map; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; +import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +/** + * AWS Secrets Manager Parameter Provider

+ * + * Samples: + *
+ *     SecretsProvider provider = SecretsProvider.builder().build();
+ *
+ *     String value = provider.get("key");
+ *     System.out.println(value);
+ *     >>> "value"
+ *
+ *     // Get a value and cache it for 30 seconds (all others values will now be cached for 30 seconds)
+ *     String value = provider.defaultMaxAge(30, ChronoUnit.SECONDS).get("key");
+ *
+ *     // Get a value and cache it for 1 minute (all others values are cached for 5 seconds by default)
+ *     String value = provider.withMaxAge(1, ChronoUnit.MINUTES).get("key");
+ *
+ *     // Get a base64 encoded value, decoded into a String, and store it in the cache
+ *     String value = provider.withTransformation(Transformer.base64).get("key");
+ *
+ *     // Get a json value, transform it into an Object, and store it in the cache
+ *     TargetObject = provider.withTransformation(Transformer.json).get("key", TargetObject.class);
+ * 
+ */ +public class SecretsProvider extends BaseProvider { + + private final SecretsManagerClient client; + + /** + * Use the {@link SecretsProviderBuilder} to create an instance! + * + * @param client custom client you would like to use. + */ + SecretsProvider(CacheManager cacheManager, TransformationManager transformationManager, + SecretsManagerClient client) { + super(cacheManager, transformationManager); + this.client = client; + } + + /** + * Create a builder that can be used to configure and create a {@link SecretsProvider}. + * + * @return a new instance of {@link SecretsProviderBuilder} + */ + public static SecretsProviderBuilder builder() { + return new SecretsProviderBuilder(); + } + + /** + * Create a SecretsProvider with all default settings. + */ + public static SecretsProvider create() { + return new SecretsProviderBuilder().build(); + } + + /** + * Retrieve the parameter value from the AWS Secrets Manager. + * + * @param key key of the parameter + * @return the value of the parameter identified by the key + */ + @Override + protected String getValue(String key) { + GetSecretValueRequest request = GetSecretValueRequest.builder().secretId(key).build(); + + String secretValue = client.getSecretValue(request).secretString(); + if (secretValue == null) { + secretValue = + new String(Base64.getDecoder().decode(client.getSecretValue(request).secretBinary().asByteArray()), + UTF_8); + } + return secretValue; + } + + /** + * @throws UnsupportedOperationException as it is not possible to get multiple values simultaneously from Secrets Manager + */ + @Override + protected Map getMultipleValues(String path) { + throw new UnsupportedOperationException("Impossible to get multiple values from AWS Secrets Manager"); + } + + + // For test purpose only + SecretsManagerClient getClient() { + return client; + } + +} diff --git a/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProviderBuilder.java b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProviderBuilder.java new file mode 100644 index 000000000..125425200 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/main/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProviderBuilder.java @@ -0,0 +1,101 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.secrets; + +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.ParamProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +/** + * Implements a {@link ParamProvider} on top of the SecretsManager service. SecretsManager provides + */ +public class SecretsProviderBuilder { + + private SecretsManagerClient client; + private CacheManager cacheManager; + private TransformationManager transformationManager; + + private static SecretsManagerClient createClient() { + return SecretsManagerClient.builder() + .httpClientBuilder(UrlConnectionHttpClient.builder()) + .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .putAdvancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX, + UserAgentConfigurator.getUserAgent(BaseProvider.PARAMETERS)).build()) + .build(); + } + + /** + * Create a {@link SecretsProvider} instance. + * + * @return a {@link SecretsProvider} + */ + public SecretsProvider build() { + if (cacheManager == null) { + // TODO - what should we do with this + cacheManager = new CacheManager(); + } + SecretsProvider provider; + if (client == null) { + client = createClient(); + } + + provider = new SecretsProvider(cacheManager, transformationManager, client); + + return provider; + } + + /** + * Set custom {@link SecretsManagerClient} to pass to the {@link SecretsProvider}.
+ * Use it if you want to customize the region or any other part of the client. + * + * @param client Custom client + * @return the builder to chain calls (eg.
builder.withClient().build()
) + */ + public SecretsProviderBuilder withClient(SecretsManagerClient client) { + this.client = client; + return this; + } + + /** + * Provide a CacheManager to the {@link SecretsProvider} + * + * @param cacheManager the manager that will handle the cache of parameters + * @return the builder to chain calls (eg.
builder.withCacheManager().build()
) + */ + public SecretsProviderBuilder withCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + return this; + } + + /** + * Provide a transformationManager to the {@link SecretsProvider} + * + * @param transformationManager the manager that will handle transformation of parameters + * @return the builder to chain calls (eg.
builder.withTransformationManager().build()
) + */ + public SecretsProviderBuilder withTransformationManager(TransformationManager transformationManager) { + this.transformationManager = transformationManager; + return this; + } +} diff --git a/powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParamAspectTest.java b/powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParamAspectTest.java new file mode 100644 index 000000000..7aa0f0872 --- /dev/null +++ b/powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/SecretsParamAspectTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.secrets; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Supplier; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class SecretsParamAspectTest { + + @Test + public void parameterInjectedByProvider() throws Exception { + // Setup our aspect to return a mocked SecretsProvider + String key = "myKey"; + String value = "mySecretValue"; + SecretsProvider provider = Mockito.mock(SecretsProvider.class); + + Supplier providerBuilder = () -> provider; + writeStaticField(SecretsParamAspect.class, "providerBuilder", providerBuilder, true); + + // Setup our mocked SecretsProvider to return a value for our test data + Mockito.when(provider.get(key)).thenReturn(value); + + // Create an instance of a class and let the SecretsParamAspect inject it + MyInjectedClass obj = new MyInjectedClass(); + assertThat(obj.mySecret).isEqualTo(value); + } + + class MyInjectedClass { + @SecretsParam(key = "myKey") + public String mySecret; + } + +} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SecretsProviderTest.java b/powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProviderTest.java similarity index 81% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SecretsProviderTest.java rename to powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProviderTest.java index f4f2d9bee..21173cad1 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SecretsProviderTest.java +++ b/powertools-parameters/powertools-parameters-secrets/src/test/java/software/amazon/lambda/powertools/parameters/secrets/SecretsProviderTest.java @@ -12,12 +12,11 @@ * */ -package software.amazon.lambda.powertools.parameters; +package software.amazon.lambda.powertools.parameters.secrets; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatRuntimeException; -import static org.mockito.MockitoAnnotations.openMocks; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.time.temporal.ChronoUnit; import java.util.Base64; @@ -27,6 +26,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; @@ -51,9 +51,9 @@ public class SecretsProviderTest { @BeforeEach public void init() { - openMocks(this); + MockitoAnnotations.openMocks(this); cacheManager = new CacheManager(); - provider = new SecretsProvider(cacheManager, client); + provider = new SecretsProvider(cacheManager, transformationManager, client); } @Test @@ -62,7 +62,6 @@ public void getValue() { String expectedValue = "Value1"; GetSecretValueResponse response = GetSecretValueResponse.builder().secretString(expectedValue).build(); Mockito.when(client.getSecretValue(paramCaptor.capture())).thenReturn(response); - provider.defaultMaxAge(1, ChronoUnit.DAYS); provider.withMaxAge(2, ChronoUnit.DAYS); String value = provider.getValue(key); @@ -96,13 +95,14 @@ public void getMultipleValuesThrowsException() { } @Test - public void testSecretsProviderBuilderMissingCacheManager_throwsException() { + public void testGetSecretsProvider_withoutParameter_shouldCreateDefaultClient() { - // Act & Assert - assertThatIllegalStateException().isThrownBy(() -> SecretsProvider.builder() - .withClient(client) - .withTransformationManager(transformationManager) - .build()) - .withMessage("No CacheManager provided, please provide one"); + // Act + SecretsProvider secretsProvider = SecretsProvider.builder() + .build(); + + // Assert + assertNotNull(secretsProvider); + assertNotNull(secretsProvider.getClient()); } } diff --git a/powertools-parameters/powertools-parameters-ssm/pom.xml b/powertools-parameters/powertools-parameters-ssm/pom.xml new file mode 100644 index 000000000..80276189d --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + + software.amazon.lambda + powertools-parent + 2.0.0-SNAPSHOT + ../../pom.xml + + + powertools-parameters-ssm + Powertools for AWS Lambda (Java) library Parameters - SSM + SSM Parameter Store implementation for the Parameters module + + + + software.amazon.lambda + powertools-parameters + ${project.version} + + + + software.amazon.awssdk + ssm + + + software.amazon.awssdk + apache-client + + + software.amazon.awssdk + netty-nio-client + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.apache.commons + commons-lang3 + test + + + org.assertj + assertj-core + test + + + org.aspectj + aspectjweaver + test + + + + + + + maven-surefire-plugin + 3.1.2 + + + eu-central-1 + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + \ No newline at end of file diff --git a/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMParam.java b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMParam.java new file mode 100644 index 000000000..9b1587bb4 --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMParam.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.ssm; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import software.amazon.lambda.powertools.parameters.transform.Transformer; + +/** + * Inject a parameter from the SSM Parameter Store into a field. You can also use + * {@code SSMProviderBuilder} to obtain SSM values directly, rather than injecting them implicitly. + * + * Usage: + *
+ * @SSMParam(key = "/my/parameter")
+ * String myParameter;
+ * 
+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SSMParam { + /** + * Mandatory. Key from the SSM parameter store + * @return + */ + String key(); + + + /** + * Optional. a transfer to apply to the value + */ + Class transformer() default Transformer.class; +} diff --git a/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMParamAspect.java b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMParamAspect.java new file mode 100644 index 000000000..b4d370506 --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMParamAspect.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.ssm; + +import java.util.function.Supplier; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.FieldSignature; +import software.amazon.lambda.powertools.parameters.BaseParamAspect; + +/** + * Provides the SSM parameter store parameter aspect. This aspect is responsible for injecting + * parameters from SSM Parameter Store into fields annotated with @SSMParam. See the + * README and Powertools for Lambda (Java) documentation for information on using this feature. + */ +@Aspect +public class SSMParamAspect extends BaseParamAspect { + + // This supplier produces a new SSMProvider each time it is called + private static Supplier providerBuilder = () -> SSMProvider.builder() + .build(); + + @Pointcut("get(* *) && @annotation(secretsParam)") + public void getParam(SSMParam secretsParam) { + } + + @Around("getParam(ssmPaam)") + public Object injectParam(final ProceedingJoinPoint joinPoint, final SSMParam ssmPaam) { + + SSMProvider provider = providerBuilder.get(); + return getAndTransform(ssmPaam.key(), ssmPaam.transformer(), provider, + (FieldSignature) joinPoint.getSignature()); + } + +} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SSMProvider.java b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMProvider.java similarity index 60% rename from powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SSMProvider.java rename to powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMProvider.java index 10bb70c15..c24b08b84 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SSMProvider.java +++ b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMProvider.java @@ -12,32 +12,25 @@ * */ -package software.amazon.lambda.powertools.parameters; +package software.amazon.lambda.powertools.parameters.ssm; -import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.Map; -import software.amazon.awssdk.core.SdkSystemSetting; -import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; -import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; -import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; -import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.GetParameterRequest; import software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest; import software.amazon.awssdk.services.ssm.model.GetParametersByPathResponse; import software.amazon.awssdk.utils.StringUtils; -import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; +import software.amazon.lambda.powertools.parameters.BaseProvider; import software.amazon.lambda.powertools.parameters.cache.CacheManager; import software.amazon.lambda.powertools.parameters.transform.TransformationManager; -import software.amazon.lambda.powertools.parameters.transform.Transformer; /** * AWS System Manager Parameter Store Provider

* * Samples: *
- *     SSMProvider provider = ParamManager.getSsmProvider();
+ *     SSMProvider provider = SSMProvider.builder().build();
  *
  *     String value = provider.get("key");
  *     System.out.println(value);
@@ -80,33 +73,30 @@ public class SSMProvider extends BaseProvider {
      * Constructor with custom {@link SsmClient}. 
* Use when you need to customize region or any other attribute of the client.

*

- * Use the {@link SSMProvider.Builder} to create an instance of it. + * Use the {@link SSMProviderBuilder} to create an instance of it. * - * @param client custom client you would like to use. + * @param client custom client you would like to use. + * @param transformationManager Null, or a transformation manager */ - SSMProvider(CacheManager cacheManager, SsmClient client) { - super(cacheManager); + SSMProvider(CacheManager cacheManager, TransformationManager transformationManager, SsmClient client) { + super(cacheManager, transformationManager); this.client = client; } /** - * Constructor with only a CacheManager
- *

- * Used in {@link ParamManager#createProvider(Class)} + * Create a builder that can be used to configure and create a {@link SSMProvider}. * - * @param cacheManager handles the parameter caching + * @return a new instance of {@link SSMProviderBuilder} */ - SSMProvider(CacheManager cacheManager) { - this(cacheManager, Builder.createClient()); + public static SSMProviderBuilder builder() { + return new SSMProviderBuilder(); } /** - * Create a builder that can be used to configure and create a {@link SSMProvider}. - * - * @return a new instance of {@link SSMProvider.Builder} + * Create a SSMProvider with all default settings. */ - public static SSMProvider.Builder builder() { - return new SSMProvider.Builder(); + public static SSMProvider create() { + return new SSMProviderBuilder().build(); } /** @@ -124,33 +114,6 @@ public String getValue(String key) { return client.getParameter(request).parameter().value(); } - /** - * {@inheritDoc} - */ - @Override - public SSMProvider defaultMaxAge(int maxAge, ChronoUnit unit) { - super.defaultMaxAge(maxAge, unit); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public SSMProvider withMaxAge(int maxAge, ChronoUnit unit) { - super.withMaxAge(maxAge, unit); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public SSMProvider withTransformation(Class transformerClass) { - super.withTransformation(transformerClass); - return this; - } - /** * Tells System Manager Parameter Store to decrypt the parameter value.
* By default, parameter values are not decrypted.
@@ -210,7 +173,7 @@ private Map getMultipleBis(String path, String nextToken) { res.parameters().forEach(parameter -> { /* Standardize the parameter name - The parameter name returned by SSM will contained the full path. + The parameter name returned by SSM will contain the full path. However, for readability, we should return only the part after the path. */ @@ -242,75 +205,4 @@ SsmClient getClient() { return client; } - static class Builder { - private SsmClient client; - private CacheManager cacheManager; - private TransformationManager transformationManager; - - private static SsmClient createClient() { - return SsmClient.builder() - .httpClientBuilder(UrlConnectionHttpClient.builder()) - .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) - .overrideConfiguration(ClientOverrideConfiguration.builder() - .putAdvancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX, - UserAgentConfigurator.getUserAgent(PARAMETERS)).build()) - .build(); - } - - /** - * Create a {@link SSMProvider} instance. - * - * @return a {@link SSMProvider} - */ - public SSMProvider build() { - if (cacheManager == null) { - throw new IllegalStateException("No CacheManager provided, please provide one"); - } - SSMProvider provider; - if (client == null) { - client = createClient(); - } - - provider = new SSMProvider(cacheManager, client); - - if (transformationManager != null) { - provider.setTransformationManager(transformationManager); - } - return provider; - } - - /** - * Set custom {@link SsmClient} to pass to the {@link SSMProvider}.
- * Use it if you want to customize the region or any other part of the client. - * - * @param client Custom client - * @return the builder to chain calls (eg.

builder.withClient().build()
) - */ - public SSMProvider.Builder withClient(SsmClient client) { - this.client = client; - return this; - } - - /** - * Mandatory. Provide a CacheManager to the {@link SSMProvider} - * - * @param cacheManager the manager that will handle the cache of parameters - * @return the builder to chain calls (eg.
builder.withCacheManager().build()
) - */ - public SSMProvider.Builder withCacheManager(CacheManager cacheManager) { - this.cacheManager = cacheManager; - return this; - } - - /** - * Provide a transformationManager to the {@link SSMProvider} - * - * @param transformationManager the manager that will handle transformation of parameters - * @return the builder to chain calls (eg.
builder.withTransformationManager().build()
) - */ - public SSMProvider.Builder withTransformationManager(TransformationManager transformationManager) { - this.transformationManager = transformationManager; - return this; - } - } } diff --git a/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMProviderBuilder.java b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMProviderBuilder.java new file mode 100644 index 000000000..3b4fff1b3 --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/main/java/software/amazon/lambda/powertools/parameters/ssm/SSMProviderBuilder.java @@ -0,0 +1,98 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.ssm; + +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ssm.SsmClient; +import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +/** + * Builder for the {@link SSMProvider} + */ +public class SSMProviderBuilder { + private SsmClient client; + private CacheManager cacheManager; + private TransformationManager transformationManager; + + private static SsmClient createClient() { + return SsmClient.builder() + .httpClientBuilder(UrlConnectionHttpClient.builder()) + .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .putAdvancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX, + UserAgentConfigurator.getUserAgent(BaseProvider.PARAMETERS)).build()) + .build(); + } + + /** + * Create a {@link SSMProvider} instance. + * + * @return a {@link SSMProvider} + */ + public SSMProvider build() { + if (cacheManager == null) { + cacheManager = new CacheManager(); + } + SSMProvider provider; + if (client == null) { + client = createClient(); + } + + provider = new SSMProvider(cacheManager, transformationManager, client); + + return provider; + } + + /** + * Set custom {@link SsmClient} to pass to the {@link SSMProvider}.
+ * Use it if you want to customize the region or any other part of the client. + * + * @param client Custom client + * @return the builder to chain calls (eg.
builder.withClient().build()
) + */ + public SSMProviderBuilder withClient(SsmClient client) { + this.client = client; + return this; + } + + /** + * Provide a CacheManager to the {@link SSMProvider} + * + * @param cacheManager the manager that will handle the cache of parameters + * @return the builder to chain calls (eg.
builder.withCacheManager().build()
) + */ + public SSMProviderBuilder withCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + return this; + } + + /** + * Provide a transformationManager to the {@link SSMProvider} + * + * @param transformationManager the manager that will handle transformation of parameters + * @return the builder to chain calls (eg.
builder.withTransformationManager().build()
) + */ + public SSMProviderBuilder withTransformationManager(TransformationManager transformationManager) { + this.transformationManager = transformationManager; + return this; + } +} diff --git a/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/SSMParamAspectTest.java b/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/SSMParamAspectTest.java new file mode 100644 index 000000000..e56d20ffa --- /dev/null +++ b/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/SSMParamAspectTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters.ssm; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Supplier; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class SSMParamAspectTest { + + // This class tests the SSM Param aspect in the same fashion + // as the tests for the aspects for the other providers. + + @Test + public void parameterInjectedByProvider() throws Exception { + + String key = "myKey"; + String value = "mySecretValue"; + SSMProvider provider = Mockito.mock(SSMProvider.class); + + Supplier providerBuilder = () -> provider; + writeStaticField(SSMParamAspect.class, "providerBuilder", providerBuilder, true); + + // Setup our mocked SSMProvider to return a value for our test data + Mockito.when(provider.get(key)).thenReturn(value); + + // Create an instance of a class and let the SSMParamAspect inject it + MyInjectedClass obj = new MyInjectedClass(); + assertThat(obj.mySecret).isEqualTo(value); + } + + class MyInjectedClass { + @SSMParam(key = "myKey") + public String mySecret; + } + +} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SSMProviderTest.java b/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/SSMProviderTest.java similarity index 84% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SSMProviderTest.java rename to powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/SSMProviderTest.java index 2a4f8f927..b105da438 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SSMProviderTest.java +++ b/powertools-parameters/powertools-parameters-ssm/src/test/java/software/amazon/lambda/powertools/parameters/ssm/SSMProviderTest.java @@ -12,16 +12,10 @@ * */ -package software.amazon.lambda.powertools.parameters; +package software.amazon.lambda.powertools.parameters.ssm; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -31,8 +25,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import software.amazon.awssdk.services.ssm.SsmClient; import software.amazon.awssdk.services.ssm.model.GetParameterRequest; import software.amazon.awssdk.services.ssm.model.GetParameterResponse; @@ -62,9 +59,9 @@ public class SSMProviderTest { @BeforeEach public void init() { - openMocks(this); + MockitoAnnotations.openMocks(this); cacheManager = new CacheManager(); - provider = new SSMProvider(cacheManager, client); + provider = new SSMProvider(cacheManager, null, client); } @Test @@ -100,7 +97,7 @@ public void getMultiple() { parameters.add(Parameter.builder().name("/prod/app1/key2").value("foo2").build()); parameters.add(Parameter.builder().name("/prod/app1/key3").value("foo3").build()); GetParametersByPathResponse response = GetParametersByPathResponse.builder().parameters(parameters).build(); - when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response); + Mockito.when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response); Map params = provider.getMultiple("/prod/app1"); assertThat(params).contains( @@ -123,7 +120,7 @@ public void getMultipleWithTrailingSlash() { parameters.add(Parameter.builder().name("/prod/app1/key2").value("foo2").build()); parameters.add(Parameter.builder().name("/prod/app1/key3").value("foo3").build()); GetParametersByPathResponse response = GetParametersByPathResponse.builder().parameters(parameters).build(); - when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response); + Mockito.when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response); Map params = provider.getMultiple("/prod/app1/"); assertThat(params).contains( @@ -146,7 +143,7 @@ public void getMultiple_cached_shouldNotCallSSM() { parameters.add(Parameter.builder().name("/prod/app1/key2").value("foo2").build()); parameters.add(Parameter.builder().name("/prod/app1/key3").value("foo3").build()); GetParametersByPathResponse response = GetParametersByPathResponse.builder().parameters(parameters).build(); - when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response); + Mockito.when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response); provider.getMultiple("/prod/app1"); @@ -156,7 +153,8 @@ public void getMultiple_cached_shouldNotCallSSM() { provider.get("/prod/app1/key2"); provider.get("/prod/app1/key3"); - verify(client, times(1)).getParametersByPath(any(GetParametersByPathRequest.class)); + Mockito.verify(client, Mockito.times(1)) + .getParametersByPath(ArgumentMatchers.any(GetParametersByPathRequest.class)); } @@ -172,7 +170,7 @@ public void getMultipleWithNextToken() { parameters2.add(Parameter.builder().name("/prod/app1/key3").value("foo3").build()); GetParametersByPathResponse response2 = GetParametersByPathResponse.builder().parameters(parameters2).build(); - when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response1, response2); + Mockito.when(client.getParametersByPath(paramByPathCaptor.capture())).thenReturn(response1, response2); Map params = provider.getMultiple("/prod/app1"); @@ -196,22 +194,10 @@ public void getMultipleWithNextToken() { assertThat(request2.nextToken()).isEqualTo("123abc"); } - @Test - public void testSecretsProviderBuilderMissingCacheManager_throwsException() { - - // Act & Assert - assertThatIllegalStateException().isThrownBy(() -> SSMProvider.builder() - .withClient(client) - .withTransformationManager(transformationManager) - .build()) - .withMessage("No CacheManager provided, please provide one"); - } - private void initMock(String expectedValue) { Parameter parameter = Parameter.builder().value(expectedValue).build(); GetParameterResponse result = GetParameterResponse.builder().parameter(parameter).build(); - when(client.getParameter(paramCaptor.capture())).thenReturn(result); - provider.defaultMaxAge(1, ChronoUnit.DAYS); + Mockito.when(client.getParameter(paramCaptor.capture())).thenReturn(result); provider.withMaxAge(2, ChronoUnit.DAYS); provider.recursive(); } diff --git a/powertools-parameters/powertools-parameters-tests/pom.xml b/powertools-parameters/powertools-parameters-tests/pom.xml new file mode 100644 index 000000000..d8e9b2a02 --- /dev/null +++ b/powertools-parameters/powertools-parameters-tests/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + software.amazon.lambda + powertools-parent + 2.0.0-SNAPSHOT + ../../pom.xml + + + powertools-parameters-tests + Powertools parameters tests that cut across all the parameters providers + + + true + + + + + software.amazon.lambda + powertools-parameters-ssm + test + ${project.version} + + + software.amazon.lambda + powertools-parameters-secrets + test + ${project.version} + + + software.amazon.lambda + powertools-parameters-dynamodb + test + ${project.version} + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.apache.commons + commons-lang3 + test + + + org.assertj + assertj-core + test + + + org.aspectj + aspectjweaver + test + + + \ No newline at end of file diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/BaseProviderTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/BaseProviderTest.java similarity index 91% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/BaseProviderTest.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/BaseProviderTest.java index edc671e2c..5fa740253 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/BaseProviderTest.java +++ b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/BaseProviderTest.java @@ -25,6 +25,7 @@ import static software.amazon.lambda.powertools.parameters.transform.Transformer.json; import java.time.Clock; +import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.Base64; import java.util.HashMap; @@ -51,9 +52,8 @@ public void setup() { clock = Clock.systemDefaultZone(); cacheManager = new CacheManager(); - provider = new BasicProvider(cacheManager); transformationManager = new TransformationManager(); - provider.setTransformationManager(transformationManager); + provider = new BasicProvider(cacheManager, transformationManager); } @Test @@ -138,7 +138,8 @@ public void get_customTTL_expired_shouldGetValue() { @Test public void get_customDefaultTTL_cached_shouldGetFromCache() { - provider.defaultMaxAge(12, ChronoUnit.MINUTES).get("foobar"); + provider.cacheManager.setDefaultExpirationTime(Duration.of(12, MINUTES)); + provider.get("foobar"); getFromStore = false; provider.setClock(offset(clock, of(10, MINUTES))); @@ -149,7 +150,7 @@ public void get_customDefaultTTL_cached_shouldGetFromCache() { @Test public void get_customDefaultTTL_expired_shouldGetValue() { - provider.defaultMaxAge(2, ChronoUnit.MINUTES).get("barbaz"); + provider.cacheManager.setDefaultExpirationTime(Duration.of(2, MINUTES)); getFromStore = false; provider.setClock(offset(clock, of(3, MINUTES))); @@ -160,9 +161,7 @@ public void get_customDefaultTTL_expired_shouldGetValue() { @Test public void get_customDefaultTTLAndTTL_cached_shouldGetFromCache() { - provider.defaultMaxAge(12, ChronoUnit.MINUTES) - .withMaxAge(5, SECONDS) - .get("foobaz"); + provider.get("foobaz"); getFromStore = false; provider.setClock(offset(clock, of(4, SECONDS))); @@ -173,9 +172,10 @@ public void get_customDefaultTTLAndTTL_cached_shouldGetFromCache() { @Test public void get_customDefaultTTLAndTTL_expired_shouldGetValue() { - provider.defaultMaxAge(2, ChronoUnit.MINUTES) - .withMaxAge(5, SECONDS) - .get("bariton"); + + provider.cacheManager.setDefaultExpirationTime(Duration.ofMinutes(2)); + + provider.withMaxAge(5, SECONDS).get("bariton"); getFromStore = false; provider.setClock(offset(clock, of(6, SECONDS))); @@ -275,8 +275,9 @@ public void getObject_customTTL_expired_shouldGetValue() { public void getObject_customDefaultTTL_cached_shouldGetFromCache() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); - provider.defaultMaxAge(12, ChronoUnit.MINUTES) - .withTransformation(json) + provider.cacheManager.setDefaultExpirationTime(Duration.of(12, MINUTES)); + + provider.withTransformation(json) .get("foo", ObjectToDeserialize.class); getFromStore = false; @@ -290,8 +291,9 @@ public void getObject_customDefaultTTL_cached_shouldGetFromCache() { public void getObject_customDefaultTTL_expired_shouldGetValue() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); - provider.defaultMaxAge(2, ChronoUnit.MINUTES) - .withTransformation(json) + provider.cacheManager.setDefaultExpirationTime(Duration.of(2, MINUTES)); + + provider.withTransformation(json) .get("foo", ObjectToDeserialize.class); getFromStore = false; @@ -305,9 +307,10 @@ public void getObject_customDefaultTTL_expired_shouldGetValue() { public void getObject_customDefaultTTLAndTTL_cached_shouldGetFromCache() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); - provider.defaultMaxAge(12, ChronoUnit.MINUTES) + provider.cacheManager.setDefaultExpirationTime(Duration.ofSeconds(5)); + + provider.withTransformation(json) .withMaxAge(5, SECONDS) - .withTransformation(json) .get("foo", ObjectToDeserialize.class); getFromStore = false; @@ -321,8 +324,9 @@ public void getObject_customDefaultTTLAndTTL_cached_shouldGetFromCache() { public void getObject_customDefaultTTLAndTTL_expired_shouldGetValue() { provider.setValue("{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); - provider.defaultMaxAge(2, ChronoUnit.MINUTES) - .withMaxAge(5, SECONDS) + provider.cacheManager.setDefaultExpirationTime(Duration.ofMinutes(2)); + + provider.withMaxAge(5, SECONDS) .withTransformation(json) .get("foo", ObjectToDeserialize.class); getFromStore = false; @@ -335,7 +339,7 @@ public void getObject_customDefaultTTLAndTTL_expired_shouldGetValue() { @Test public void get_noTransformationManager_shouldThrowException() { - provider.setTransformationManager(null); + provider = new BasicProvider(new CacheManager(), null); assertThatIllegalStateException() .isThrownBy(() -> provider.withTransformation(base64).get("foo")); @@ -343,7 +347,6 @@ public void get_noTransformationManager_shouldThrowException() { @Test public void getObject_noTransformationManager_shouldThrowException() { - provider.setTransformationManager(null); assertThatIllegalStateException() .isThrownBy(() -> provider.get("foo", ObjectToDeserialize.class)); @@ -379,8 +382,8 @@ class BasicProvider extends BaseProvider { private String value = "valueFromStore"; - public BasicProvider(CacheManager cacheManager) { - super(cacheManager); + public BasicProvider(CacheManager cacheManager, TransformationManager transformationManager) { + super(cacheManager, transformationManager); } public void setValue(String value) { diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/ParamProvidersIntegrationTest.java similarity index 81% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/ParamProvidersIntegrationTest.java index d6fbe66f0..7d790d140 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java +++ b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/ParamProvidersIntegrationTest.java @@ -14,7 +14,6 @@ package software.amazon.lambda.powertools.parameters; -import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; @@ -25,14 +24,12 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import org.assertj.core.data.MapEntry; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; @@ -43,8 +40,10 @@ import software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest; import software.amazon.awssdk.services.ssm.model.GetParametersByPathResponse; import software.amazon.awssdk.services.ssm.model.Parameter; +import software.amazon.lambda.powertools.parameters.secrets.SecretsProvider; +import software.amazon.lambda.powertools.parameters.ssm.SSMProvider; -public class ParamManagerIntegrationTest { +public class ParamProvidersIntegrationTest { @Mock SsmClient ssmClient; @@ -59,19 +58,17 @@ public class ParamManagerIntegrationTest { SecretsManagerClient secretsManagerClient; @Captor ArgumentCaptor secretsCaptor; - @Mock - private AppConfigDataClient appConfigDataClient; @BeforeEach public void setup() throws IllegalAccessException { openMocks(this); - - writeStaticField(ParamManager.class, "providers", new ConcurrentHashMap<>(), true); } @Test public void ssmProvider_get() { - SSMProvider ssmProvider = ParamManager.getSsmProvider(ssmClient); + SSMProvider ssmProvider = SSMProvider.builder() + .withClient(ssmClient) + .build(); String expectedValue = "value"; Parameter parameter = Parameter.builder().value(expectedValue).build(); @@ -87,7 +84,9 @@ public void ssmProvider_get() { @Test public void ssmProvider_getMultiple() { - SSMProvider ssmProvider = ParamManager.getSsmProvider(ssmClient); + SSMProvider ssmProvider = SSMProvider.builder() + .withClient(ssmClient) + .build(); List parameters = new ArrayList<>(); parameters.add(Parameter.builder().name("/prod/app1/key1").value("foo1").build()); @@ -112,7 +111,9 @@ public void ssmProvider_getMultiple() { @Test public void secretsProvider_get() { - SecretsProvider secretsProvider = ParamManager.getSecretsProvider(secretsManagerClient); + SecretsProvider secretsProvider = SecretsProvider.builder() + .withClient(secretsManagerClient) + .build(); String expectedValue = "Value1"; GetSecretValueResponse response = GetSecretValueResponse.builder().secretString(expectedValue).build(); @@ -125,24 +126,4 @@ public void secretsProvider_get() { verify(secretsManagerClient, times(1)).getSecretValue(any(GetSecretValueRequest.class)); } - @Test - public void getDynamoDbProvider() { - - // Act - DynamoDbProvider provider = ParamManager.getDynamoDbProvider(ddbClient, "test-table"); - - // Assert - assertThat(provider).isNotNull(); - } - - @Test - public void getAppConfigProvider() { - - // Act - AppConfigProvider provider = ParamManager.getAppConfigProvider(appConfigDataClient, "test-env", "test-app"); - - // Assert - assertThat(provider).isNotNull(); - - } } diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/cache/CacheManagerTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/cache/CacheManagerTest.java similarity index 100% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/cache/CacheManagerTest.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/cache/CacheManagerTest.java diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/cache/DataStoreTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/cache/DataStoreTest.java similarity index 100% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/cache/DataStoreTest.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/cache/DataStoreTest.java diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java similarity index 100% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/internal/AnotherObject.java diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java similarity index 97% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java index 2c9db3712..a98d68c22 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java +++ b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/internal/CustomProvider.java @@ -25,7 +25,7 @@ public class CustomProvider extends BaseProvider { private final Map values = new HashMap<>(); public CustomProvider(CacheManager cacheManager) { - super(cacheManager); + super(cacheManager, null); values.put("/simple", "value"); values.put("/base64", Base64.getEncoder().encodeToString("value".getBytes())); values.put("/json", "{\"foo\":\"Foo\", \"bar\":42, \"baz\":123456789}"); diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/Base64TransformerTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/Base64TransformerTest.java similarity index 100% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/Base64TransformerTest.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/Base64TransformerTest.java diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformerTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformerTest.java similarity index 100% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformerTest.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/JsonTransformerTest.java diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/ObjectToDeserialize.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/ObjectToDeserialize.java similarity index 100% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/ObjectToDeserialize.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/ObjectToDeserialize.java diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java b/powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java similarity index 100% rename from powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java rename to powertools-parameters/powertools-parameters-tests/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java diff --git a/powertools-parameters/spotbugs-exclude.xml b/powertools-parameters/spotbugs-exclude.xml new file mode 100644 index 000000000..d48e9bee1 --- /dev/null +++ b/powertools-parameters/spotbugs-exclude.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseParamAspect.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseParamAspect.java new file mode 100644 index 000000000..f7ebdf97a --- /dev/null +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseParamAspect.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + * + */ + +package software.amazon.lambda.powertools.parameters; + +import org.aspectj.lang.reflect.FieldSignature; +import software.amazon.lambda.powertools.parameters.transform.Transformer; + +/** + * Provides a common base for all parameter aspects. This lets us group functionality that + * we need to reimplement in each aspect. This class should be extended for each + * additional parameter aspect. + */ +public abstract class BaseParamAspect { + + /** + * Gets the parameter value from the provider and transforms it if necessary. This transformation + * is generic across all parameter providers. + * + * @param key The key of the parameter to get + * @param transformer The transformer to use to transform the parameter value + * @param provider A concrete instance of the parameter provider to retrieve the value from + * @param fieldSignature The signature of the field that the parameter is being injected into + * @return The value of the parameter, transformed if necessary + */ + protected Object getAndTransform(String key, Class transformer, BaseProvider provider, + FieldSignature fieldSignature) { + if (transformer.isInterface()) { + // No transformation + return provider.get(key); + } else { + Class fieldType = fieldSignature.getFieldType(); + if (String.class.isAssignableFrom(fieldType)) { + // Basic transformation + return provider + .withTransformation(transformer) + .get(key); + } else { + // Complex transformation + return provider + .withTransformation(transformer) + .get(key, fieldType); + } + } + } +} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseProvider.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseProvider.java index edb82f5ec..bedace28c 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseProvider.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/BaseProvider.java @@ -34,11 +34,12 @@ public abstract class BaseProvider implements ParamProvider { public static final String PARAMETERS = "parameters"; protected final CacheManager cacheManager; - private TransformationManager transformationManager; + private final TransformationManager transformationManager; private Clock clock; - public BaseProvider(CacheManager cacheManager) { + public BaseProvider(CacheManager cacheManager, TransformationManager transformationManager) { this.cacheManager = cacheManager; + this.transformationManager = transformationManager; } /** @@ -59,25 +60,10 @@ public BaseProvider(CacheManager cacheManager) { */ protected abstract Map getMultipleValues(String path); - /** - * (Optional) Set the default max age for the cache of all parameters. Override the default 5 seconds.
- * If for some parameters, you need to set a different maxAge, use {@link #withMaxAge(int, ChronoUnit)}.
- * Use {@link #withMaxAge(int, ChronoUnit)} after {#defaultMaxAge(int, ChronoUnit)} in the chain. - * - * @param maxAge Maximum time to cache the parameter, before calling the underlying parameter store. - * @param unit Unit of time - * @return the provider itself in order to chain calls (eg.
provider.defaultMaxAge(10, SECONDS).get("key")
). - */ - protected BaseProvider defaultMaxAge(int maxAge, ChronoUnit unit) { - Duration duration = Duration.of(maxAge, unit); - cacheManager.setDefaultExpirationTime(duration); - return this; - } - /** * (Optional) Builder method to call before {@link #get(String)} or {@link #get(String, Class)} * to set cache max age for the parameter to get.

- * The max age is reset to default (either 5 or a custom value set with {@link #defaultMaxAge}) after each get, + * The max age is reset to default (either 5 or a custom value that may be set on the CacheManager) after each get, * so you need to use this method for each parameter to cache with non-default max age.

* * Not Thread Safe: calling this method simultaneously by several threads @@ -225,10 +211,6 @@ protected void resetToDefaults() { } } - protected void setTransformationManager(TransformationManager transformationManager) { - this.transformationManager = transformationManager; - } - /** * For test purpose * diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/DynamoDbProvider.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/DynamoDbProvider.java deleted file mode 100644 index 363f39d7c..000000000 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/DynamoDbProvider.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * Licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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. - * - */ - -package software.amazon.lambda.powertools.parameters; - -import java.util.Collections; -import java.util.Map; -import java.util.stream.Collectors; -import software.amazon.awssdk.core.SdkSystemSetting; -import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; -import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; -import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; -import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; -import software.amazon.awssdk.services.dynamodb.model.QueryRequest; -import software.amazon.awssdk.services.dynamodb.model.QueryResponse; -import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; -import software.amazon.lambda.powertools.parameters.cache.CacheManager; -import software.amazon.lambda.powertools.parameters.exception.DynamoDbProviderSchemaException; -import software.amazon.lambda.powertools.parameters.transform.TransformationManager; - -/** - * Implements a {@link ParamProvider} on top of DynamoDB. The schema of the table - * is described in the Powertools for AWS Lambda (Java) documentation. - * - * @see Parameters provider documentation - */ -public class DynamoDbProvider extends BaseProvider { - - private final DynamoDbClient client; - private final String tableName; - - DynamoDbProvider(CacheManager cacheManager, DynamoDbClient client, String tableName) { - super(cacheManager); - this.client = client; - this.tableName = tableName; - } - - DynamoDbProvider(CacheManager cacheManager, String tableName) { - this(cacheManager, Builder.createClient(), tableName); - } - - /** - * Create a builder that can be used to configure and create a {@link DynamoDbProvider}. - * - * @return a new instance of {@link DynamoDbProvider.Builder} - */ - public static DynamoDbProvider.Builder builder() { - return new DynamoDbProvider.Builder(); - } - - /** - * Return a single value from the DynamoDB parameter provider. - * - * @param key key of the parameter - * @return The value, if it exists, null if it doesn't. Throws if the row exists but doesn't match the schema. - */ - @Override - protected String getValue(String key) { - GetItemResponse resp = client.getItem(GetItemRequest.builder() - .tableName(tableName) - .key(Collections.singletonMap("id", AttributeValue.fromS(key))) - .attributesToGet("value") - .build()); - - // If we have an item at the key, we should be able to get a 'val' out of it. If not it's - // exceptional. - // If we don't have an item at the key, we should return null. - if (resp.hasItem() && !resp.item().values().isEmpty()) { - if (!resp.item().containsKey("value")) { - throw new DynamoDbProviderSchemaException("Missing 'value': " + resp.item().toString()); - } - return resp.item().get("value").s(); - } - - return null; - } - - /** - * Returns multiple values from the DynamoDB parameter provider. - * - * @param path Parameter store path - * @return All values matching the given path, and an empty map if none do. Throws if any records exist that don't match the schema. - */ - @Override - protected Map getMultipleValues(String path) { - - QueryResponse resp = client.query(QueryRequest.builder() - .tableName(tableName) - .keyConditionExpression("id = :v_id") - .expressionAttributeValues(Collections.singletonMap(":v_id", AttributeValue.fromS(path))) - .build()); - - return resp - .items() - .stream() - .peek((i) -> - { - if (!i.containsKey("sk")) { - throw new DynamoDbProviderSchemaException("Missing 'sk': " + i.toString()); - } - if (!i.containsKey("value")) { - throw new DynamoDbProviderSchemaException("Missing 'value': " + i.toString()); - } - }) - .collect( - Collectors.toMap( - (i) -> i.get("sk").s(), - (i) -> i.get("value").s())); - - - } - - static class Builder { - private DynamoDbClient client; - private String table; - private CacheManager cacheManager; - private TransformationManager transformationManager; - - private static DynamoDbClient createClient() { - return DynamoDbClient.builder() - .httpClientBuilder(UrlConnectionHttpClient.builder()) - .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) - .overrideConfiguration(ClientOverrideConfiguration.builder() - .putAdvancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX, - UserAgentConfigurator.getUserAgent(PARAMETERS)).build()) - .build(); - } - - /** - * Create a {@link DynamoDbProvider} instance. - * - * @return a {@link DynamoDbProvider} - */ - public DynamoDbProvider build() { - if (cacheManager == null) { - throw new IllegalStateException("No CacheManager provided; please provide one"); - } - if (table == null) { - throw new IllegalStateException("No DynamoDB table name provided; please provide one"); - } - DynamoDbProvider provider; - if (client == null) { - client = createClient(); - } - provider = new DynamoDbProvider(cacheManager, client, table); - - if (transformationManager != null) { - provider.setTransformationManager(transformationManager); - } - return provider; - } - - /** - * Set custom {@link DynamoDbClient} to pass to the {@link DynamoDbClient}.
- * Use it if you want to customize the region or any other part of the client. - * - * @param client Custom client - * @return the builder to chain calls (eg.
builder.withClient().build()
) - */ - public DynamoDbProvider.Builder withClient(DynamoDbClient client) { - this.client = client; - return this; - } - - /** - * Mandatory. Provide a CacheManager to the {@link DynamoDbProvider} - * - * @param cacheManager the manager that will handle the cache of parameters - * @return the builder to chain calls (eg.
builder.withCacheManager().build()
) - */ - public DynamoDbProvider.Builder withCacheManager(CacheManager cacheManager) { - this.cacheManager = cacheManager; - return this; - } - - /** - * Mandatory. Provide a DynamoDB table to the {@link DynamoDbProvider} - * - * @param table the table that parameters will be retrieved from. - * @return the builder to chain calls (eg.
builder.withTable().build()
) - */ - public DynamoDbProvider.Builder withTable(String table) { - this.table = table; - return this; - } - - /** - * Provide a transformationManager to the {@link DynamoDbProvider} - * - * @param transformationManager the manager that will handle transformation of parameters - * @return the builder to chain calls (eg.
builder.withTransformationManager().build()
) - */ - public DynamoDbProvider.Builder withTransformationManager(TransformationManager transformationManager) { - this.transformationManager = transformationManager; - return this; - } - } -} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java deleted file mode 100644 index 6fee0f114..000000000 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * Licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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. - * - */ - -package software.amazon.lambda.powertools.parameters; - -import java.lang.reflect.Constructor; -import java.util.concurrent.ConcurrentHashMap; -import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; -import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.lambda.powertools.parameters.cache.CacheManager; -import software.amazon.lambda.powertools.parameters.transform.TransformationManager; - -/** - * Utility class to retrieve instances of parameter providers. - * Each instance is unique (singleton). - */ -public final class ParamManager { - - private static final CacheManager cacheManager = new CacheManager(); - private static final TransformationManager transformationManager = new TransformationManager(); - - // NOTE: For testing purposes `providers` cannot be final - private static ConcurrentHashMap, BaseProvider> providers = new ConcurrentHashMap<>(); - - /** - * Get a concrete implementation of {@link BaseProvider}.
- * You can specify {@link SecretsProvider}, {@link SSMProvider} or create your - * custom provider by extending {@link BaseProvider} if you need to integrate with a different parameter store. - * - * @return a {@link SecretsProvider} - * @deprecated You should not use this method directly but a typed one (getSecretsProvider, getSsmProvider, getDynamoDbProvider, getAppConfigProvider), will be removed in v2 - */ - // TODO in v2: remove public access to this and review how we get providers (it was not designed for DDB and AppConfig in mind initially) - public static T getProvider(Class providerClass) { - if (providerClass == null) { - throw new IllegalStateException("providerClass cannot be null."); - } - if (providerClass == DynamoDbProvider.class || providerClass == AppConfigProvider.class) { - throw new IllegalArgumentException( - providerClass + " cannot be instantiated like this, additional parameters are required"); - } - return (T) providers.computeIfAbsent(providerClass, ParamManager::createProvider); - } - - /** - * Get a {@link SecretsProvider} with default {@link SecretsManagerClient}.
- * If you need to customize the region, or other part of the client, use {@link ParamManager#getSecretsProvider(SecretsManagerClient)} instead. - * - * @return a {@link SecretsProvider} - */ - public static SecretsProvider getSecretsProvider() { - return getProvider(SecretsProvider.class); - } - - /** - * Get a {@link SSMProvider} with default {@link SsmClient}.
- * If you need to customize the region, or other part of the client, use {@link ParamManager#getSsmProvider(SsmClient)} instead. - * - * @return a {@link SSMProvider} - */ - public static SSMProvider getSsmProvider() { - return getProvider(SSMProvider.class); - } - - /** - * Get a {@link DynamoDbProvider} with default {@link DynamoDbClient}
- * If you need to customize the region, or other part of the client, use {@link ParamManager#getDynamoDbProvider(DynamoDbClient, String)} - */ - public static DynamoDbProvider getDynamoDbProvider(String tableName) { - // Because we need a DDB table name to configure our client, we can't use - // ParamManager#getProvider. This means that we need to make sure we do the same stuff - - // set transformation manager and cache manager. - return DynamoDbProvider.builder() - .withCacheManager(cacheManager) - .withTable(tableName) - .withTransformationManager(transformationManager) - .build(); - } - - /** - * Get a {@link AppConfigProvider} with default {@link AppConfigDataClient}.
- * If you need to customize the region, or other part of the client, use {@link ParamManager#getAppConfigProvider(AppConfigDataClient, String, String)} instead. - * - * @return a {@link AppConfigProvider} - */ - public static AppConfigProvider getAppConfigProvider(String environment, String application) { - // Because we need a DDB table name to configure our client, we can't use - // ParamManager#getProvider. This means that we need to make sure we do the same stuff - - // set transformation manager and cache manager. - return AppConfigProvider.builder() - .withCacheManager(cacheManager) - .withTransformationManager(transformationManager) - .withEnvironment(environment) - .withApplication(application) - .build(); - } - - - /** - * Get a {@link SecretsProvider} with your custom {@link SecretsManagerClient}.
- * Use this to configure region or other part of the client. Use {@link ParamManager#getSsmProvider()} if you don't need this customization. - * - * @return a {@link SecretsProvider} - */ - public static SecretsProvider getSecretsProvider(SecretsManagerClient client) { - return (SecretsProvider) providers.computeIfAbsent(SecretsProvider.class, (k) -> SecretsProvider.builder() - .withClient(client) - .withCacheManager(cacheManager) - .withTransformationManager(transformationManager) - .build()); - } - - /** - * Get a {@link SSMProvider} with your custom {@link SsmClient}.
- * Use this to configure region or other part of the client. Use {@link ParamManager#getSsmProvider()} if you don't need this customization. - * - * @return a {@link SSMProvider} - */ - public static SSMProvider getSsmProvider(SsmClient client) { - return (SSMProvider) providers.computeIfAbsent(SSMProvider.class, (k) -> SSMProvider.builder() - .withClient(client) - .withCacheManager(cacheManager) - .withTransformationManager(transformationManager) - .build()); - } - - /** - * Get a {@link DynamoDbProvider} with your custom {@link DynamoDbClient}.
- * Use this to configure region or other part of the client. Use {@link ParamManager#getDynamoDbProvider(String)} )} if you don't need this customization. - * - * @return a {@link DynamoDbProvider} - */ - public static DynamoDbProvider getDynamoDbProvider(DynamoDbClient client, String table) { - return (DynamoDbProvider) providers.computeIfAbsent(DynamoDbProvider.class, (k) -> DynamoDbProvider.builder() - .withClient(client) - .withTable(table) - .withCacheManager(cacheManager) - .withTransformationManager(transformationManager) - .build()); - } - - /** - * Get a {@link AppConfigProvider} with your custom {@link AppConfigDataClient}.
- * Use this to configure region or other part of the client. Use {@link ParamManager#getAppConfigProvider(String, String)} if you don't need this customization. - * - * @return a {@link AppConfigProvider} - */ - public static AppConfigProvider getAppConfigProvider(AppConfigDataClient client, String environment, - String application) { - return (AppConfigProvider) providers.computeIfAbsent(AppConfigProvider.class, (k) -> AppConfigProvider.builder() - .withClient(client) - .withCacheManager(cacheManager) - .withTransformationManager(transformationManager) - .withEnvironment(environment) - .withApplication(application) - .build()); - } - - - public static CacheManager getCacheManager() { - return cacheManager; - } - - public static TransformationManager getTransformationManager() { - return transformationManager; - } - - static T createProvider(Class providerClass) { - try { - Constructor constructor = providerClass.getDeclaredConstructor(CacheManager.class); - T provider = - constructor.newInstance(cacheManager); // FIXME: avoid reflection here as we may have issues (#1280) - provider.setTransformationManager(transformationManager); - return provider; - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Unexpected error occurred. Please raise issue at " + - "https://github.com/aws-powertools/powertools-lambda-java/issues", e); - } - } - -} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SecretsProvider.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SecretsProvider.java deleted file mode 100644 index b77f501f2..000000000 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SecretsProvider.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * Licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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. - * - */ - -package software.amazon.lambda.powertools.parameters; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.time.temporal.ChronoUnit; -import java.util.Base64; -import java.util.Map; -import software.amazon.awssdk.core.SdkSystemSetting; -import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; -import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; -import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; -import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; -import software.amazon.lambda.powertools.common.internal.UserAgentConfigurator; -import software.amazon.lambda.powertools.parameters.cache.CacheManager; -import software.amazon.lambda.powertools.parameters.transform.TransformationManager; -import software.amazon.lambda.powertools.parameters.transform.Transformer; - -/** - * AWS Secrets Manager Parameter Provider

- * - * Samples: - *
- *     SecretsProvider provider = ParamManager.getSecretsProvider();
- *
- *     String value = provider.get("key");
- *     System.out.println(value);
- *     >>> "value"
- *
- *     // Get a value and cache it for 30 seconds (all others values will now be cached for 30 seconds)
- *     String value = provider.defaultMaxAge(30, ChronoUnit.SECONDS).get("key");
- *
- *     // Get a value and cache it for 1 minute (all others values are cached for 5 seconds by default)
- *     String value = provider.withMaxAge(1, ChronoUnit.MINUTES).get("key");
- *
- *     // Get a base64 encoded value, decoded into a String, and store it in the cache
- *     String value = provider.withTransformation(Transformer.base64).get("key");
- *
- *     // Get a json value, transform it into an Object, and store it in the cache
- *     TargetObject = provider.withTransformation(Transformer.json).get("key", TargetObject.class);
- * 
- */ -public class SecretsProvider extends BaseProvider { - - private final SecretsManagerClient client; - - /** - * Constructor with custom {@link SecretsManagerClient}.
- * Use when you need to customize region or any other attribute of the client.

- *

- * Use the {@link Builder} to create an instance of it. - * - * @param client custom client you would like to use. - */ - SecretsProvider(CacheManager cacheManager, SecretsManagerClient client) { - super(cacheManager); - this.client = client; - } - - /** - * Constructor with only a CacheManager
- *

- * Used in {@link ParamManager#createProvider(Class)} - * - * @param cacheManager handles the parameter caching - */ - SecretsProvider(CacheManager cacheManager) { - this(cacheManager, Builder.createClient()); - } - - /** - * Create a builder that can be used to configure and create a {@link SecretsProvider}. - * - * @return a new instance of {@link SecretsProvider.Builder} - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Retrieve the parameter value from the AWS Secrets Manager. - * - * @param key key of the parameter - * @return the value of the parameter identified by the key - */ - @Override - protected String getValue(String key) { - GetSecretValueRequest request = GetSecretValueRequest.builder().secretId(key).build(); - - String secretValue = client.getSecretValue(request).secretString(); - if (secretValue == null) { - secretValue = - new String(Base64.getDecoder().decode(client.getSecretValue(request).secretBinary().asByteArray()), - UTF_8); - } - return secretValue; - } - - /** - * @throws UnsupportedOperationException as it is not possible to get multiple values simultaneously from Secrets Manager - */ - @Override - protected Map getMultipleValues(String path) { - throw new UnsupportedOperationException("Impossible to get multiple values from AWS Secrets Manager"); - } - - /** - * {@inheritDoc} - */ - @Override - public SecretsProvider defaultMaxAge(int maxAge, ChronoUnit unit) { - super.defaultMaxAge(maxAge, unit); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public SecretsProvider withMaxAge(int maxAge, ChronoUnit unit) { - super.withMaxAge(maxAge, unit); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public SecretsProvider withTransformation(Class transformerClass) { - super.withTransformation(transformerClass); - return this; - } - - // For test purpose only - SecretsManagerClient getClient() { - return client; - } - - static class Builder { - - private SecretsManagerClient client; - private CacheManager cacheManager; - private TransformationManager transformationManager; - - private static SecretsManagerClient createClient() { - return SecretsManagerClient.builder() - .httpClientBuilder(UrlConnectionHttpClient.builder()) - .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable()))) - .overrideConfiguration(ClientOverrideConfiguration.builder() - .putAdvancedOption(SdkAdvancedClientOption.USER_AGENT_SUFFIX, - UserAgentConfigurator.getUserAgent(PARAMETERS)).build()) - .build(); - } - - /** - * Create a {@link SecretsProvider} instance. - * - * @return a {@link SecretsProvider} - */ - public SecretsProvider build() { - if (cacheManager == null) { - throw new IllegalStateException("No CacheManager provided, please provide one"); - } - SecretsProvider provider; - if (client == null) { - client = createClient(); - } - - provider = new SecretsProvider(cacheManager, client); - - if (transformationManager != null) { - provider.setTransformationManager(transformationManager); - } - return provider; - } - - /** - * Set custom {@link SecretsManagerClient} to pass to the {@link SecretsProvider}.
- * Use it if you want to customize the region or any other part of the client. - * - * @param client Custom client - * @return the builder to chain calls (eg.

builder.withClient().build()
) - */ - public Builder withClient(SecretsManagerClient client) { - this.client = client; - return this; - } - - /** - * Mandatory. Provide a CacheManager to the {@link SecretsProvider} - * - * @param cacheManager the manager that will handle the cache of parameters - * @return the builder to chain calls (eg.
builder.withCacheManager().build()
) - */ - public Builder withCacheManager(CacheManager cacheManager) { - this.cacheManager = cacheManager; - return this; - } - - /** - * Provide a transformationManager to the {@link SecretsProvider} - * - * @param transformationManager the manager that will handle transformation of parameters - * @return the builder to chain calls (eg.
builder.withTransformationManager().build()
) - */ - public Builder withTransformationManager(TransformationManager transformationManager) { - this.transformationManager = transformationManager; - return this; - } - } -} diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java deleted file mode 100644 index 081af108d..000000000 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * Licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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. - * - */ - -package software.amazon.lambda.powertools.parameters.internal; - -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.aspectj.lang.reflect.FieldSignature; -import software.amazon.lambda.powertools.parameters.BaseProvider; -import software.amazon.lambda.powertools.parameters.Param; -import software.amazon.lambda.powertools.parameters.ParamManager; - -@Aspect -public class LambdaParametersAspect { - - @Pointcut("get(* *) && @annotation(paramAnnotation)") - public void getParam(Param paramAnnotation) { - } - - @Around("getParam(paramAnnotation)") - public Object injectParam(final ProceedingJoinPoint joinPoint, final Param paramAnnotation) { - BaseProvider provider = ParamManager.getProvider(paramAnnotation.provider()); - - if (paramAnnotation.transformer().isInterface()) { - // No transformation - return provider.get(paramAnnotation.key()); - } else { - FieldSignature s = (FieldSignature) joinPoint.getSignature(); - if (String.class.isAssignableFrom(s.getFieldType())) { - // Basic transformation - return provider - .withTransformation(paramAnnotation.transformer()) - .get(paramAnnotation.key()); - } else { - // Complex transformation - return provider - .withTransformation(paramAnnotation.transformer()) - .get(paramAnnotation.key(), s.getFieldType()); - } - } - } - -} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerTest.java deleted file mode 100644 index b84fcf743..000000000 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerTest.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * Licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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. - * - */ - -package software.amazon.lambda.powertools.parameters; - -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; -import static org.assertj.core.api.Assertions.assertThatRuntimeException; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; -import software.amazon.lambda.powertools.parameters.cache.CacheManager; -import software.amazon.lambda.powertools.parameters.internal.CustomProvider; -import software.amazon.lambda.powertools.parameters.transform.TransformationManager; - -public class ParamManagerTest { - - @Test - public void testGetCacheManager() { - - // Act - CacheManager cacheManager = ParamManager.getCacheManager(); - - // Assert - assertNotNull(cacheManager); - } - - @Test - public void testGetTransformationManager() { - - // Act - TransformationManager transformationManager = ParamManager.getTransformationManager(); - - // Assert - assertNotNull(transformationManager); - } - - @Test - public void testCreateProvider() { - - // Act - CustomProvider customProvider = ParamManager.createProvider(CustomProvider.class); - - // Assert - assertNotNull(customProvider); - } - - @Test - public void testCreateUninstanciableProvider_throwsException() { - - // Act & Assert - assertThatRuntimeException().isThrownBy(() -> ParamManager.createProvider(BaseProvider.class)); - } - - @Test - public void testGetProviderWithProviderClass() { - - // Act - SecretsProvider secretsProvider = ParamManager.getProvider(SecretsProvider.class); - - // Assert - assertNotNull(secretsProvider); - } - - @Test - public void testGetProviderWithProviderClass_throwsException() { - - // Act & Assert - assertThatIllegalStateException().isThrownBy(() -> ParamManager.getProvider(null)); - } - - @Test - public void testGetSecretsProvider_withoutParameter_shouldCreateDefaultClient() { - - // Act - SecretsProvider secretsProvider = ParamManager.getSecretsProvider(); - - // Assert - assertNotNull(secretsProvider); - assertNotNull(secretsProvider.getClient()); - } - - @Test - public void testGetSSMProvider_withoutParameter_shouldCreateDefaultClient() { - - // Act - SSMProvider ssmProvider = ParamManager.getSsmProvider(); - - // Assert - assertNotNull(ssmProvider); - assertNotNull(ssmProvider.getClient()); - } - - @Test - public void testGetDynamoDBProvider_requireOtherParameters_throwException() { - - // Act & Assert - assertThatIllegalArgumentException().isThrownBy(() -> ParamManager.getProvider(DynamoDbProvider.class)); - } - - @Test - public void testGetAppConfigProvider_requireOtherParameters_throwException() { - - // Act & Assert - assertThatIllegalArgumentException().isThrownBy(() -> ParamManager.getProvider(AppConfigProvider.class)); - } -} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java deleted file mode 100644 index 2c246336b..000000000 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * Licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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. - * - */ - -package software.amazon.lambda.powertools.parameters.internal; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import software.amazon.lambda.powertools.parameters.Param; -import software.amazon.lambda.powertools.parameters.ParamManager; -import software.amazon.lambda.powertools.parameters.SSMProvider; -import software.amazon.lambda.powertools.parameters.exception.TransformationException; -import software.amazon.lambda.powertools.parameters.transform.Base64Transformer; -import software.amazon.lambda.powertools.parameters.transform.JsonTransformer; -import software.amazon.lambda.powertools.parameters.transform.ObjectToDeserialize; - -public class LambdaParametersAspectTest { - - @Mock - private SSMProvider defaultProvider; - - @Param(key = "/default") - private String defaultValue; - - @Param(key = "/simple", provider = CustomProvider.class) - private String param; - - @Param(key = "/base64", provider = CustomProvider.class, transformer = Base64Transformer.class) - private String basicTransform; - - @Param(key = "/json", provider = CustomProvider.class, transformer = JsonTransformer.class) - private ObjectToDeserialize complexTransform; - - @Param(key = "/json", provider = CustomProvider.class, transformer = JsonTransformer.class) - private AnotherObject wrongTransform; - - @BeforeEach - public void init() { - openMocks(this); - } - - @Test - public void testDefault_ShouldUseSSMProvider() { - try (MockedStatic mocked = mockStatic(ParamManager.class)) { - mocked.when(() -> ParamManager.getProvider(SSMProvider.class)).thenReturn(defaultProvider); - when(defaultProvider.get("/default")).thenReturn("value"); - - assertThat(defaultValue).isEqualTo("value"); - mocked.verify(() -> ParamManager.getProvider(SSMProvider.class), times(1)); - verify(defaultProvider, times(1)).get("/default"); - - mocked.reset(); - } - } - - @Test - public void testSimple() { - assertThat(param).isEqualTo("value"); - } - - @Test - public void testWithBasicTransform() { - assertThat(basicTransform).isEqualTo("value"); - } - - @Test - public void testWithComplexTransform() { - assertThat(complexTransform) - .isInstanceOf(ObjectToDeserialize.class) - .matches( - o -> o.getFoo().equals("Foo") && - o.getBar() == 42 && - o.getBaz() == 123456789); - } - - @Test - public void testWithComplexTransformWrongTargetClass_ShouldThrowException() { - assertThatExceptionOfType(TransformationException.class) - .isThrownBy(() -> - { - AnotherObject obj = wrongTransform; - }); - } - -} diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index eca7e266f..dc22f22b6 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -8,6 +8,40 @@ https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -65,14 +99,6 @@ - - - - - - - - @@ -140,18 +166,10 @@ - - - - - - - -