Skip to content

Commit a93b984

Browse files
authored
powertools-parameters module (#90)
1 parent b57786b commit a93b984

33 files changed

+2983
-7
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,4 @@ docs/.cache
9898
docs/public
9999
/example/.aws-sam/
100100
/example/HelloWorldFunction/.aws-sam/
101+
samconfig.toml

docs/content/utilities/parameters.mdx

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
---
2+
title: Parameters
3+
description: Utility
4+
---
5+
6+
import Note from "../../src/components/Note"
7+
8+
The parameters utility provides a way to retrieve parameter values from
9+
[AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) or
10+
[AWS Secrets Manager](https://aws.amazon.com/secrets-manager/). It also provides a base class to create your parameter provider implementation.
11+
12+
**Key features**
13+
14+
* Retrieve one or multiple parameters from the underlying provider
15+
* Cache parameter values for a given amount of time (defaults to 5 seconds)
16+
* Transform parameter values from JSON or base 64 encoded strings
17+
18+
**IAM Permissions**
19+
20+
This utility requires additional permissions to work as expected. See the table below:
21+
22+
Provider | Function/Method | IAM Permission
23+
------------------------------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------
24+
SSM Parameter Store | `SSMProvider.get(String)` `SSMProvider.get(String, Class)` | `ssm:GetParameter`
25+
SSM Parameter Store | `SSMProvider.getMultiple(String)` | `ssm:GetParametersByPath`
26+
Secrets Manager | `SecretsProvider.get(String)` `SecretsProvider.get(String, Class)` | `secretsmanager:GetSecretValue`
27+
28+
## SSM Parameter Store
29+
30+
You can retrieve a single parameter using SSMProvider.get() and pass the key of the parameter.
31+
For multiple parameters, you can use SSMProvider.getMultiple() and pass the path to retrieve them all.
32+
33+
```java:title=AppWithSSM.java
34+
35+
public class AppWithSSM implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
36+
// Get an instance of the SSM Provider
37+
SSMProvider ssmProvider = ParamManager.getSsmProvider();
38+
39+
// Retrieve a single parameter
40+
String value = ssmProvider.get("/my/parameter");
41+
42+
// Retrieve multiple parameters from a path prefix
43+
// This returns a Map with the parameter name as key
44+
Map<String, String> values = ssmProvider.getMultiple("/my/path/prefix");
45+
46+
}
47+
```
48+
49+
Alternatively, you can retrieve an instance of a provider and configure its underlying SDK client,
50+
in order to get data from other regions or use specific credentials:
51+
52+
```java
53+
SsmClient client = SsmClient.builder().region(Region.EU_CENTRAL_1).build();
54+
SSMProvider ssmProvider = ParamManager.getSsmProvider(client);
55+
```
56+
### Additional arguments
57+
58+
The AWS Systems Manager Parameter Store provider supports two additional arguments for the `get()` and `getMultiple()` methods:
59+
60+
| Option | Default | Description |
61+
|---------------|---------|-------------|
62+
| **withDecryption()** | `False` | Will automatically decrypt the parameter. |
63+
| **recursive()** | `False` | For `getMultiple()` only, will fetch all parameter values recursively based on a path prefix. |
64+
65+
**Example:**
66+
67+
```java:title=AppWithSSM.java
68+
69+
public class AppWithSSM implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
70+
// Get an instance of the SSM Provider
71+
SSMProvider ssmProvider = ParamManager.getSsmProvider();
72+
73+
// Retrieve a single parameter and decrypt it
74+
String value = ssmProvider.withDecryption().get("/my/parameter");
75+
76+
// Retrieve multiple parameters recursively from a path prefix
77+
Map<String, String> values = ssmProvider.recursive().getMultiple("/my/path/prefix");
78+
79+
}
80+
```
81+
82+
## Secrets Manager
83+
84+
```java:title=AppWithSecrets.java
85+
86+
public class AppWithSecrets implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
87+
// Get an instance of the Secrets Provider
88+
SecretsProvider secretsProvider = ParamManager.getSecretsProvider();
89+
90+
// Retrieve a single secret
91+
String value = secretsProvider.get("/my/secret");
92+
93+
}
94+
```
95+
96+
Alternatively, you can retrieve an instance of a provider and configure its underlying SDK client,
97+
in order to get data from other regions or use specific credentials:
98+
99+
```java
100+
SecretsManagerClient client = SecretsManagerClient.builder().region(Region.EU_CENTRAL_1).build();
101+
SecretsProvider secretsProvider = ParamManager.getSecretsProvider(client);
102+
```
103+
104+
## Advanced configuration
105+
106+
### Caching
107+
108+
By default, all parameters and their corresponding values are cached for 5 seconds.
109+
110+
You can customize this default value using:
111+
```java
112+
provider.defaultMaxAge(int, ChronoUnit)
113+
```
114+
115+
You can also customize this value for each parameter with:
116+
```java
117+
provider.withMaxAge(int, ChronoUnit).get()
118+
```
119+
120+
### Transform values
121+
122+
Parameter values can be transformed using ```withTransformation(transformerClass)```.
123+
Base64 and JSON transformations are provided:
124+
125+
```java
126+
String value = provider
127+
.withTransformation(Transformer.base64)
128+
.get("/my/parameter/b64");
129+
```
130+
131+
For more complex transformation, you need to specify how to deserialize:
132+
133+
```java
134+
MyObj object = provider
135+
.withTransformation(Transformer.json)
136+
.get("/my/parameter/json", MyObj.class);
137+
```
138+
139+
**Note**: ```SSMProvider.getMultiple()``` does not support transformation and will return simple Strings.
140+
141+
**Write your own Transformer**
142+
143+
You can write your own transformer, by implementing the ```Transformer``` interface and the ```applyTransformation()``` method.
144+
For example, if you wish to deserialize XML into an object:
145+
146+
```java:title=XmlTransformer.java
147+
public class XmlTransformer<T> implements Transformer<T> {
148+
149+
private final XmlMapper mapper = new XmlMapper();
150+
151+
@Override
152+
public T applyTransformation(String value, Class<T> targetClass) throws TransformationException {
153+
try {
154+
return mapper.readValue(value, targetClass);
155+
} catch (IOException e) {
156+
throw new TransformationException(e);
157+
}
158+
}
159+
}
160+
```
161+
162+
Then use it like this:
163+
164+
```java
165+
MyObj object = provider
166+
.withTransformation(XmlTransformer.class)
167+
.get("/my/parameter/xml", MyObj.class);
168+
```
169+
170+
### Fluent API
171+
172+
To simplify the use of the library, you can chain all method calls before a get.
173+
174+
**Example:**
175+
176+
```java
177+
ssmProvider
178+
.defaultMaxAge(10, SECONDS) // will set 10 seconds as the default cache TTL
179+
.withMaxAge(1, MINUTES) // will set the cache TTL for this value at 1 minute
180+
.withTransformation(json) // json is a static import from Transformer.json
181+
.withDecryption() // enable decryption of the parameter value
182+
.get("/my/param", MyObj.class); // finally get the value
183+
```
184+
185+
## Create your own provider
186+
You can create your own custom parameter store provider by inheriting the ```BaseProvider``` class and implementing the
187+
```String getValue(String key)``` method to retrieve data from your underlying store.
188+
189+
All transformation and caching logic is handled by the get() methods in the base class.
190+
191+
Here is an example implementation using S3 as a custom parameter store:
192+
193+
```java:title=S3Provider.java
194+
public class S3Provider extends BaseProvider {
195+
196+
private final S3Client client;
197+
private String bucket;
198+
199+
S3Provider(CacheManager cacheManager) {
200+
this(cacheManager, S3Client.create());
201+
}
202+
203+
S3Provider(CacheManager cacheManager, S3Client client) {
204+
super(cacheManager);
205+
this.client = client;
206+
}
207+
208+
public S3Provider withBucket(String bucket) {
209+
this.bucket = bucket;
210+
return this;
211+
}
212+
213+
@Override
214+
protected String getValue(String key) {
215+
if (bucket == null) {
216+
throw new IllegalStateException("A bucket must be specified, using withBucket() method");
217+
}
218+
219+
GetObjectRequest request = GetObjectRequest.builder().bucket(bucket).key(key).build();
220+
ResponseBytes<GetObjectResponse> response = client.getObject(request, ResponseTransformer.toBytes());
221+
return response.asUtf8String();
222+
}
223+
224+
@Override
225+
protected Map<String, String> getMultipleValues(String path) {
226+
if (bucket == null) {
227+
throw new IllegalStateException("A bucket must be specified, using withBucket() method");
228+
}
229+
230+
ListObjectsV2Request listRequest = ListObjectsV2Request.builder().bucket(bucket).prefix(path).build();
231+
List<S3Object> s3Objects = client.listObjectsV2(listRequest).contents();
232+
233+
Map<String, String> result = new HashMap<>();
234+
s3Objects.forEach(s3Object -> {
235+
result.put(s3Object.key(), getValue(s3Object.key()));
236+
});
237+
238+
return result;
239+
}
240+
241+
@Override
242+
protected void resetToDefaults() {
243+
super.resetToDefaults();
244+
bucket = null;
245+
}
246+
247+
}
248+
```
249+
And then use it like this :
250+
251+
```java
252+
S3Provider provider = new S3Provider(ParamManager.getCacheManager());
253+
provider.setTransformationManager(ParamManager.getTransformationManager()); // optional, needed for transformations
254+
String value = provider.withBucket("myBucket").get("myKey");
255+
```

docs/gatsby-config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ module.exports = {
2828
'core/metrics'
2929
],
3030
'Utilities': [
31-
'utilities/sqs_large_message_handling'
31+
'utilities/sqs_large_message_handling',
32+
'utilities/parameters'
3233
],
3334
},
3435
navConfig: {

example/HelloWorldFunction/pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
<artifactId>powertools-metrics</artifactId>
2929
<version>0.3.1-beta</version>
3030
</dependency>
31+
<dependency>
32+
<groupId>software.amazon.lambda</groupId>
33+
<artifactId>powertools-parameters</artifactId>
34+
<version>0.3.1-beta</version>
35+
</dependency>
3136
<dependency>
3237
<groupId>com.amazonaws</groupId>
3338
<artifactId>aws-lambda-java-core</artifactId>
@@ -51,7 +56,7 @@
5156
<dependency>
5257
<groupId>com.fasterxml.jackson.core</groupId>
5358
<artifactId>jackson-databind</artifactId>
54-
<version>2.9.10.5</version>
59+
<version>2.11.2</version>
5560
</dependency>
5661

5762
<dependency>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package helloworld;
2+
3+
import com.amazonaws.services.lambda.runtime.Context;
4+
import com.amazonaws.services.lambda.runtime.RequestHandler;
5+
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
6+
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
7+
import org.apache.logging.log4j.LogManager;
8+
import org.apache.logging.log4j.Logger;
9+
import software.amazon.lambda.powertools.parameters.ParamManager;
10+
import software.amazon.lambda.powertools.parameters.SSMProvider;
11+
import software.amazon.lambda.powertools.parameters.SecretsProvider;
12+
13+
import java.io.BufferedReader;
14+
import java.io.IOException;
15+
import java.io.InputStreamReader;
16+
import java.net.URL;
17+
import java.util.HashMap;
18+
import java.util.Map;
19+
import java.util.stream.Collectors;
20+
21+
import static java.time.temporal.ChronoUnit.SECONDS;
22+
import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64;
23+
import static software.amazon.lambda.powertools.parameters.transform.Transformer.json;
24+
25+
public class AppParams implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
26+
27+
Logger log = LogManager.getLogger();
28+
29+
SSMProvider ssmProvider = ParamManager.getSsmProvider();
30+
SecretsProvider secretsProvider = ParamManager.getSecretsProvider();
31+
32+
String simplevalue = ssmProvider.defaultMaxAge(30, SECONDS).get("/powertools-java/sample/simplekey");
33+
String listvalue = ssmProvider.withMaxAge(60, SECONDS).get("/powertools-java/sample/keylist");
34+
MyObject jsonobj = ssmProvider.withTransformation(json).get("/powertools-java/sample/keyjson", MyObject.class);
35+
Map<String, String> allvalues = ssmProvider.getMultiple("/powertools-java/sample");
36+
String b64value = ssmProvider.withTransformation(base64).get("/powertools-java/sample/keybase64");
37+
38+
Map<String, String> secretjson = secretsProvider.withTransformation(json).get("/powertools-java/userpwd", Map.class);
39+
MyObject secretjsonobj = secretsProvider.withMaxAge(42, SECONDS).withTransformation(json).get("/powertools-java/secretcode", MyObject.class);
40+
41+
@Override
42+
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
43+
44+
log.info("\n=============== SSM Parameter Store ===============");
45+
log.info("simplevalue={}, listvalue={}, b64value={}\n", simplevalue, listvalue, b64value);
46+
log.info("jsonobj={}\n", jsonobj);
47+
48+
log.info("allvalues (multiple):");
49+
allvalues.forEach((key, value) -> log.info("- {}={}\n", key, value));
50+
51+
log.info("\n=============== Secrets Manager ===============");
52+
log.info("secretjson:");
53+
secretjson.forEach((key, value) -> log.info("- {}={}\n", key, value));
54+
log.info("secretjsonobj={}\n", secretjsonobj);
55+
56+
Map<String, String> headers = new HashMap<>();
57+
headers.put("Content-Type", "application/json");
58+
headers.put("X-Custom-Header", "application/json");
59+
60+
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent()
61+
.withHeaders(headers);
62+
try {
63+
final String pageContents = this.getPageContents("https://checkip.amazonaws.com");
64+
String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents);
65+
66+
return response
67+
.withStatusCode(200)
68+
.withBody(output);
69+
} catch (IOException e) {
70+
return response
71+
.withBody("{}")
72+
.withStatusCode(500);
73+
}
74+
}
75+
76+
private String getPageContents(String address) throws IOException{
77+
URL url = new URL(address);
78+
try(BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
79+
return br.lines().collect(Collectors.joining(System.lineSeparator()));
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)