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(core): resource overrides (escape hatch) #784

Merged
merged 8 commits into from
Sep 27, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
275 changes: 270 additions & 5 deletions docs/src/aws-construct-lib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,274 @@ were part of your app.

.. _cloudformation_layer:

AWS CloudFormation Layer
========================
Access the AWS CloudFormation Layer
===================================

This topic discusses ways to directly modify the underlying CloudFormation
resources at the AWS Construct Library. We also call this technique an "escape
hatch", as it allows users to "escape" from the abstraction boundary defined by
the AWS Construct and patch the underlying resources.

.. important::

**We do not recommend this method, as it breaks the abstraction layer and
might produce unexpected results**.

Furthermore, the internal implementation of an AWS construct is not part of
the API compatibility guarantees that we can make. This means that updates to
the construct library may break your code without a major version bump.

AWS constructs, such as :py:class:`Topic <@aws-cdk/aws-sns.Topic>`, encapsulate
one or more AWS CloudFormation resources behind their APIs. These resources are
also represented as constructs under the ``cloudformation`` namespace in each
library. For example, the :py:class:`@aws-cdk/aws-s3.Bucket` construct
encapsulates the :py:class:`@aws-cdk/aws-s3.cloudformation.BucketResource`. When
a stack that includes an AWS construct is synthesized, the CloudFormation
definition of the underlying resources are included in the resulting template.

Eventually, the APIs provided by AWS constructs are expected to support all the
services and capabilities offered by AWS, but we are aware that the library
still has many gaps both at the service level (some services don't have any
constructs yet) and at the resource level (an AWS construct exists, but some
features are missing).

.. note::

If you encounter a missing capability in the AWS Construct Library, whether
it is an entire library, a specific resource or a feature,
`raise an issue <https://github.com/awslabs/aws-cdk/issues/new>`_ on GitHub,
and letting us know.

This topic covers the following use cases:

- How to access the low-level CloudFormation resources encapsulated by an AWS construct
- How to specify resource options such as metadata, dependencies on resources
- How to add overrides to a CloudFormation resource and property definitions
- How to directly define low-level CloudFormation resources without an AWS construct

You can also find more information on how to work directly with the AWS
CloudFormation layer under :py:doc:`cloudformation`.

Accessing Low-level Resources
-----------------------------

You can use :py:meth:`construct.findChild(id) <@aws-cdk/cdk.Construct.findChild>`
to access any child of this construct by its construct ID. By convention, the "main"
resource of any AWS Construct is called ``"Resource"``.

The following example shows how to access the underlying S3 bucket resource
given an :py:class:`s3.Bucket <@aws-cdk/s3.Bucket>` construct:

.. code-block:: ts

// let's create an AWS bucket construct
const bucket = new s3.Bucket(this, 'MyBucket');

// we use our knowledge that the main construct is called "Resource" and
// that it's actual type is s3.cloudformation.BucketResource; const
const bucketResource = bucket.findResource('Resource') as s3.cloudformation.BucketResource;

At this point, ``bucketResource`` represents the low-level CloudFormation resource of type
:py:class:`s3.cloudformation.BucketResource <@aws-cdk/aws-s3.cloudformation.BucketResource>`
encapsulated by our bucket.

:py:meth:`construct.findChild(id) <@aws-cdk/cdk.Construct.findChild>` will fail
if the child could not be located, which means that if the underlying |l2| changes
the IDs or structure for some reason, synthesis fails.

It is also possible to use :py:meth:`construct.children <@aws-cdk/cdk.Construct.children>` for more
eladb marked this conversation as resolved.
Show resolved Hide resolved
advanced queries. For example, we can look for a child that has a certain CloudFormation resource
type:

.. code-block:: ts

const bucketResource =
bucket.children.find(c => (c as cdk.Resource).resourceType === 'AWS::S3::Bucket')
eladb marked this conversation as resolved.
Show resolved Hide resolved
as s3.cloudformation.BucketResource;

From that point, users are interacting with CloudFormation resource classes
(which extend :py:class:`cdk.Resource <@aws-cdk/cdk.Resource>`), so we will look
into how to use their APIs in order to modify the behavior of the AWS construct
at hand.

Resource Options
----------------

:py:class:`cdk.Resource <@aws-cdk/cdk.Resource>` has a few facilities for
setting resource options such as ``Metadata``, ``DependsOn``, etc.

For example, this code:

.. code-block:: ts

const bucketResource = bucket.findChild('Resource') as s3.cloudformation.BucketResource;

bucketResource.options.metadata = { MetadataKey: 'MetadataValue' };
bucketResource.options.updatePolicy = {
autoScalingRollingUpdate: {
pauseTime: '390'
}
};

bucketResource.addDependency(otherBucket.findChild('Resource') as cdk.Resource);
eladb marked this conversation as resolved.
Show resolved Hide resolved

Synthesizes the following template:

.. code-block:: json

{
"Type": "AWS::S3::Bucket",
"DependsOn": [ "Other34654A52" ],
"UpdatePolicy": {
"AutoScalingRollingUpdate": {
"PauseTime": "390"
}
},
"Metadata": {
"MetadataKey": "MetadataValue"
}
}

