From 49fa5e03b4e9cf5ac813ce385f6703dc6e6c77c5 Mon Sep 17 00:00:00 2001 From: Peter Mlocek Date: Thu, 21 Nov 2019 21:29:16 -0800 Subject: [PATCH] feat(Java): add Java code for resource-overrides example. --- java/resource-overrides/README.md | 57 +++++++ java/resource-overrides/cdk.json | 3 + java/resource-overrides/pom.xml | 91 ++++++++++ .../awscdk/examples/ResourceOverridesApp.java | 13 ++ .../examples/ResourceOverridesStack.java | 157 ++++++++++++++++++ 5 files changed, 321 insertions(+) create mode 100644 java/resource-overrides/README.md create mode 100644 java/resource-overrides/cdk.json create mode 100644 java/resource-overrides/pom.xml create mode 100644 java/resource-overrides/src/main/java/software/amazon/awscdk/examples/ResourceOverridesApp.java create mode 100644 java/resource-overrides/src/main/java/software/amazon/awscdk/examples/ResourceOverridesStack.java diff --git a/java/resource-overrides/README.md b/java/resource-overrides/README.md new file mode 100644 index 000000000..a7c3328cd --- /dev/null +++ b/java/resource-overrides/README.md @@ -0,0 +1,57 @@ +# CDK Java Example: Resource Override + +--- + +![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. + +--- + + +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 + + + $ cdk synth + + + $ cdk deploy + + + $ cdk diff + diff --git a/java/resource-overrides/cdk.json b/java/resource-overrides/cdk.json new file mode 100644 index 000000000..d366be72d --- /dev/null +++ b/java/resource-overrides/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "mvn exec:java -Dexec.mainClass=software.amazon.awscdk.examples.ResourceOverridesApp" +} diff --git a/java/resource-overrides/pom.xml b/java/resource-overrides/pom.xml new file mode 100644 index 000000000..80704781a --- /dev/null +++ b/java/resource-overrides/pom.xml @@ -0,0 +1,91 @@ + + + + 4.0.0 + + com.amazonaws.cdk + resource-overrides + 1.0.0 + + + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.1.0 + + + jar-with-dependencies + + + + com.amazonaws.cdk.examples.ResourceOverridesApp + + + + + + make-assembly + package + + single + + + + + + + + + + + + software.amazon.awscdk + core + 1.17.1.DEVPREVIEW + + + + software.amazon.awscdk + s3 + 1.17.1.DEVPREVIEW + + + + software.amazon.awscdk + ec2 + 1.17.1.DEVPREVIEW + + + + software.amazon.awscdk + autoscaling + 1.17.1.DEVPREVIEW + + + + + com.google.guava + guava + 28.1-jre + + + + + + diff --git a/java/resource-overrides/src/main/java/software/amazon/awscdk/examples/ResourceOverridesApp.java b/java/resource-overrides/src/main/java/software/amazon/awscdk/examples/ResourceOverridesApp.java new file mode 100644 index 000000000..833f5a5b7 --- /dev/null +++ b/java/resource-overrides/src/main/java/software/amazon/awscdk/examples/ResourceOverridesApp.java @@ -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(); + } +} diff --git a/java/resource-overrides/src/main/java/software/amazon/awscdk/examples/ResourceOverridesStack.java b/java/resource-overrides/src/main/java/software/amazon/awscdk/examples/ResourceOverridesStack.java new file mode 100644 index 000000000..cb61da194 --- /dev/null +++ b/java/resource-overrides/src/main/java/software/amazon/awscdk/examples/ResourceOverridesStack.java @@ -0,0 +1,157 @@ +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) { + super(parent, name); + + Bucket otherBucket = new Bucket(this, "Other"); + + 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() + .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 + // + CfnLaunchConfiguration launchConfiguration = (CfnLaunchConfiguration) asg.getNode().findChild("LaunchConfig"); + launchConfiguration.addPropertyOverride("Foo.Bar", "Hello"); + } + + /** + * Example of accessing L1 bucket resource from L2 bucket construct. + *

+ * 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"); + } +}