Skip to content

Commit

Permalink
Add integration test for S3 and placeholders for CF and CW
Browse files Browse the repository at this point in the history
  • Loading branch information
belosh59 committed Jun 14, 2021
1 parent e7c117b commit d8c910d
Show file tree
Hide file tree
Showing 14 changed files with 382 additions and 122 deletions.
6 changes: 6 additions & 0 deletions magpie-aws/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,12 @@
<version>${testcontainer.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>cloudformation</artifactId>
<version>${aws.sdk.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.javatuples.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.exception.SdkServiceException;
import software.amazon.awssdk.regions.Region;
Expand All @@ -36,6 +37,7 @@

import javax.annotation.Nullable;
import java.io.IOException;
import java.net.URI;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
Expand Down Expand Up @@ -181,7 +183,7 @@ public static Pair<Double, GetMetricStatisticsResponse> getCloudwatchDoubleMetri

public static GetMetricStatisticsResponse getCloudwatchMetricStatistics( String regionID, String namespace, String metric, Statistic statistic, List<Dimension> dimensions) {

try (final CloudWatchClient client = CloudWatchClient.builder().region(Region.of(regionID)).build()) {
try (final CloudWatchClient client = configure(CloudWatchClient.builder(), Region.of(regionID))) {

// The start time is t-minus 2 days (48 hours) because an asset is considered "active" if it's been updated within
// 48hrs, otherwise it is considered "terminated/deleted", so start capturing at the longest possible period
Expand All @@ -200,7 +202,20 @@ public static GetMetricStatisticsResponse getCloudwatchMetricStatistics( String
return client.getMetricStatistics(request);
}
}

public static String getAwsAccountId() {
return StsClient.create().getCallerIdentity().account();
}

public static <BuilderT extends AwsClientBuilder<BuilderT, ClientT>, ClientT> ClientT
configure(AwsClientBuilder<BuilderT, ClientT> builder, Region region) {
// Remap magpie clients to local environment
String magpieAwsEndpoint = System.getProperty("MAGPIE_AWS_ENDPOINT");
if (magpieAwsEndpoint != null) {
builder.endpointOverride(URI.create(magpieAwsEndpoint));
}
// Build for region only
builder.region(region);
return builder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.Map;
import java.util.stream.Collectors;

import static io.openraven.magpie.plugins.aws.discovery.AWSUtils.configure;
import static io.openraven.magpie.plugins.aws.discovery.AWSUtils.getAwsResponse;

public class DynamoDbDiscovery implements AWSDiscovery {
Expand All @@ -54,7 +55,7 @@ public List<Region> getSupportedRegions() {

@Override
public void discover(ObjectMapper mapper, Session session, Region region, Emitter emitter, Logger logger, String account) {
final var client = DynamoDbClient.builder().region(region).build();
final var client = configure(DynamoDbClient.builder(), region);

discoverGlobalTables(mapper, session, region, emitter, client, account);
discoverTables(mapper, session, region, emitter, client, account);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import static io.openraven.magpie.plugins.aws.discovery.AWSUtils.configure;
import static io.openraven.magpie.plugins.aws.discovery.AWSUtils.getAwsResponse;

public class S3Discovery implements AWSDiscovery {
Expand All @@ -67,7 +68,7 @@ public List<Region> getSupportedRegions() {

@Override
public void discover(ObjectMapper mapper, Session session, Region region, Emitter emitter, Logger logger, String account) {
final var client = S3Client.builder().region(region).build();
final var client = configure(S3Client.builder(), region);
final String RESOURCE_TYPE = "AWS::S3::Bucket";

try {
Expand Down Expand Up @@ -110,9 +111,9 @@ public void discover(ObjectMapper mapper, Session session, Region region, Emitte
private Optional<List<Bucket>> getBuckets(Session session, S3Client client, Region bucketRegion, Logger logger) {
try {
//
// This method is executed whenever the bucket cache does not contain entries for a given session ID. This ensures
// that we only make this expensive computation the lesser of once per scan or once per timeout period (defined above).
//
// This method is executed whenever the bucket cache does not contain entries for a given session ID. This ensures
// that we only make this expensive computation the lesser of once per scan or once per timeout period (defined above).
//
var buckets = bucketCache.get(session.getId(), () -> {
logger.debug("No cache found for {}, creating one now.", session);
var map = new HashMap<Region, List<Bucket>>();
Expand All @@ -121,7 +122,9 @@ private Optional<List<Bucket>> getBuckets(Session session, S3Client client, Regi
final var location = resp.locationConstraint();
// Thanks to https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/model/GetBucketLocationResponse.html#locationConstraint--
// we need to be aware of both null and UNKNOWN_TO_SDK_VERSION values.
var region = resp.locationConstraintAsString().isEmpty() ? Region.US_EAST_1 : Region.of(location.toString());
var region = Region.US_EAST_1.toString().equals(resp.locationConstraintAsString())
? Region.US_EAST_1
: Region.of(location.toString());
logger.debug("Associating {} to region {}", bucket.name(), region);
var list = map.getOrDefault(region, new LinkedList<>());
list.add(bucket);
Expand Down Expand Up @@ -163,7 +166,10 @@ private void discoverPublic(S3Client client, Bucket resource, AWSResource data)
.bucket(resource.name())
.build());

isPublicByPolicy = bucketPolicyStatus.policyStatus().isPublic();
// TODO : Does Null check required if policy not setup (catch with test)
isPublicByPolicy = bucketPolicyStatus.policyStatus().isPublic() != null
? bucketPolicyStatus.policyStatus().isPublic()
: false;
} catch (SdkServiceException ex) {
if (!(ex.statusCode() == 403 || ex.statusCode() == 404)) {
throw ex;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.openraven.magpie.plugins.aws.discovery.services;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.openraven.magpie.plugins.aws.discovery.AWSUtils;
import org.junit.jupiter.api.BeforeAll;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.utility.DockerImageName;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudformation.CloudFormationClient;
import software.amazon.awssdk.services.cloudformation.model.*;

import java.util.Objects;
import java.util.Scanner;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.*;

public abstract class BaseAWSServiceIT {

private static final Logger LOGGER = LoggerFactory.getLogger(BaseAWSServiceIT.class);
private static final int EXPOSED_EDGE_PORT = 4566;
protected static final String EMPTY_STACK_TEMPLATE_PATH = "/template/empty-stack.yml";
private static final String STACK_NAME = "integration-stack-" + System.nanoTime();

protected static final Region BASE_REGION = Region.US_WEST_1;

private static CloudFormationClient cfClient;

protected static final ObjectMapper MAPPER = new ObjectMapper()
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.findAndRegisterModules();

protected static LocalStackContainer localStackContainer =
new LocalStackContainer(DockerImageName.parse("localstack/localstack:0.11.3"))
.withExposedPorts(EXPOSED_EDGE_PORT)
.withEnv("DEFAULT_REGION", BASE_REGION.id())
.withEnv("DEBUG", "1")
.withServices(DYNAMODB, CLOUDWATCH, S3, CLOUDFORMATION, IAM); // At the moment cannot launch services dynamically

static {
localStackContainer.start();
setupEnvironment();
initiateCloudFormationClient();
startStackWithResources(EMPTY_STACK_TEMPLATE_PATH);
}

protected static void setupEnvironment() {
System.getProperties().setProperty("aws.accessKeyId", "foo");
System.getProperties().setProperty("aws.secretAccessKey", "bar");
System.getProperties().setProperty("MAGPIE_AWS_ENDPOINT",
String.format("http://%s:%d",
localStackContainer.getHost(),
localStackContainer.getMappedPort(EXPOSED_EDGE_PORT)));
}

protected static void initiateCloudFormationClient() {
cfClient = AWSUtils.configure(CloudFormationClient.builder(), BASE_REGION);
}

protected static void startStackWithResources(String templatePath) {
CreateStackRequest createStackRequest = CreateStackRequest.builder()
.stackName(STACK_NAME)
.templateBody(getCFTemplate(templatePath))
.build();

CreateStackResponse createdStack = cfClient.createStack(createStackRequest);
assertNotNull(createdStack.stackId());
LOGGER.info("Stack has been created with definition: {}", createdStack); // For transparency purpose
}

protected static void updateStackWithResources(String templatePath) {
UpdateStackRequest updateStackRequest = UpdateStackRequest.builder()
.stackName(STACK_NAME)
.templateBody(getCFTemplate(templatePath))
.build();

UpdateStackResponse updateStackResponse = cfClient.updateStack(updateStackRequest);
assertNotNull(updateStackResponse.stackId());
LOGGER.info("Stack has been updated with template: {}", templatePath);
}

private static String getCFTemplate(String resourcePath) {
return new Scanner(Objects.requireNonNull(BaseAWSServiceIT.class.getResourceAsStream(resourcePath)), "UTF-8")
.useDelimiter("\\A").next();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.openraven.magpie.plugins.aws.discovery.services;

import io.openraven.magpie.api.Emitter;
import io.openraven.magpie.api.MagpieEnvelope;
import io.openraven.magpie.api.Session;
import io.openraven.magpie.plugins.aws.discovery.AWSUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;

import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
class DynamoDbDiscoveryIT extends BaseAWSServiceIT {

private static final String TEST_TABLE = "entities";
private static final String CF_DYNAMODB_TEMPLATE_PATH = "/template/dynamo-db-template.yml";

private final DynamoDbDiscovery dynamoDbDiscovery = new DynamoDbDiscovery();

@Mock
private Emitter emitter;

@Captor
private ArgumentCaptor<MagpieEnvelope> envelopeCapture;

@Test
void discoverDynamoDB() {
// prepare
DynamoDbClient dynamoDbClient = AWSUtils.configure(DynamoDbClient.builder(), BASE_REGION);
updateStackWithResources(CF_DYNAMODB_TEMPLATE_PATH);

// Execute
dynamoDbDiscovery.discoverTables(
MAPPER,
new Session(),
BASE_REGION,
emitter,
dynamoDbClient,
"account");

// Verify
Mockito.verify(emitter).emit(envelopeCapture.capture());
var contents = envelopeCapture.getValue().getContents();

assertNotNull(contents.get("documentId"));
assertEquals(String.format("arn:aws:dynamodb:%s:000000000000:table/%s", BASE_REGION, TEST_TABLE),
contents.get("arn").asText());
assertEquals(TEST_TABLE, contents.get("resourceName").asText());
assertEquals("AWS::DynamoDB::Table", contents.get("resourceType").asText());
assertEquals(BASE_REGION.toString(), contents.get("awsRegion").asText());
}

// TODO: Global Table coverage required
}

This file was deleted.

Loading

0 comments on commit d8c910d

Please sign in to comment.