Overriding Resource Properties
------------------------------

Each low-level resource in the CDK has a strongly-typed property called
``propertyOverrides``. It allows users to apply overrides that adhere to the
CloudFormation schema of the resource, and use code-completion and
type-checking.

You will normally use this mechanism when a certain feature is available at the
CloudFormation layer but is not exposed by the AWS Construct.

The following example sets a bucket's analytics configuration:

.. code-block:: ts

bucketResource.propertyOverrides.analyticsConfigurations = [
{
id: 'config1',
storageClassAnalysis: {
dataExport: {
outputSchemaVersion: '1',
destination: {
format: 'html',
bucketArn: otherBucket.bucketArn // use tokens freely
}
}
}
}
];

Raw Overrides
-------------

In cases the strongly-typed overrides are not sufficient, or, for example, if
the schema defined in CloudFormation is not up-to-date, the method
:py:meth:`cdk.Resource.addOverride(path, value) <@aws-cdk/cdk.Resource.addOverride>`
can be used to define an override that will by applied to the resource
definition during synthesis.

For example:

.. code-block:: ts

// define an override at the resource definition root, you can even modify the "Type"
// of the resource if needed.
bucketResource.addOverride('Type', 'AWS::S3::SpecialBucket');

// define an override for a property (both are equivalent operations):
bucketResource.addPropertyOverride('VersioningConfiguration.Status', 'NewStatus');
bucketResource.addOverride('Properties.VersioningConfiguration.Status', 'NewStatus');

// use dot-notation to define overrides in complex structures which will be merged
// with the values set by the higher-level construct
bucketResource.addPropertyOverride('LoggingConfiguration.DestinationBucketName', otherBucket.bucketName);

// it is also possible to assign a `null` value
bucketResource.addPropertyOverride('Foo.Bar', null);

Synthesizes to:

.. code-block:: json

{
"Type": "AWS::S3::SpecialBucket",
"Properties": {
"Foo": {
"Bar": null
},
"VersioningConfiguration": {
"Status": "NewStatus"
},
"LoggingConfiguration": {
"DestinationBucketName": {
"Ref": "Other34654A52"
}
}
}
}

Use ``undefined``, :py:meth:`cdk.Resource.addDeletionOverride <@aws-cdk/cdk.Resource.addDeletionOverride>`
or :py:meth:`cdk.Resource.addPropertyDeletionOverride <@aws-cdk/cdk.Resource.addPropertyDeletionOverride>`
to delete values:

.. code-block:: ts

const bucket = new s3.Bucket(this, 'MyBucket', {
versioned: true,
encryption: s3.BucketEncryption.KmsManaged
});

const bucketResource = bucket.findChild('Resource') as s3.cloudformation.BucketResource;
bucketResource.addPropertyOverride('BucketEncryption.ServerSideEncryptionConfiguration.0.EncryptEverythingAndAlways', true);
bucketResource.addPropertyDeletionOverride('BucketEncryption.ServerSideEncryptionConfiguration.0.ServerSideEncryptionByDefault');

Synthesizes to:

.. code-block:: json

"MyBucketF68F3FF0": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"EncryptEverythingAndAlways": true
}
]
},
"VersioningConfiguration": {
"Status": "Enabled"
}
}
}

Directly Defining CloudFormation Resources
-------------------------------------------

It is also possible to explicitly define CloudFormation resources in your stack.
To that end, instantiate one of the constructs under the ``cloudformation``
namespace of the dedicated library.

.. code-block:: ts

new s3.cloudformation.BucketResource(this, 'MyBucket', {
analyticsConfigurations: [
// ...
]
});

In the rare case where you want to define a resource that doesn't have a
corresponding ``cloudformation`` class (such as a new resource that was not yet
published in the CloudFormation resource specification), you can instantiate the
:py:class:`cdk.Resource <@aws-cdk/cdk.Resource>` object:

.. code-block:: ts

new cdk.Resource(this, 'MyBucket', {
type: 'AWS::S3::Bucket',
properties: {
AnalyticsConfiguration: [ /* ... */ ] // note the PascalCase here
}
});

Every module in the AWS Construct Library includes a ``cloudformation`` namespace which contains
low-level constructs which represent the low-level AWS CloudFormation semantics of this service.
See :py:doc:`cloudformation` for details.
22 changes: 22 additions & 0 deletions examples/cdk-examples-typescript/resource-overrides/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"app": "node index",
"context": {
"availability-zones:993655754359:us-east-1": [
"us-east-1a",
"us-east-1b",
"us-east-1c",
"us-east-1d",
"us-east-1e",
"us-east-1f"
],
"availability-zones:585695036304:us-east-1": [
"us-east-1a",
"us-east-1b",
"us-east-1c",
"us-east-1d",
"us-east-1e",
"us-east-1f"
],
"ssm:585695036304:us-east-1:/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2": "ami-0ff8a91507f77f867"
}
}
Loading