Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add DynamoDB provider to parameters module #1091

Merged
merged 12 commits into from
Mar 20, 2023
4 changes: 4 additions & 0 deletions powertools-parameters/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@
<groupId>software.amazon.awssdk</groupId>
<artifactId>url-connection-client</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
scottgerring marked this conversation as resolved.
Show resolved Hide resolved
<artifactId>dynamodb</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package software.amazon.lambda.powertools.parameters;

import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
import software.amazon.awssdk.core.SdkSystemSetting;
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.*;
import software.amazon.lambda.powertools.parameters.cache.CacheManager;

import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;

public class DynamoDBProvider extends BaseProvider {

private final DynamoDbClient client;
private final String tableName;

public DynamoDBProvider(CacheManager cacheManager, String tableName) {
this(cacheManager, DynamoDbClient.builder()
.httpClientBuilder(UrlConnectionHttpClient.builder())
scottgerring marked this conversation as resolved.
Show resolved Hide resolved
//.credentialsProvider(EnvironmentVariableCredentialsProvider.create())
//.region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable())))
.build(),
tableName
);

}

DynamoDBProvider(CacheManager cacheManager, DynamoDbClient client, String tableName) {
super(cacheManager);
this.client = client;
this.tableName = tableName;
}

@Override
protected String getValue(String key) {
GetItemResponse resp = client.getItem(GetItemRequest.builder()
.tableName(tableName)
.key(Collections.singletonMap("id", AttributeValue.fromS(key)))
.attributesToGet("val")
.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()) {
return resp.item().get("val").s();
}

return null;
}

@Override
protected Map<String, String> getMultipleValues(String path) {

QueryResponse resp = client.query(QueryRequest.builder()
scottgerring marked this conversation as resolved.
Show resolved Hide resolved
.tableName(tableName)
.keyConditionExpression("id = :v_id")
.expressionAttributeValues(Collections.singletonMap(":v_id", AttributeValue.fromS(path)))
.build());

return resp
.items()
.stream()
.collect(
Collectors.toMap(
(i) -> i.get("sk").s(),
(i) -> i.get("val").s()));


}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package software.amazon.lambda.powertools.parameters;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.lambda.powertools.parameters.cache.CacheManager;

import java.util.HashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

/**
* This class provides simple end-to-end style testing of the DynamoDBProvider class.
* It is ignored, for now, as it requires AWS access and that's not yet run as part
* of our unit test suite in the cloud.
*
* The test is kept here for 1/ local development and 2/ in preparation for future
* E2E tests running in the cloud CI.
*/
@Disabled
public class DynamoDBProviderE2ETest {

final String ParamsTestTable = "ddb-params-test";
final String MultiparamsTestTable = "ddb-multiparams-test";
private DynamoDbClient ddbClient;

public DynamoDBProviderE2ETest() {
// Create a DDB client to inject test data into our test tables
ddbClient = DynamoDbClient.builder()
.httpClientBuilder(UrlConnectionHttpClient.builder())
.build();
}

@Test
public void TestGetValue() {

// Arrange
HashMap<String, AttributeValue> testItem = new HashMap<String, AttributeValue>();
testItem.put("id", AttributeValue.fromS("test_param"));
testItem.put("val", AttributeValue.fromS("the_value_is_hello!"));
ddbClient.putItem(PutItemRequest.builder()
.tableName(ParamsTestTable)
.item(testItem)
.build());

// Act
DynamoDBProvider provider = new DynamoDBProvider(new CacheManager(), ParamsTestTable);

// Assert
String value = provider.getValue("test_param");
assertThat(value).isEqualTo("the_value_is_hello!");
}

@Test
public void TestGetValues() {

// Arrange
HashMap<String, AttributeValue> testItem = new HashMap<String, AttributeValue>();
testItem.put("id", AttributeValue.fromS("test_param"));
testItem.put("sk", AttributeValue.fromS("test_param_part_1"));
testItem.put("val", AttributeValue.fromS("the_value_is_hello!"));
ddbClient.putItem(PutItemRequest.builder()
.tableName(MultiparamsTestTable)
.item(testItem)
.build());

HashMap<String, AttributeValue> testItem2 = new HashMap<String, AttributeValue>();
testItem2.put("id", AttributeValue.fromS("test_param"));
testItem2.put("sk", AttributeValue.fromS("test_param_part_2"));
testItem2.put("val", AttributeValue.fromS("the_value_is_still_hello!"));
ddbClient.putItem(PutItemRequest.builder()
.tableName(MultiparamsTestTable)
.item(testItem2)
.build());

// Act
DynamoDBProvider provider = new DynamoDBProvider(new CacheManager(), MultiparamsTestTable);
Map<String, String> values = provider.getMultipleValues("test_param");

// Assert
assertThat(values.size()).isEqualTo(2);
assertThat(values.get("test_param_part_1")).isEqualTo("the_value_is_hello!");
assertThat(values.get("test_param_part_2")).isEqualTo("the_value_is_still_hello!");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package software.amazon.lambda.powertools.parameters;

import org.junit.jupiter.api.Assertions;
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 org.mockito.Mockito;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;
import software.amazon.lambda.powertools.parameters.cache.CacheManager;

import java.util.HashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.MockitoAnnotations.openMocks;

public class DynamoDBProviderTest {

@Mock
DynamoDbClient client;

@Captor
ArgumentCaptor<GetItemRequest> getItemValueCaptor;

@Captor
ArgumentCaptor<QueryRequest> queryRequestCaptor;


private DynamoDBProvider provider;
private final String tableName = "ddb-test-table";

@BeforeEach
public void init() {
openMocks(this);
CacheManager cacheManager = new CacheManager();
provider = new DynamoDBProvider(cacheManager, client, tableName);
}


@Test
public void getValue() {

// Arrange
String key = "Key1";
String expectedValue = "Value1";
HashMap<String, AttributeValue> responseData = new HashMap<String, AttributeValue>();
responseData.put("id", AttributeValue.fromS(key));
responseData.put("val", AttributeValue.fromS(expectedValue));
GetItemResponse response = GetItemResponse.builder()
.item(responseData)
.build();
Mockito.when(client.getItem(getItemValueCaptor.capture())).thenReturn(response);

// Act
String value = provider.getValue(key);

// Assert
assertThat(value).isEqualTo(expectedValue);
assertThat(getItemValueCaptor.getValue().tableName()).isEqualTo(tableName);
assertThat(getItemValueCaptor.getValue().key().get("id").s()).isEqualTo(key);
}


@Test
public void getValueWithoutResultsReturnsNull() {
// Arrange
Mockito.when(client.getItem(getItemValueCaptor.capture())).thenReturn(GetItemResponse.builder()
.item(null)
.build());

// Act
String value = provider.getValue("key");

// Assert
assertThat(value).isEqualTo(null);
}

@Test
public void getValueWithMalformedRowThrows() {
// Arrange
String key = "Key1";
HashMap<String, AttributeValue> responseData = new HashMap<String, AttributeValue>();
responseData.put("id", AttributeValue.fromS(key));
responseData.put("not-val", AttributeValue.fromS("something"));
Mockito.when(client.getItem(getItemValueCaptor.capture())).thenReturn(GetItemResponse.builder()
.item(responseData)
.build());
// Act
Assertions.assertThrows(NullPointerException.class, () -> {
String value = provider.getValue(key);
});
}


@Test
public void getValues() {

// Arrange
String key = "Key1";
String subkey1 = "Subkey1";
String val1 = "Val1";
String subkey2 = "Subkey2";
String val2 = "Val2";
HashMap<String, AttributeValue> item1 = new HashMap<String, AttributeValue>();
item1.put("id", AttributeValue.fromS(key));
item1.put("sk", AttributeValue.fromS(subkey1));
item1.put("val", AttributeValue.fromS(val1));
HashMap<String, AttributeValue> item2 = new HashMap<String, AttributeValue>();
item2.put("id", AttributeValue.fromS(key));
item2.put("sk", AttributeValue.fromS(subkey2));
item2.put("val", AttributeValue.fromS(val2));
QueryResponse response = QueryResponse.builder()
.items(item1, item2)
.build();
Mockito.when(client.query(queryRequestCaptor.capture())).thenReturn(response);

// Act
Map<String, String> values = provider.getMultipleValues(key);

// Assert
assertThat(values.size()).isEqualTo(2);
assertThat(values.get(subkey1)).isEqualTo(val1);
assertThat(values.get(subkey2)).isEqualTo(val2);
assertThat(queryRequestCaptor.getValue().tableName()).isEqualTo(tableName);
assertThat(queryRequestCaptor.getValue().keyConditionExpression()).isEqualTo("id = :v_id");
assertThat(queryRequestCaptor.getValue().expressionAttributeValues().get(":v_id").s()).isEqualTo(key);
}
}