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(Java): add Java code for resource-overrides example. #167

Merged
merged 6 commits into from
Dec 4, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ $ cdk destroy
|---------|-------------|
| [hello-world](https://github.com/aws-samples/aws-cdk-examples/tree/master/java/hello-world/) | A demo application that uses the CDK in Java |
| [lambda-cron](https://github.com/aws-samples/aws-cdk-examples/tree/master/java/lambda-cron/) | Running a Lambda on a schedule |
| [resource-overrides](https://github.com/aws-samples/aws-cdk-examples/tree/master/java/resource-overrides/) | Use of the resource overrides (aka ["escape hatch"](https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html)) mechanism. |

## Python examples <a name="Python"></a>

Expand Down
57 changes: 57 additions & 0 deletions java/resource-overrides/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# CDK Java Example: Resource Override
<!--BEGIN STABILITY BANNER-->
---

![Stability: REFERENCE](https://img.shields.io/badge/stability-Reference-informational.svg?style=for-the-badge)

> **This is a reference example. It may not build, and exists to demonstrate features*
>
> This example has code elements that will block a successful build, and should be used for reference only.

---
<!--END STABILITY BANNER-->

This example shows the use of the resource overrides (["escape hatch"](https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html)) mechanism.

We add an `AWS::S3::Bucket` resource, and then proceed to change the properties
of the underlying CloudFormation resource.

There are two steps:

* Access the underlying CloudFormation resource by using
`construct.getNode().getDefaultChild()` or `construct.getNode().findChild(childId)`.
* Change the resource by the various `add[Property]Override()` methods,
or assigning to properties or `getCfnOptions()`.

**NOTE** The point is to show how to change various aspects of the generated
CloudFormation template. The end result is a template that cannot be successfully
deployed!

## Building

To build this app, run `mvn compile`. This will download the required
dependencies to compile the Java code.

You can use your IDE to write code and unit tests, but you will need to use the
CDK toolkit if you wish to synthesize/deploy stacks.

## CDK Toolkit

The [`cdk.json`](./cdk.json) file in the root of this repository includes
instructions for the CDK toolkit on how to execute this program.

Specifically, it will tell the toolkit to use the `mvn exec:java` command as the
entry point of your application. After changing your Java code, you will be able
to run the CDK toolkit commands as usual (Maven will recompile as needed):

$ cdk ls
<list all stacks in this program>

$ cdk synth
<outputs cloudformation template>

$ cdk deploy
<deploy stack to your account - trying to deploy this stack will fail, see note above>

$ cdk diff
<diff against deployed stack>
3 changes: 3 additions & 0 deletions java/resource-overrides/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "mvn exec:java -Dexec.mainClass=software.amazon.awscdk.examples.ResourceOverridesApp"
}
106 changes: 106 additions & 0 deletions java/resource-overrides/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>

<groupId>com.amazonaws.cdk</groupId>
<artifactId>resource-overrides</artifactId>
<version>1.0.0</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.amazonaws.cdk.examples.ResourceOverridesApp</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

</plugins>
</build>

<dependencies>
<!-- https://mvnrepository.com/artifact/software.amazon.awscdk/core -->
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>core</artifactId>
<version>1.17.1.DEVPREVIEW</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.awscdk/s3 -->
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>s3</artifactId>
<version>1.17.1.DEVPREVIEW</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.awscdk/ec2 -->
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>ec2</artifactId>
<version>1.17.1.DEVPREVIEW</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.awscdk/autoscaling -->
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>autoscaling</artifactId>
<version>1.17.1.DEVPREVIEW</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.1-jre</version>
</dependency>

<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.skyscreamer/jsonassert -->
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>1.5.0</version>
<scope>test</scope>
</dependency>

</dependencies>


</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package software.amazon.awscdk.examples;

import software.amazon.awscdk.core.App;

public class ResourceOverridesApp {
public static void main(final String[] args) {
App app = new App();

new ResourceOverridesStack(app, "resource-overrides");

app.synth();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package software.amazon.awscdk.examples;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import software.amazon.awscdk.core.*;
import software.amazon.awscdk.services.autoscaling.AutoScalingGroup;
import software.amazon.awscdk.services.autoscaling.AutoScalingGroupProps;
import software.amazon.awscdk.services.autoscaling.CfnLaunchConfiguration;
import software.amazon.awscdk.services.ec2.*;
import software.amazon.awscdk.services.s3.Bucket;
import software.amazon.awscdk.services.s3.BucketEncryption;
import software.amazon.awscdk.services.s3.BucketProps;
import software.amazon.awscdk.services.s3.CfnBucket;

import java.util.Collections;

/**
* This is an example of how to override properties of underlying CloudFormation resource of
* high-level CDK construct.
*
* Note: this is just a reference code to show examples of how to use L1 resources.
* Running `cdk deploy` on this app will fail, however you can still run `cdk synth` and explore
* CloudFormation template that gets generated.
*/
class ResourceOverridesStack extends Stack {
public ResourceOverridesStack(final Construct parent, final String name) {
pimlock marked this conversation as resolved.
Show resolved Hide resolved
super(parent, name);

Bucket otherBucket = new Bucket(this, "Other");
pimlock marked this conversation as resolved.
Show resolved Hide resolved

Bucket bucket = new Bucket(this, "MyBucket", BucketProps.builder()
.versioned(true)
.encryption(BucketEncryption.KMS_MANAGED)
.build()
);
CfnBucket bucketResource = (CfnBucket) bucket.getNode().getDefaultChild();

//
// This is how to access L1 construct
//
accessCfnBucketExample(bucket);

//
// This is how to modify properties of L1 construct
//
modifyPropertiesExample(bucket);

//
// This is how to specify resource options such as dependencies, metadata, update policy
//
bucketResource.getNode().addDependency(otherBucket.getNode().getDefaultChild());
bucketResource.getCfnOptions().setMetadata(
ImmutableMap.of("MetadataKey", "MetadataValue")
);
bucketResource.getCfnOptions().setUpdatePolicy(
CfnUpdatePolicy.builder()
.autoScalingRollingUpdate(CfnAutoScalingRollingUpdate.builder().pauseTime("390").build())
.build()
);

//
// This is how to specify "raw" overrides at the __resource__ level
//
bucketResource.addOverride("Type", "AWS::S3::Bucketeer"); // even "Type" can be overridden
bucketResource.addOverride("Transform", "Boom");
bucketResource.addOverride("Properties.CorsConfiguration",
ImmutableMap.builder()
.put("Custom", 123)
.put("Bar", ImmutableList.of("A", "B"))
.build()
);

// addPropertyOverride simply allows you to omit the "Properties." prefix
bucketResource.addPropertyOverride("VersioningConfiguration.Status", "NewStatus");
bucketResource.addPropertyOverride("Token", otherBucket.getBucketArn());
// it's possible to mix L1 and L2 constructs - in this case otherBucket.getBucketName() will create "Ref:" in CloudFormation template
bucketResource.addPropertyOverride("LoggingConfiguration.DestinationBucketName", otherBucket.getBucketName());

bucketResource.setAnalyticsConfigurations(Collections.singletonList(ImmutableMap.builder()
.put("id", "config1")
.put("storageClassAnalysis", ImmutableMap.of(
"dataExport", ImmutableMap.builder()
.put("outputSchemaVersion", "1")
.put("destination", ImmutableMap.builder()
.put("format", "html")
// using L2 construct's method will work as expected
.put("bucketArn", otherBucket.getBucketArn())
.build()
)
.build()
)
)
.build()
));

//
// It is also possible to request a deletion of a value by either assigning
// `null` or use the `addDeletionOverride` method
//
bucketResource.addDeletionOverride("Metadata");
// same as above
bucketResource.addOverride("Metadata", null);
bucketResource.addPropertyDeletionOverride("CorsConfiguration.Bar");

Vpc vpc = new Vpc(this, "VPC", VpcProps.builder().maxAzs(1).build());
AutoScalingGroup asg = new AutoScalingGroup(this, "ASG", AutoScalingGroupProps.builder()
pimlock marked this conversation as resolved.
Show resolved Hide resolved
.vpc(vpc)
.instanceType(InstanceType.of(InstanceClass.MEMORY4, InstanceSize.XLARGE))
.machineImage(new AmazonLinuxImage())
.build()
);

//
// The default child resource is called `Resource`, but secondary resources, such as
// an LaunchConfig, InstanceRole will have a different ID.
// See https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.ConstructNode.html#defaultchild
// You can see all the resources under given construct by running `cdk synth` and looking for `aws:cdk:path`
//
CfnLaunchConfiguration launchConfiguration = (CfnLaunchConfiguration) asg.getNode().findChild("LaunchConfig");
launchConfiguration.addPropertyOverride("Foo.Bar", "Hello");
}

/**
* Example of accessing L1 bucket resource from L2 bucket construct.
* <p>
* You can read more on L1 vs L2 constructs here: https://aws.amazon.com/blogs/developer/contributing-to-the-aws-cloud-development-kit/
*/
private void accessCfnBucketExample(Bucket bucket) {
// accessing through finding a child of specific type (not pretty in Java)
CfnBucket bucketResource1 = (CfnBucket) bucket.getNode().getChildren()
.stream()
.filter(child -> ((CfnResource) child).getCfnResourceType().equals("AWS::S3::Bucket"))
.findFirst()
.get();

// accessing through getting a default child
CfnBucket bucketResource2 = (CfnBucket) bucket.getNode().getDefaultChild();

assert bucketResource1.equals(bucketResource2);
}

/**
* Example of how properties of CloudFormation resource can be modified.
* Paths for the properties can be found in CloudFormation documentation.
* For S3 bucket properties see: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html
*/
private void modifyPropertiesExample(Bucket bucket) {
CfnBucket bucketCfnResource = (CfnBucket) bucket.getNode().getDefaultChild();

// This is an invalid CF property, but there is no validation at this point, so anything can be set.
// This is just to show that anything can be set at this point, but it's only validated ones the stack
// is being deployed to CloudFormation.
bucketCfnResource.addPropertyOverride("BucketEncryption.ServerSideEncryptionConfiguration.0.EncryptEverythingAndAlways", true);

// This is a valid CF property
bucketCfnResource.addPropertyDeletionOverride("BucketEncryption.ServerSideEncryptionConfiguration.0.ServerSideEncryptionByDefault");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package software.amazon.awscdk.examples;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.skyscreamer.jsonassert.JSONCompareMode;
import software.amazon.awscdk.core.App;
import software.amazon.awscdk.core.ConstructNode;
import software.amazon.awscdk.core.IConstruct;
import software.amazon.awscdk.core.Stack;
import software.amazon.awscdk.cxapi.CloudFormationStackArtifact;

import java.io.IOException;

public class ResourceOverridesStackTest {
private static final ObjectMapper JSON = new ObjectMapper();

@Test
public void shouldGenerateValidCloudFormationTemplate() throws Exception {
App app = new App();
Stack stack = new ResourceOverridesStack(app, "resource-overrides");

String actual = getStackTemplateJson(stack)
.toPrettyString();
String expected = readJsonFromResource("testResourceOverrides.expected.json")
.toPrettyString();

JSONAssert.assertEquals(expected, actual, JSONCompareMode.LENIENT);
}

private static JsonNode readJsonFromResource(String resourceName) throws IOException {
return JSON.readTree(
ResourceOverridesStackTest.class.getResource(resourceName)
);
}

private static JsonNode getStackTemplateJson(Stack stack) {
IConstruct root = stack.getNode().getRoot();
CloudFormationStackArtifact stackArtifact = ConstructNode.synth(root.getNode())
.getStackByName(stack.getStackName());

return JSON.valueToTree(stackArtifact.getTemplate());
}
}
Loading