From fdd1612f0323deaa7f00e6c3396bc31402cd964d Mon Sep 17 00:00:00 2001 From: Michael Wittig Date: Thu, 23 Feb 2017 17:06:14 +0100 Subject: [PATCH] added tests to project --- test/.gitignore | 3 + test/README.md | 73 ++++++++ test/pom.xml | 93 ++++++++++ .../de/widdix/awscftemplates/AAWSTest.java | 121 ++++++++++++ .../awscftemplates/ACloudFormationTest.java | 172 ++++++++++++++++++ .../java/de/widdix/awscftemplates/ATest.java | 41 +++++ .../java/de/widdix/awscftemplates/Config.java | 54 ++++++ .../ec2/TestEC2AutoRecovery.java | 46 +++++ .../awscftemplates/ecs/TestECSCluster.java | 77 ++++++++ .../awscftemplates/ecs/TestECSService.java | 157 ++++++++++++++++ .../awscftemplates/jenkins/TestJenkins.java | 110 +++++++++++ .../security/TestSecurityAuthProxy.java | 72 ++++++++ .../staticwebsite/TestStaticWebsite.java | 46 +++++ .../awscftemplates/vpc/TestVPC2AZs.java | 25 +++ .../awscftemplates/vpc/TestVPC3AZs.java | 25 +++ .../awscftemplates/vpc/TestVPC4AZs.java | 25 +++ .../awscftemplates/vpc/TestVPCEndpointS3.java | 36 ++++ .../awscftemplates/vpc/TestVPCNatGateway.java | 37 ++++ .../vpc/TestVPCNatInstance.java | 44 +++++ .../awscftemplates/vpc/TestVPCSshBastion.java | 46 +++++ .../wordpress/TestWordpressHA.java | 76 ++++++++ 21 files changed, 1379 insertions(+) create mode 100644 test/.gitignore create mode 100644 test/README.md create mode 100644 test/pom.xml create mode 100644 test/src/test/java/de/widdix/awscftemplates/AAWSTest.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/ACloudFormationTest.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/ATest.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/Config.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/ec2/TestEC2AutoRecovery.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/ecs/TestECSCluster.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/ecs/TestECSService.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/jenkins/TestJenkins.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/security/TestSecurityAuthProxy.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/staticwebsite/TestStaticWebsite.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/vpc/TestVPC2AZs.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/vpc/TestVPC3AZs.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/vpc/TestVPC4AZs.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCEndpointS3.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCNatGateway.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCNatInstance.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCSshBastion.java create mode 100644 test/src/test/java/de/widdix/awscftemplates/wordpress/TestWordpressHA.java diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 000000000..ec1055155 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,3 @@ +target/ +.idea/ +*.iml diff --git a/test/README.md b/test/README.md new file mode 100644 index 000000000..a8f325dcd --- /dev/null +++ b/test/README.md @@ -0,0 +1,73 @@ +# Free Templates for AWS CloudFormation + +Tests for our free Templates for AWS CloudFormation. The goal of this tests is to ensure that our templates are always working. The test are implemented in Java 8 and run in JUnit 4. + +If you run this tests, many AWS CloudFormation tests are created and **charges will apply**! + +[widdix GmbH](https://widdix.net) sponsors the test runs on every push and once per week to ensure that everything is working as expected. + +## Prerequisits + +To run this tests you need: +* A resolvable Route 53 hosted zone where record sets can be added automatically (**NOT** your production environment!) +* A domain suffix that cen be used for subdomains (e.g. `awstest.mydomain.com`) +* A wildcard ACM certificate in `us-east-1` that matches with the domain suffix from above (e.g. `*.awstest.mydomain.com`) +* A wildcard ACM certificate in the region you want to run this tests in like above + +## Supported env variables + +* `HOSTED_ZONE_ID` **required** A hosted zone id of your Route 53 hosted zone where the tests can create record sets +* `DOMAIN_SUFFIX` **required** A domain suffix that is part of your hosted zone +* `CLOUDFRONT_ACM_CERTIFICATE_ARN` **required** A wildcard ACM certificate in `us-east-1` +* `ACM_CERTIFICATE_ARN` **required** A wildcard ACM certificate in the region where the tests run +* `IAM_ROLE_ARN` if the tests should assume an IAM role before they run supply the ARN of the IAM role +* `TEMPLATE_DIR` Load templates from local disk (instead of S3 bucket `widdix-aws-cf-templates`). Must end with an `/`. See `BUCKET` as well. +* `BUCKET` Some templates are to big to be passed as a string from local disk, therefore you need to supply the name of the bucket that is used to upload templates. + +## Usage + +### AWS credentials + +The AWS credentials are passed in as defined by the AWS SDK for Java: http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html + +One addition is, that you can supply the env variable `IAM_ROLE_ARN` which let's the tests assume a role before they start with the default credentials. + +### Region selection + +The region selection works like defined by the AWS SDK for Java: http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/java-dg-region-selection.html + +### Run all tests + +``` +AWS_REGION="us-east-1" HOSTED_ZONE_ID="..." DOMAIN_SUFFIX="..." CLOUDFRONT_ACM_CERTIFICATE_ARN="..." ACM_CERTIFICATE_ARN="..." mvn test +``` + +### Run a single test suite + +to run the `TestJenkins` tests: + +``` +AWS_REGION="us-east-1" HOSTED_ZONE_ID="..." DOMAIN_SUFFIX="..." CLOUDFRONT_ACM_CERTIFICATE_ARN="..." ACM_CERTIFICATE_ARN="..." mvn -Dtest=TestJenkins test +``` + +### Run a single test + +to run the `TestJenkins.testHA` test: + +``` +AWS_REGION="us-east-1" HOSTED_ZONE_ID="..." DOMAIN_SUFFIX="..." CLOUDFRONT_ACM_CERTIFICATE_ARN="..." ACM_CERTIFICATE_ARN="..." mvn -Dtest=TestJenkins#testHA test +``` + +### Load templates from local file system + +``` +AWS_REGION="us-east-1" HOSTED_ZONE_ID="..." DOMAIN_SUFFIX="..." CLOUDFRONT_ACM_CERTIFICATE_ARN="..." ACM_CERTIFICATE_ARN="..." BUCKET="..." TEMPLATE_DIR="/path/to/widdix-aws-cf-templates/" mvn test +``` + +### Assume role + +This is useful if you run on a integration server like Jenkins and want to assume a different IAM role for this tests. + +``` +IAM_ROLE_ARN="arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME" mvn test +``` diff --git a/test/pom.xml b/test/pom.xml new file mode 100644 index 000000000..f746cc91f --- /dev/null +++ b/test/pom.xml @@ -0,0 +1,93 @@ + + + 4.0.0 + + de.widdix + awscftemplates-tests + 1.0-SNAPSHOT + + + + com.amazonaws + aws-java-sdk-cloudformation + test + + + com.amazonaws + aws-java-sdk-ec2 + test + + + com.amazonaws + aws-java-sdk-s3 + test + + + com.amazonaws + aws-java-sdk-route53 + test + + + com.amazonaws + aws-java-sdk-sts + test + + + com.amazonaws + aws-java-sdk-ecs + test + + + de.taimos + httputils + 1.10 + test + + + com.evanlennick + retry4j + 0.6.2 + test + + + com.jcraft + jsch + 0.1.54 + test + + + junit + junit + 4.12 + test + + + + + + + com.amazonaws + aws-java-sdk-bom + 1.11.95 + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 1.8 + 1.8 + + + + + diff --git a/test/src/test/java/de/widdix/awscftemplates/AAWSTest.java b/test/src/test/java/de/widdix/awscftemplates/AAWSTest.java new file mode 100644 index 000000000..0bd1ffa94 --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/AAWSTest.java @@ -0,0 +1,121 @@ +package de.widdix.awscftemplates; + +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; +import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; +import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.AmazonEC2AsyncClientBuilder; +import com.amazonaws.services.ec2.model.*; +import com.amazonaws.services.route53.AmazonRoute53AsyncClientBuilder; +import com.amazonaws.services.route53.model.*; +import com.amazonaws.services.securitytoken.AWSSecurityTokenService; +import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; +import com.amazonaws.services.route53.AmazonRoute53; + +import java.util.List; +import java.util.UUID; + +public abstract class AAWSTest extends ATest { + + public final static String IAM_SESSION_NAME = "aws-cf-templates"; + + protected final AWSCredentialsProvider credentialsProvider; + + private AmazonEC2 ec2; + + private AmazonRoute53 route53; + + public AAWSTest() { + super(); + if (Config.has(Config.Key.IAM_ROLE_ARN)) { + final AWSSecurityTokenService sts = AWSSecurityTokenServiceClientBuilder.standard().build(); + this.credentialsProvider = new STSAssumeRoleSessionCredentialsProvider.Builder(Config.get(Config.Key.IAM_ROLE_ARN), IAM_SESSION_NAME).withStsClient(sts).build(); + } else { + this.credentialsProvider = new DefaultAWSCredentialsProviderChain(); + } + this.ec2 = AmazonEC2AsyncClientBuilder.standard().withCredentials(this.credentialsProvider).build(); + this.route53 = AmazonRoute53AsyncClientBuilder.standard().withCredentials(this.credentialsProvider).build(); + } + + protected final KeyPair createKey(final String keyName) { + final CreateKeyPairResult res = this.ec2.createKeyPair(new CreateKeyPairRequest().withKeyName(keyName)); + return res.getKeyPair(); + } + + protected final void deleteKey(final String keyName) { + this.ec2.deleteKeyPair(new DeleteKeyPairRequest().withKeyName(keyName)); + } + + private void waitForDomain(final String name, final String changeId, final ChangeStatus finalStatus) { + System.out.println("waitForDomain[" + name + "]: to reach status " + finalStatus); + while (true) { + try { + Thread.sleep(5000); + } catch (final InterruptedException e) { + // continue + } + final GetChangeResult res = this.route53.getChange(new GetChangeRequest().withId(changeId)); + final ChangeStatus currentStatus = ChangeStatus.fromValue(res.getChangeInfo().getStatus()); + if (finalStatus == currentStatus) { + System.out.println("waitForDomain[" + name + "]: final status reached."); + return; + } else { + System.out.println("waitForDomain[" + name + "]: continue to wait (still in intermediate status " + currentStatus + ") ..."); + } + } + } + + protected final String generateDomain(final String prefix) { + return prefix + "." + Config.get(Config.Key.DOMAIN_SUFFIX); + } + + protected final String createDomain(final String prefix, final String host) { + final String name = this.generateDomain(prefix); + final ResourceRecord rr = new ResourceRecord(host); + final ResourceRecordSet rrs = new ResourceRecordSet(name, RRType.CNAME).withTTL(60L).withResourceRecords(rr); + final Change create = new Change().withAction(ChangeAction.CREATE).withResourceRecordSet(rrs); + final ChangeBatch changeBatch = new ChangeBatch().withChanges(create); + final ChangeResourceRecordSetsRequest req = new ChangeResourceRecordSetsRequest().withHostedZoneId(Config.get(Config.Key.HOSTED_ZONE_ID)).withChangeBatch(changeBatch); + final ChangeResourceRecordSetsResult res = this.route53.changeResourceRecordSets(req); + this.waitForDomain(name, res.getChangeInfo().getId(), ChangeStatus.INSYNC); + return name; + } + + protected final void deleteDomain(final String prefix) { + final String name = this.generateDomain(prefix); + final ListResourceRecordSetsResult res1 = this.route53.listResourceRecordSets(new ListResourceRecordSetsRequest().withHostedZoneId(Config.get(Config.Key.HOSTED_ZONE_ID)).withStartRecordName(name)); + final ResourceRecordSet rrs = res1.getResourceRecordSets().get(0); + final Change delete = new Change().withAction(ChangeAction.DELETE).withResourceRecordSet(rrs); + final ChangeBatch changeBatch = new ChangeBatch().withChanges(delete); + final ChangeResourceRecordSetsRequest req = new ChangeResourceRecordSetsRequest().withHostedZoneId(Config.get(Config.Key.HOSTED_ZONE_ID)).withChangeBatch(changeBatch); + final ChangeResourceRecordSetsResult res2 = this.route53.changeResourceRecordSets(req); + this.waitForDomain(name, res2.getChangeInfo().getId(), ChangeStatus.INSYNC); + } + + protected final Vpc getDefaultVPC() { + final DescribeVpcsResult res = this.ec2.describeVpcs(new DescribeVpcsRequest().withFilters(new Filter().withName("isDefault").withValues("true"))); + return res.getVpcs().get(0); + } + + protected final List getDefaultSubnets() { + final DescribeSubnetsResult res = this.ec2.describeSubnets(new DescribeSubnetsRequest().withFilters(new Filter().withName("defaultForAz").withValues("true"))); + return res.getSubnets(); + } + + protected final SecurityGroup getDefaultSecurityGroup() { + final Vpc vpc = this.getDefaultVPC(); + final DescribeSecurityGroupsResult res = this.ec2.describeSecurityGroups(new DescribeSecurityGroupsRequest().withFilters( + new Filter().withName("vpc-id").withValues(vpc.getVpcId()), + new Filter().withName("group-name").withValues("default") + )); + return res.getSecurityGroups().get(0); + } + + protected final String random8String() { + final String uuid = UUID.randomUUID().toString().replace("-", "").toLowerCase(); + final int beginIndex = (int) (Math.random() * (uuid.length() - 7)); + final int endIndex = beginIndex + 7; + return "r" + uuid.substring(beginIndex, endIndex); // must begin [a-z] + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/ACloudFormationTest.java b/test/src/test/java/de/widdix/awscftemplates/ACloudFormationTest.java new file mode 100644 index 000000000..1bbc0b993 --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/ACloudFormationTest.java @@ -0,0 +1,172 @@ +package de.widdix.awscftemplates; + +import com.amazonaws.AmazonServiceException; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.cloudformation.AmazonCloudFormation; +import com.amazonaws.services.cloudformation.AmazonCloudFormationClientBuilder; +import com.amazonaws.services.cloudformation.model.*; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; + +public abstract class ACloudFormationTest extends AAWSTest { + + public static String readFile(String path, Charset encoding) { + try { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, encoding); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + private AmazonCloudFormation cf = AmazonCloudFormationClientBuilder.standard().withCredentials(this.credentialsProvider).build(); + + private AmazonS3 s3 = AmazonS3ClientBuilder.standard().withCredentials(this.credentialsProvider).build(); + + public ACloudFormationTest() { + super(); + } + + protected final void createStack(final String stackName, final String template, final Parameter...parameters) { + CreateStackRequest req = new CreateStackRequest() + .withStackName(stackName) + .withParameters(parameters) + .withCapabilities(Capability.CAPABILITY_IAM); + if (Config.has(Config.Key.TEMPLATE_DIR)) { + final String dir = Config.get(Config.Key.TEMPLATE_DIR); + if (Config.has(Config.Key.BUCKET)) { + final String bucket = Config.get(Config.Key.BUCKET); + final String bucketLocation = s3.getBucketLocation(bucket); + s3.putObject(bucket, stackName, new File(dir + template)); + req = req.withTemplateURL("https://s3-" + bucketLocation + ".amazonaws.com/" + bucket + "/" + stackName); + } else { + final String body = readFile(dir + template, Charset.forName("UTF-8")); + req = req.withTemplateBody(body); + } + } else { + req = req.withTemplateURL("https://s3-eu-west-1.amazonaws.com/widdix-aws-cf-templates/" + template); + } + this.cf.createStack(req); + } + + protected enum FinalStatus { + CREATE_COMPLETE(StackStatus.CREATE_COMPLETE, false, true, StackStatus.CREATE_IN_PROGRESS), + DELETE_COMPLETE(StackStatus.DELETE_COMPLETE, true, false, StackStatus.DELETE_IN_PROGRESS); + + private final StackStatus finalStatus; + private final boolean notFoundIsFinalStatus; + private final boolean notFoundIsIntermediateStatus; + private final Set intermediateStatus; + + FinalStatus(StackStatus finalStatus, boolean notFoundIsFinalStatus, boolean notFoundIsIntermediateStatus, StackStatus...intermediateStatus) { + this.finalStatus = finalStatus; + this.notFoundIsFinalStatus = notFoundIsFinalStatus; + this.notFoundIsIntermediateStatus = notFoundIsIntermediateStatus; + this.intermediateStatus = new HashSet<>(Arrays.asList(intermediateStatus)); + } + } + + private List getStackEvents(final String stackName) { + final List events = new ArrayList<>(); + String nextToken = null; + do { + try { + final DescribeStackEventsResult res = this.cf.describeStackEvents(new DescribeStackEventsRequest().withStackName(stackName).withNextToken(nextToken)); + events.addAll(res.getStackEvents()); + nextToken = res.getNextToken(); + } catch (final AmazonServiceException e) { + if (e.getErrorMessage().equals("Stack [" + stackName + "] does not exist")) { + nextToken = null; + } else { + throw e; + } + } + } while (nextToken != null); + Collections.reverse(events); + return events; + } + + protected final void waitForStack(final String stackName, final FinalStatus finalStackStatus) { + System.out.println("waitForStack[" + stackName + "]: to reach status " + finalStackStatus.finalStatus); + final List eventsDisplayed = new ArrayList<>(); + while (true) { + try { + Thread.sleep(5000); + } catch (final InterruptedException e) { + // continue + } + final List events = getStackEvents(stackName); + for (final StackEvent event : events) { + boolean displayed = false; + for (final StackEvent eventDisplayed : eventsDisplayed) { + if (event.getEventId().equals(eventDisplayed.getEventId())) { + displayed = true; + } + } + if (!displayed) { + System.out.println("waitForStack[" + stackName + "]: " + event.getTimestamp().toString() + " " + event.getLogicalResourceId() + " " + event.getResourceStatus() + " " + event.getResourceStatusReason()); + eventsDisplayed.add(event); + } + } + try { + final DescribeStacksResult res = this.cf.describeStacks(new DescribeStacksRequest().withStackName(stackName)); + final StackStatus currentStatus = StackStatus.fromValue(res.getStacks().get(0).getStackStatus()); + if (finalStackStatus.finalStatus == currentStatus) { + System.out.println("waitForStack[" + stackName + "]: final status reached."); + return; + } else { + if (finalStackStatus.intermediateStatus.contains(currentStatus)) { + System.out.println("waitForStack[" + stackName + "]: continue to wait (still in intermediate status " + currentStatus + ") ..."); + } else { + throw new RuntimeException("waitForStack[" + stackName + "]: reached invalid intermediate status " + currentStatus + "."); + } + } + } catch (final AmazonServiceException e) { + if (e.getErrorMessage().equals("Stack with id " + stackName + " does not exist")) { + if (finalStackStatus.notFoundIsFinalStatus) { + System.out.println("waitForStack[" + stackName + "]: final reached (not found)."); + return; + } else { + if (finalStackStatus.notFoundIsIntermediateStatus) { + System.out.println("waitForStack[" + stackName + "]: continue to wait (stack not found) ..."); + } else { + throw new RuntimeException("waitForStack[" + stackName + "]: stack not found."); + } + } + } else { + throw e; + } + } + } + } + + protected final Map getStackOutputs(final String stackName) { + final DescribeStacksResult res = this.cf.describeStacks(new DescribeStacksRequest().withStackName(stackName)); + final List outputs = res.getStacks().get(0).getOutputs(); + final Map map = new HashMap<>(outputs.size()); + for (final Output output : outputs) { + map.put(output.getOutputKey(), output.getOutputValue()); + } + return map; + } + + protected final String getStackOutputValue(final String stackName, final String outputKey) { + return this.getStackOutputs(stackName).get(outputKey); + } + + protected final void deleteStack(final String stackName) { + this.cf.deleteStack(new DeleteStackRequest().withStackName(stackName)); + if (Config.has(Config.Key.BUCKET)) { + final String bucket = Config.get(Config.Key.BUCKET); + s3.deleteObject(bucket, stackName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/ATest.java b/test/src/test/java/de/widdix/awscftemplates/ATest.java new file mode 100644 index 000000000..bf2655052 --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/ATest.java @@ -0,0 +1,41 @@ +package de.widdix.awscftemplates; + +import com.amazonaws.services.ec2.model.KeyPair; +import com.evanlennick.retry4j.CallExecutor; +import com.evanlennick.retry4j.CallResults; +import com.evanlennick.retry4j.RetryConfig; +import com.evanlennick.retry4j.RetryConfigBuilder; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.Session; +import org.junit.Assert; + +import java.time.temporal.ChronoUnit; +import java.util.concurrent.Callable; + +public abstract class ATest { + + protected final T retry(Callable callable) { + final RetryConfig config = new RetryConfigBuilder() + .retryOnAnyException() + .withMaxNumberOfTries(30) + .withDelayBetweenTries(10, ChronoUnit.SECONDS) + .withFixedBackoff() + .build(); + final CallResults results = new CallExecutor(config).execute(callable); + return (T) results.getResult(); + } + + protected final void probeSSH(final String host, final KeyPair key) { + final Callable callable = () -> { + final JSch jsch = new JSch(); + final Session session = jsch.getSession("ec2-user", host); + jsch.addIdentity(key.getKeyName(), key.getKeyMaterial().getBytes(), null, null); + jsch.setConfig("StrictHostKeyChecking", "no"); // for testing this should be fine. adding the host key seems to be only possible via a file which is not very useful here + session.connect(10000); + session.disconnect(); + return true; + }; + Assert.assertTrue(this.retry(callable)); + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/Config.java b/test/src/test/java/de/widdix/awscftemplates/Config.java new file mode 100644 index 000000000..c0061fb8a --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/Config.java @@ -0,0 +1,54 @@ +package de.widdix.awscftemplates; + +public final class Config { + + public enum Key { + TEMPLATE_DIR("TEMPLATE_DIR"), + BUCKET("BUCKET"), + IAM_ROLE_ARN("IAM_ROLE_ARN"), + HOSTED_ZONE_ID("HOSTED_ZONE_ID"), + DOMAIN_SUFFIX("DOMAIN_SUFFIX"), + ACM_CERTIFICATE_ARN("ACM_CERTIFICATE_ARN"), + CLOUDFRONT_ACM_CERTIFICATE_ARN("CLOUDFRONT_ACM_CERTIFICATE_ARN"); + + private final String name; + private final String defaultValue; + + Key(String name, String defaultValue) { + this.name = name; + this.defaultValue = defaultValue; + } + + Key(String name) { + this.name = name; + this.defaultValue = null; + } + } + + public static String get(final Key key) { + final String env = System.getenv(key.name); + if (env == null) { + if (key.defaultValue == null) { + throw new RuntimeException("config not found: " + key.name); + } else { + return key.defaultValue; + } + } else { + return env; + } + } + + public static boolean has(final Key key) { + final String env = System.getenv(key.name); + if (env == null) { + if (key.defaultValue == null) { + return false; + } else { + return true; + } + } else { + return true; + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/ec2/TestEC2AutoRecovery.java b/test/src/test/java/de/widdix/awscftemplates/ec2/TestEC2AutoRecovery.java new file mode 100644 index 000000000..4fcd63b36 --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/ec2/TestEC2AutoRecovery.java @@ -0,0 +1,46 @@ +package de.widdix.awscftemplates.ec2; + +import com.amazonaws.services.cloudformation.model.Parameter; +import com.amazonaws.services.ec2.model.KeyPair; +import de.widdix.awscftemplates.ACloudFormationTest; +import org.junit.Test; + +public class TestEC2AutoRecovery extends ACloudFormationTest { + + @Test + public void test() { + final String vpcStackName = "vpc-2azs-" + this.random8String(); + final String stackName = "ec2-auto-recovery-" + this.random8String(); + final String classB = "10"; + final String keyName = "key-" + this.random8String(); + try { + final KeyPair key = this.createKey(keyName); + try { + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); + this.waitForStack(vpcStackName, FinalStatus.CREATE_COMPLETE); + try { + this.createStack(stackName, + "ec2/ec2-auto-recovery.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), + new Parameter().withParameterKey("KeyName").withParameterValue(keyName) + ); + this.waitForStack(stackName, FinalStatus.CREATE_COMPLETE); + final String host = this.getStackOutputValue(stackName, "IPAddress"); + this.probeSSH(host, key); + } finally { + this.deleteStack(stackName); + this.waitForStack(stackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(vpcStackName); + this.waitForStack(vpcStackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteKey(keyName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/ecs/TestECSCluster.java b/test/src/test/java/de/widdix/awscftemplates/ecs/TestECSCluster.java new file mode 100644 index 000000000..0c9febe5f --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/ecs/TestECSCluster.java @@ -0,0 +1,77 @@ +package de.widdix.awscftemplates.ecs; + +import com.amazonaws.services.cloudformation.model.Parameter; +import com.amazonaws.services.ecs.AmazonECS; +import com.amazonaws.services.ecs.AmazonECSClientBuilder; +import com.amazonaws.services.ecs.model.DescribeContainerInstancesRequest; +import com.amazonaws.services.ecs.model.DescribeContainerInstancesResult; +import com.amazonaws.services.ecs.model.ListContainerInstancesRequest; +import com.amazonaws.services.ecs.model.ListContainerInstancesResult; +import de.widdix.awscftemplates.ACloudFormationTest; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.Callable; + +public class TestECSCluster extends ACloudFormationTest { + + private final AmazonECS ecs = AmazonECSClientBuilder.standard().withCredentials(this.credentialsProvider).build(); + + @Test + public void test() { + final String vpcStackName = "vpc-2azs-" + this.random8String(); + final String stackName = "ecs-cluster-" + this.random8String(); + final String classB = "10"; + final String keyName = "key-" + this.random8String(); + try { + this.createKey(keyName); + try { + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); + this.waitForStack(vpcStackName, FinalStatus.CREATE_COMPLETE); + try { + this.createStack(stackName, + "ecs/cluster.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), + new Parameter().withParameterKey("KeyName").withParameterValue(keyName) + ); + this.waitForStack(stackName, FinalStatus.CREATE_COMPLETE); + final String cluster = this.getStackOutputValue(stackName, "Cluster"); + final Callable callable = () -> { + final ListContainerInstancesResult res1 = this.ecs.listContainerInstances(new ListContainerInstancesRequest().withCluster(cluster)); + final DescribeContainerInstancesResult res2 = this.ecs.describeContainerInstances(new DescribeContainerInstancesRequest().withCluster(cluster).withContainerInstances(res1.getContainerInstanceArns())); + // check container instances + if (res2.getContainerInstances().size() != 2) { + throw new RuntimeException("2 instances expected, but saw " + res2.getContainerInstances().size()); + } + if (!res2.getContainerInstances().get(0).getStatus().equals("ACTIVE")) { + throw new RuntimeException("container 0 status expected ACTIVE, but saw " + res2.getContainerInstances().get(0).getStatus()); + } + if (!res2.getContainerInstances().get(0).getAgentConnected()) { + throw new RuntimeException("container 0 agent expected connected, but saw " + res2.getContainerInstances().get(0).getAgentConnected()); + } + if (!res2.getContainerInstances().get(1).getStatus().equals("ACTIVE")) { + throw new RuntimeException("container 1 status expected ACTIVE, but saw " + res2.getContainerInstances().get(1).getStatus()); + } + if (!res2.getContainerInstances().get(1).getAgentConnected()) { + throw new RuntimeException("container 1 agent expected connected, but saw " + res2.getContainerInstances().get(1).getAgentConnected()); + } + return true; + }; + Assert.assertTrue(this.retry(callable)); + } finally { + this.deleteStack(stackName); + this.waitForStack(stackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(vpcStackName); + this.waitForStack(vpcStackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteKey(keyName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/ecs/TestECSService.java b/test/src/test/java/de/widdix/awscftemplates/ecs/TestECSService.java new file mode 100644 index 000000000..19be53b6d --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/ecs/TestECSService.java @@ -0,0 +1,157 @@ +package de.widdix.awscftemplates.ecs; + +import com.amazonaws.services.cloudformation.model.Parameter; +import com.amazonaws.services.ecs.AmazonECS; +import com.amazonaws.services.ecs.AmazonECSClientBuilder; +import com.amazonaws.services.ecs.model.*; +import de.taimos.httputils.WS; +import de.widdix.awscftemplates.ACloudFormationTest; +import org.apache.http.HttpResponse; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.Callable; + +public class TestECSService extends ACloudFormationTest { + + private final AmazonECS ecs = AmazonECSClientBuilder.standard().withCredentials(this.credentialsProvider).build(); + + private String createTaskDefinition(final String family) { + final PortMapping pm = new PortMapping().withContainerPort(80).withProtocol("tcp"); + final ContainerDefinition cd = new ContainerDefinition().withName("main").withImage("nginx:1.11.5").withMemory(128).withPortMappings(pm).withEssential(true); + final RegisterTaskDefinitionResult res = this.ecs.registerTaskDefinition(new RegisterTaskDefinitionRequest().withFamily(family).withNetworkMode("bridge").withContainerDefinitions(cd)); + return res.getTaskDefinition().getTaskDefinitionArn(); + } + + private void deleteTaskDefinition(final String taskDefinitionArn) { + this.ecs.deregisterTaskDefinition(new DeregisterTaskDefinitionRequest().withTaskDefinition(taskDefinitionArn)); + } + + @Test + public void testClusterAlb() { + final String vpcStackName = "vpc-2azs-" + this.random8String(); + final String clusterStackName = "ecs-cluster-" + this.random8String(); + final String stackName = "ecs-service-" + this.random8String(); + final String classB = "10"; + final String keyName = "key-" + this.random8String(); + final String taskDefinitionArn = this.createTaskDefinition(stackName); + try { + try { + this.createKey(keyName); + try { + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); + this.waitForStack(vpcStackName, ACloudFormationTest.FinalStatus.CREATE_COMPLETE); + try { + this.createStack(clusterStackName, + "ecs/cluster.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), + new Parameter().withParameterKey("KeyName").withParameterValue(keyName) + ); + this.waitForStack(clusterStackName, ACloudFormationTest.FinalStatus.CREATE_COMPLETE); + final String cluster = this.getStackOutputValue(clusterStackName, "Cluster"); + try { + this.createStack(stackName, + "ecs/service-cluster-alb.yaml", + new Parameter().withParameterKey("ParentClusterStack").withParameterValue(clusterStackName), + new Parameter().withParameterKey("TaskDefinitionArn").withParameterValue(taskDefinitionArn) + ); + this.waitForStack(stackName, ACloudFormationTest.FinalStatus.CREATE_COMPLETE); + final String url = this.getStackOutputValue(stackName, "URL"); + final Callable callable = () -> { + final HttpResponse response = WS.url(url).timeout(10000).get(); + // check HTTP response code + if (WS.getStatus(response) != 404) { + throw new RuntimeException("404 expected, but saw " + WS.getStatus(response)); + } + return true; + }; + Assert.assertTrue(this.retry(callable)); + } finally { + this.deleteStack(stackName); + this.waitForStack(stackName, ACloudFormationTest.FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(clusterStackName); + this.waitForStack(clusterStackName, ACloudFormationTest.FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(vpcStackName); + this.waitForStack(vpcStackName, ACloudFormationTest.FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteKey(keyName); + } + } finally { + this.deleteTaskDefinition(taskDefinitionArn); + } + } + + @Test + public void testDedicatedAlb() { + final String vpcStackName = "vpc-2azs-" + this.random8String(); + final String clusterStackName = "ecs-cluster-" + this.random8String(); + final String stackName = "ecs-service-" + this.random8String(); + final String classB = "10"; + final String keyName = "key-" + this.random8String(); + final String taskDefinitionArn = this.createTaskDefinition(stackName); + try { + try { + this.createKey(keyName); + try { + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); + this.waitForStack(vpcStackName, ACloudFormationTest.FinalStatus.CREATE_COMPLETE); + try { + this.createStack(clusterStackName, + "ecs/cluster.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), + new Parameter().withParameterKey("KeyName").withParameterValue(keyName) + ); + this.waitForStack(clusterStackName, ACloudFormationTest.FinalStatus.CREATE_COMPLETE); + final String cluster = this.getStackOutputValue(clusterStackName, "Cluster"); + try { + this.createStack(stackName, + "ecs/service-dedicated-alb.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), + new Parameter().withParameterKey("ParentClusterStack").withParameterValue(clusterStackName), + new Parameter().withParameterKey("TaskDefinitionArn").withParameterValue(taskDefinitionArn) + ); + this.waitForStack(stackName, ACloudFormationTest.FinalStatus.CREATE_COMPLETE); + final String url = this.getStackOutputValue(stackName, "URL"); + final Callable callable = () -> { + final HttpResponse response = WS.url(url).timeout(10000).get(); + // check HTTP response code + if (WS.getStatus(response) != 200) { + throw new RuntimeException("200 expected, but saw " + WS.getStatus(response)); + } + return WS.getResponseAsString(response); + }; + final String response = this.retry(callable); + // check if nginx page appears + Assert.assertTrue(response.contains("Welcome to nginx!")); + } finally { + this.deleteStack(stackName); + this.waitForStack(stackName, ACloudFormationTest.FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(clusterStackName); + this.waitForStack(clusterStackName, ACloudFormationTest.FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(vpcStackName); + this.waitForStack(vpcStackName, ACloudFormationTest.FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteKey(keyName); + } + } finally { + this.deleteTaskDefinition(taskDefinitionArn); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/jenkins/TestJenkins.java b/test/src/test/java/de/widdix/awscftemplates/jenkins/TestJenkins.java new file mode 100644 index 000000000..60d6c6437 --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/jenkins/TestJenkins.java @@ -0,0 +1,110 @@ +package de.widdix.awscftemplates.jenkins; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.taimos.httputils.WS; +import de.widdix.awscftemplates.ACloudFormationTest; +import org.apache.http.HttpResponse; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.Callable; + +public class TestJenkins extends ACloudFormationTest { + + @Test + public void testHA() { + final String vpcStackName = "vpc-2azs-" + this.random8String(); + final String stackName = "jenkins-ha-" + this.random8String(); + final String classB = "10"; + final String keyName = "key-" + this.random8String(); + final String masterAdminPassword = this.random8String(); + try { + this.createKey(keyName); + try { + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); + this.waitForStack(vpcStackName, FinalStatus.CREATE_COMPLETE); + try { + this.createStack(stackName, + "jenkins/jenkins2-ha.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), + new Parameter().withParameterKey("KeyName").withParameterValue(keyName), + new Parameter().withParameterKey("MasterAdminPassword").withParameterValue(masterAdminPassword) + ); + this.waitForStack(stackName, FinalStatus.CREATE_COMPLETE); + final String url = this.getStackOutputValue(stackName, "URL"); + final Callable callable = () -> { + final HttpResponse response = WS.url(url).authBasic("admin", masterAdminPassword).timeout(10000).get(); + // check HTTP response code + if (WS.getStatus(response) != 200) { + throw new RuntimeException("200 expected, but saw " + WS.getStatus(response)); + } + return WS.getResponseAsString(response); + }; + final String response = this.retry(callable); + // check if Jenkins appears in HTML + Assert.assertTrue(response.contains("Jenkins")); + } finally { + this.deleteStack(stackName); + this.waitForStack(stackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(vpcStackName); + this.waitForStack(vpcStackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteKey(keyName); + } + } + + @Test + public void testHAAgents() { + final String vpcStackName = "vpc-2azs-" + this.random8String(); + final String stackName = "jenkins-ha-agents-" + this.random8String(); + final String classB = "10"; + final String keyName = "key-" + this.random8String(); + final String masterAdminPassword = this.random8String(); + try { + this.createKey(keyName); + try { + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); + this.waitForStack(vpcStackName, FinalStatus.CREATE_COMPLETE); + try { + this.createStack(stackName, + "jenkins/jenkins2-ha-agents.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), + new Parameter().withParameterKey("KeyName").withParameterValue(keyName), + new Parameter().withParameterKey("MasterAdminPassword").withParameterValue(masterAdminPassword) + ); + this.waitForStack(stackName, FinalStatus.CREATE_COMPLETE); + final String url = this.getStackOutputValue(stackName, "URL"); + final Callable callable = () -> { + final HttpResponse response = WS.url(url).authBasic("admin", masterAdminPassword).timeout(10000).get(); + // check HTTP response code + if (WS.getStatus(response) != 200) { + throw new RuntimeException("200 expected, but saw " + WS.getStatus(response)); + } + return WS.getResponseAsString(response); + }; + final String response = this.retry(callable); + // check if Jenkins appears in HTML + Assert.assertTrue(response.contains("Jenkins")); + } finally { + this.deleteStack(stackName); + this.waitForStack(stackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(vpcStackName); + this.waitForStack(vpcStackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteKey(keyName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/security/TestSecurityAuthProxy.java b/test/src/test/java/de/widdix/awscftemplates/security/TestSecurityAuthProxy.java new file mode 100644 index 000000000..e9d93e992 --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/security/TestSecurityAuthProxy.java @@ -0,0 +1,72 @@ +package de.widdix.awscftemplates.security; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.taimos.httputils.WS; +import de.widdix.awscftemplates.ACloudFormationTest; +import de.widdix.awscftemplates.Config; +import org.apache.http.HttpResponse; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.Callable; + +public class TestSecurityAuthProxy extends ACloudFormationTest { + + @Test + public void testHAGitHubOrga() { + final String vpcStackName = "vpc-2azs-" + this.random8String(); + final String proxyStackName = "auth-proxy-ha-github-orga-" + this.random8String(); + final String classB = "10"; + final String keyName = "key-" + this.random8String(); + try { + this.createKey(keyName); + try { + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); + this.waitForStack(vpcStackName, FinalStatus.CREATE_COMPLETE); + try { + this.createStack(proxyStackName, + "security/auth-proxy-ha-github-orga.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), + new Parameter().withParameterKey("CertificateArn").withParameterValue(Config.get(Config.Key.ACM_CERTIFICATE_ARN)), + new Parameter().withParameterKey("KeyName").withParameterValue(keyName), + new Parameter().withParameterKey("GitHubOrganization").withParameterValue("widdix"), // fake value + new Parameter().withParameterKey("GitHubClientId").withParameterValue("2bb8ab97cb147fa499f6"), // fake value + new Parameter().withParameterKey("GitHubClientSecret").withParameterValue("d3a1a8a9b6525fb0599d22fc750f17ef76032c62"), // fake value + new Parameter().withParameterKey("Upstream").withParameterValue("https://widdix.net/"), + new Parameter().withParameterKey("CookieSecret").withParameterValue("ylLjZRVNRlzW7sqyQeERBQ==") // fake value + ); + this.waitForStack(proxyStackName, FinalStatus.CREATE_COMPLETE); + try { + final String domain = this.createDomain(proxyStackName, this.getStackOutputValue(proxyStackName, "DNSName")); + final String url = "https://" + domain; + final Callable callable = () -> { + final HttpResponse response = WS.url(url).timeout(10000).get(); + // check HTTP response code + if (WS.getStatus(response) != 403) { + throw new RuntimeException("403 expected, but saw " + WS.getStatus(response)); + } + return WS.getResponseAsString(response); + }; + final String response = this.retry(callable); + // check if OAuth2 Proxy appears in HTML + Assert.assertTrue(response.contains("OAuth2 Proxy")); + } finally { + this.deleteDomain(proxyStackName); + } + } finally { + this.deleteStack(proxyStackName); + this.waitForStack(proxyStackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(vpcStackName); + this.waitForStack(vpcStackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteKey(keyName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/staticwebsite/TestStaticWebsite.java b/test/src/test/java/de/widdix/awscftemplates/staticwebsite/TestStaticWebsite.java new file mode 100644 index 000000000..ec46ed9fe --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/staticwebsite/TestStaticWebsite.java @@ -0,0 +1,46 @@ +package de.widdix.awscftemplates.staticwebsite; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.taimos.httputils.WS; +import de.widdix.awscftemplates.ACloudFormationTest; +import de.widdix.awscftemplates.Config; +import org.apache.http.HttpResponse; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.Callable; + +public class TestStaticWebsite extends ACloudFormationTest { + + @Test + public void test() { + final String stackName = "static-website-" + this.random8String(); + final String domainName = this.generateDomain(stackName); + final String redirectDomainName = this.generateDomain("www-" + stackName); + try { + this.createStack(stackName, + "static-website/static-website.yaml", + new Parameter().withParameterKey("DomainName").withParameterValue(domainName), + new Parameter().withParameterKey("RedirectDomainName").withParameterValue(redirectDomainName), + new Parameter().withParameterKey("CertificateType").withParameterValue("AcmCertificateArn"), + new Parameter().withParameterKey("ExistingCertificate").withParameterValue(Config.get(Config.Key.CLOUDFRONT_ACM_CERTIFICATE_ARN)), + new Parameter().withParameterKey("HostedZoneId").withParameterValue(Config.get(Config.Key.HOSTED_ZONE_ID)) + ); + this.waitForStack(stackName, FinalStatus.CREATE_COMPLETE); + final String url = "https://" + domainName; + final Callable callable = () -> { + final HttpResponse response = WS.url(url).timeout(10000).get(); + // check HTTP response code + if (WS.getStatus(response) != 403) { + throw new RuntimeException("403 expected, but saw " + WS.getStatus(response)); + } + return response; + }; + this.retry(callable); + } finally { + this.deleteStack(stackName); + this.waitForStack(stackName, FinalStatus.DELETE_COMPLETE); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPC2AZs.java b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPC2AZs.java new file mode 100644 index 000000000..f505fd37f --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPC2AZs.java @@ -0,0 +1,25 @@ +package de.widdix.awscftemplates.vpc; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awscftemplates.ACloudFormationTest; +import org.junit.Test; + +public class TestVPC2AZs extends ACloudFormationTest { + + @Test + public void test() { + final String stackName = "vpc-2azs-" + this.random8String(); + try { + this.createStack(stackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue("10") + ); + this.waitForStack(stackName, FinalStatus.CREATE_COMPLETE); + // TODO how can we check if this stack works? launch an EC2 instance into a public subnet and open google from the instance? + } finally { + this.deleteStack(stackName); + this.waitForStack(stackName, FinalStatus.DELETE_COMPLETE); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPC3AZs.java b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPC3AZs.java new file mode 100644 index 000000000..67028dd21 --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPC3AZs.java @@ -0,0 +1,25 @@ +package de.widdix.awscftemplates.vpc; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awscftemplates.ACloudFormationTest; +import org.junit.Test; + +public class TestVPC3AZs extends ACloudFormationTest { + + @Test + public void test() { + final String stackName = "vpc-3azs-" + this.random8String(); + try { + this.createStack(stackName, + "vpc/vpc-3azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue("10") + ); + this.waitForStack(stackName, FinalStatus.CREATE_COMPLETE); + // TODO how can we check if this stack works? launch an EC2 instance into a public subnet and open google from the instance? + } finally { + this.deleteStack(stackName); + this.waitForStack(stackName, FinalStatus.DELETE_COMPLETE); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPC4AZs.java b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPC4AZs.java new file mode 100644 index 000000000..818b70274 --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPC4AZs.java @@ -0,0 +1,25 @@ +package de.widdix.awscftemplates.vpc; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awscftemplates.ACloudFormationTest; +import org.junit.Test; + +public class TestVPC4AZs extends ACloudFormationTest { + + @Test + public void test() { + final String stackName = "vpc-4azs-" + this.random8String(); + try { + this.createStack(stackName, + "vpc/vpc-4azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue("10") + ); + this.waitForStack(stackName, FinalStatus.CREATE_COMPLETE); + // TODO how can we check if this stack works? launch an EC2 instance into a public subnet and open google from the instance? + } finally { + this.deleteStack(stackName); + this.waitForStack(stackName, FinalStatus.DELETE_COMPLETE); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCEndpointS3.java b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCEndpointS3.java new file mode 100644 index 000000000..12a407c2c --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCEndpointS3.java @@ -0,0 +1,36 @@ +package de.widdix.awscftemplates.vpc; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awscftemplates.ACloudFormationTest; +import org.junit.Test; + +public class TestVPCEndpointS3 extends ACloudFormationTest { + @Test + public void test() { + final String vpcStackName = "vpc-2azs-" + this.random8String(); + final String endpointStackName = "vpc-endpoint-s3-" + this.random8String(); + final String classB = "10"; + try { + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); + this.waitForStack(vpcStackName, FinalStatus.CREATE_COMPLETE); + try { + this.createStack(endpointStackName, + "vpc/vpc-endpoint-s3.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName) + ); + this.waitForStack(endpointStackName, FinalStatus.CREATE_COMPLETE); + // TODO how can we check if this stack works? launch an EC2 instance into a private subnet and connect to S3 + } finally { + this.deleteStack(endpointStackName); + this.waitForStack(endpointStackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(vpcStackName); + this.waitForStack(vpcStackName, FinalStatus.DELETE_COMPLETE); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCNatGateway.java b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCNatGateway.java new file mode 100644 index 000000000..183483119 --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCNatGateway.java @@ -0,0 +1,37 @@ +package de.widdix.awscftemplates.vpc; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awscftemplates.ACloudFormationTest; +import org.junit.Test; + +public class TestVPCNatGateway extends ACloudFormationTest { + + @Test + public void test() { + final String vpcStackName = "vpc-2azs-" + this.random8String(); + final String natStackName = "vpc-nat-gateway-" + this.random8String(); + final String classB = "10"; + try { + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); + this.waitForStack(vpcStackName, FinalStatus.CREATE_COMPLETE); + try { + this.createStack(natStackName, + "vpc/vpc-nat-gateway.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName) + ); + this.waitForStack(natStackName, FinalStatus.CREATE_COMPLETE); + // TODO how can we check if this stack works? launch an EC2 instance into a private subnet and open google from the instance? + } finally { + this.deleteStack(natStackName); + this.waitForStack(natStackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(vpcStackName); + this.waitForStack(vpcStackName, FinalStatus.DELETE_COMPLETE); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCNatInstance.java b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCNatInstance.java new file mode 100644 index 000000000..c09de9fa0 --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCNatInstance.java @@ -0,0 +1,44 @@ +package de.widdix.awscftemplates.vpc; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awscftemplates.ACloudFormationTest; +import org.junit.Test; + +public class TestVPCNatInstance extends ACloudFormationTest { + + @Test + public void test() { + final String vpcStackName = "vpc-2azs-" + this.random8String(); + final String natStackName = "vpc-nat-instance-" + this.random8String(); + final String classB = "10"; + final String keyName = "key-" + this.random8String(); + try { + this.createKey(keyName); + try { + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); + this.waitForStack(vpcStackName, FinalStatus.CREATE_COMPLETE); + try { + this.createStack(natStackName, + "vpc/vpc-nat-instance.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), + new Parameter().withParameterKey("KeyName").withParameterValue(keyName) + ); + this.waitForStack(natStackName, FinalStatus.CREATE_COMPLETE); + // TODO how can we check if this stack works? launch an EC2 instance into a private subnet and open google from the instance? + } finally { + this.deleteStack(natStackName); + this.waitForStack(natStackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(vpcStackName); + this.waitForStack(vpcStackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteKey(keyName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCSshBastion.java b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCSshBastion.java new file mode 100644 index 000000000..ecbe4fe92 --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/vpc/TestVPCSshBastion.java @@ -0,0 +1,46 @@ +package de.widdix.awscftemplates.vpc; + +import com.amazonaws.services.cloudformation.model.Parameter; +import com.amazonaws.services.ec2.model.KeyPair; +import de.widdix.awscftemplates.ACloudFormationTest; +import org.junit.Test; + +public class TestVPCSshBastion extends ACloudFormationTest { + + @Test + public void test() { + final String vpcStackName = "vpc-2azs-" + this.random8String(); + final String bastionStackName = "vpc-ssh-bastion-" + this.random8String(); + final String classB = "10"; + final String keyName = "key-" + this.random8String(); + try { + final KeyPair key = this.createKey(keyName); + try { + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); + this.waitForStack(vpcStackName, FinalStatus.CREATE_COMPLETE); + try { + this.createStack(bastionStackName, + "vpc/vpc-ssh-bastion.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), + new Parameter().withParameterKey("KeyName").withParameterValue(keyName) + ); + this.waitForStack(bastionStackName, FinalStatus.CREATE_COMPLETE); + final String host = this.getStackOutputValue(bastionStackName, "IPAddress"); + this.probeSSH(host, key); + } finally { + this.deleteStack(bastionStackName); + this.waitForStack(bastionStackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(vpcStackName); + this.waitForStack(vpcStackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteKey(keyName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awscftemplates/wordpress/TestWordpressHA.java b/test/src/test/java/de/widdix/awscftemplates/wordpress/TestWordpressHA.java new file mode 100644 index 000000000..974c132ca --- /dev/null +++ b/test/src/test/java/de/widdix/awscftemplates/wordpress/TestWordpressHA.java @@ -0,0 +1,76 @@ +package de.widdix.awscftemplates.wordpress; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.taimos.httputils.WS; +import de.widdix.awscftemplates.ACloudFormationTest; +import de.widdix.awscftemplates.Config; +import org.apache.http.HttpResponse; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.Callable; + +public class TestWordpressHA extends ACloudFormationTest { + + @Test + public void test() { + final String vpcStackName = "vpc-2azs-" + this.random8String(); + final String stackName = "wordpress-ha-" + this.random8String(); + final String classB = "10"; + final String keyName = "key-" + this.random8String(); + final String domainName = this.generateDomain(stackName); + final String blogTitle = "Stay-AWSome"; + final String blogPassword = this.random8String(); + try { + this.createKey(keyName); + try { + this.createStack(vpcStackName, + "vpc/vpc-2azs.yaml", + new Parameter().withParameterKey("ClassB").withParameterValue(classB) + ); + this.waitForStack(vpcStackName, FinalStatus.CREATE_COMPLETE); + try { + this.createStack(stackName, + "wordpress/wordpress-ha.yaml", + new Parameter().withParameterKey("ParentVPCStack").withParameterValue(vpcStackName), + new Parameter().withParameterKey("WebServerKeyName").withParameterValue(keyName), + new Parameter().withParameterKey("DomainName").withParameterValue(domainName), + new Parameter().withParameterKey("CloudFrontAcmCertificate").withParameterValue(Config.get(Config.Key.CLOUDFRONT_ACM_CERTIFICATE_ARN)), + new Parameter().withParameterKey("ElbAcmCertificate").withParameterValue(Config.get(Config.Key.ACM_CERTIFICATE_ARN)), + new Parameter().withParameterKey("BlogTitle").withParameterValue(blogTitle), + new Parameter().withParameterKey("BlogAdminUsername").withParameterValue("admin"), + new Parameter().withParameterKey("BlogAdminPassword").withParameterValue(blogPassword), + new Parameter().withParameterKey("BlogAdminEMail").withParameterValue("wordpress@localhost") + ); + this.waitForStack(stackName, FinalStatus.CREATE_COMPLETE); + try { + final String domain = this.createDomain(stackName, this.getStackOutputValue(stackName, "CloudFrontDomainName")); + final String url = "https://" + domain; + final Callable callable = () -> { + final HttpResponse response = WS.url(url).timeout(10000).get(); + // check HTTP response code + if (WS.getStatus(response) != 200) { + throw new RuntimeException("200 expected, but saw " + WS.getStatus(response)); + } + return WS.getResponseAsString(response); + }; + final String response = this.retry(callable); + // check if blog title appears in HTML + Assert.assertTrue(response.contains(blogTitle)); + } finally { + this.deleteDomain(stackName); + } + } finally { + this.deleteStack(stackName); + this.waitForStack(stackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteStack(vpcStackName); + this.waitForStack(vpcStackName, FinalStatus.DELETE_COMPLETE); + } + } finally { + this.deleteKey(keyName); + } + } + +}