Skip to content

Commit 5054eef

Browse files
author
Elad Ben-Israel
authored
feat(core): resource overrides (escape hatch) (#784)
Adds capabilities and documentation to allow users to add overrides to synthesized resource definitions in case of gaps in L2s. - resource.addOverride(path, value) - resource.addPropertyOverride(propertyPath, value) - resource.addDeletionOverride(p, v) - resource.addPropertyDeletionOVerride(pp, v) - xxxResource.propertyOverrides (of type XxxResourceProps) Documented under the AWS Construct Library topic. Fixes #606
1 parent 9d00e36 commit 5054eef

File tree

7 files changed

+783
-16
lines changed

7 files changed

+783
-16
lines changed

docs/src/aws-construct-lib.rst

Lines changed: 270 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,274 @@ were part of your app.
103103

104104
.. _cloudformation_layer:
105105

106-
AWS CloudFormation Layer
107-
========================
106+
Access the AWS CloudFormation Layer
107+
===================================
108+
109+
This topic discusses ways to directly modify the underlying CloudFormation
110+
resources at the AWS Construct Library. We also call this technique an "escape
111+
hatch", as it allows users to "escape" from the abstraction boundary defined by
112+
the AWS Construct and patch the underlying resources.
113+
114+
.. important::
115+
116+
**We do not recommend this method, as it breaks the abstraction layer and
117+
might produce unexpected results**.
118+
119+
Furthermore, the internal implementation of an AWS construct is not part of
120+
the API compatibility guarantees that we can make. This means that updates to
121+
the construct library may break your code without a major version bump.
122+
123+
AWS constructs, such as :py:class:`Topic <@aws-cdk/aws-sns.Topic>`, encapsulate
124+
one or more AWS CloudFormation resources behind their APIs. These resources are
125+
also represented as constructs under the ``cloudformation`` namespace in each
126+
library. For example, the :py:class:`@aws-cdk/aws-s3.Bucket` construct
127+
encapsulates the :py:class:`@aws-cdk/aws-s3.cloudformation.BucketResource`. When
128+
a stack that includes an AWS construct is synthesized, the CloudFormation
129+
definition of the underlying resources are included in the resulting template.
130+
131+
Eventually, the APIs provided by AWS constructs are expected to support all the
132+
services and capabilities offered by AWS, but we are aware that the library
133+
still has many gaps both at the service level (some services don't have any
134+
constructs yet) and at the resource level (an AWS construct exists, but some
135+
features are missing).
136+
137+
.. note::
138+
139+
If you encounter a missing capability in the AWS Construct Library, whether
140+
it is an entire library, a specific resource or a feature,
141+
`raise an issue <https://github.com/awslabs/aws-cdk/issues/new>`_ on GitHub,
142+
and letting us know.
143+
144+
This topic covers the following use cases:
145+
146+
- How to access the low-level CloudFormation resources encapsulated by an AWS construct
147+
- How to specify resource options such as metadata, dependencies on resources
148+
- How to add overrides to a CloudFormation resource and property definitions
149+
- How to directly define low-level CloudFormation resources without an AWS construct
150+
151+
You can also find more information on how to work directly with the AWS
152+
CloudFormation layer under :py:doc:`cloudformation`.
153+
154+
Accessing Low-level Resources
155+
-----------------------------
156+
157+
You can use :py:meth:`construct.findChild(id) <@aws-cdk/cdk.Construct.findChild>`
158+
to access any child of this construct by its construct ID. By convention, the "main"
159+
resource of any AWS Construct is called ``"Resource"``.
160+
161+
The following example shows how to access the underlying S3 bucket resource
162+
given an :py:class:`s3.Bucket <@aws-cdk/s3.Bucket>` construct:
163+
164+
.. code-block:: ts
165+
166+
// let's create an AWS bucket construct
167+
const bucket = new s3.Bucket(this, 'MyBucket');
168+
169+
// we use our knowledge that the main construct is called "Resource" and
170+
// that it's actual type is s3.cloudformation.BucketResource; const
171+
const bucketResource = bucket.findResource('Resource') as s3.cloudformation.BucketResource;
172+
173+
At this point, ``bucketResource`` represents the low-level CloudFormation resource of type
174+
:py:class:`s3.cloudformation.BucketResource <@aws-cdk/aws-s3.cloudformation.BucketResource>`
175+
encapsulated by our bucket.
176+
177+
:py:meth:`construct.findChild(id) <@aws-cdk/cdk.Construct.findChild>` will fail
178+
if the child could not be located, which means that if the underlying |l2| changes
179+
the IDs or structure for some reason, synthesis fails.
180+
181+
It is also possible to use :py:meth:`construct.children <@aws-cdk/cdk.Construct.children>` for more
182+
advanced queries. For example, we can look for a child that has a certain CloudFormation resource
183+
type:
184+
185+
.. code-block:: ts
186+
187+
const bucketResource =
188+
bucket.children.find(c => (c as cdk.Resource).resourceType === 'AWS::S3::Bucket')
189+
as s3.cloudformation.BucketResource;
190+
191+
From that point, users are interacting with CloudFormation resource classes
192+
(which extend :py:class:`cdk.Resource <@aws-cdk/cdk.Resource>`), so we will look
193+
into how to use their APIs in order to modify the behavior of the AWS construct
194+
at hand.
195+
196+
Resource Options
197+
----------------
198+
199+
:py:class:`cdk.Resource <@aws-cdk/cdk.Resource>` has a few facilities for
200+
setting resource options such as ``Metadata``, ``DependsOn``, etc.
201+
202+
For example, this code:
203+
204+
.. code-block:: ts
205+
206+
const bucketResource = bucket.findChild('Resource') as s3.cloudformation.BucketResource;
207+
208+
bucketResource.options.metadata = { MetadataKey: 'MetadataValue' };
209+
bucketResource.options.updatePolicy = {
210+
autoScalingRollingUpdate: {
211+
pauseTime: '390'
212+
}
213+
};
214+
215+
bucketResource.addDependency(otherBucket.findChild('Resource') as cdk.Resource);
216+
217+
Synthesizes the following template:
218+
219+
.. code-block:: json
220+
221+
{
222+
"Type": "AWS::S3::Bucket",
223+
"DependsOn": [ "Other34654A52" ],
224+
"UpdatePolicy": {
225+
"AutoScalingRollingUpdate": {
226+
"PauseTime": "390"
227+
}
228+
},
229+
"Metadata": {
230+
"MetadataKey": "MetadataValue"
231+
}
232+
}
233+
234+
Overriding Resource Properties
235+
------------------------------
236+
237+
Each low-level resource in the CDK has a strongly-typed property called
238+
``propertyOverrides``. It allows users to apply overrides that adhere to the
239+
CloudFormation schema of the resource, and use code-completion and
240+
type-checking.
241+
242+
You will normally use this mechanism when a certain feature is available at the
243+
CloudFormation layer but is not exposed by the AWS Construct.
244+
245+
The following example sets a bucket's analytics configuration:
246+
247+
.. code-block:: ts
248+
249+
bucketResource.propertyOverrides.analyticsConfigurations = [
250+
{
251+
id: 'config1',
252+
storageClassAnalysis: {
253+
dataExport: {
254+
outputSchemaVersion: '1',
255+
destination: {
256+
format: 'html',
257+
bucketArn: otherBucket.bucketArn // use tokens freely
258+
}
259+
}
260+
}
261+
}
262+
];
263+
264+
Raw Overrides
265+
-------------
266+
267+
In cases the strongly-typed overrides are not sufficient, or, for example, if
268+
the schema defined in CloudFormation is not up-to-date, the method
269+
:py:meth:`cdk.Resource.addOverride(path, value) <@aws-cdk/cdk.Resource.addOverride>`
270+
can be used to define an override that will by applied to the resource
271+
definition during synthesis.
272+
273+
For example:
274+
275+
.. code-block:: ts
276+
277+
// define an override at the resource definition root, you can even modify the "Type"
278+
// of the resource if needed.
279+
bucketResource.addOverride('Type', 'AWS::S3::SpecialBucket');
280+
281+
// define an override for a property (both are equivalent operations):
282+
bucketResource.addPropertyOverride('VersioningConfiguration.Status', 'NewStatus');
283+
bucketResource.addOverride('Properties.VersioningConfiguration.Status', 'NewStatus');
284+
285+
// use dot-notation to define overrides in complex structures which will be merged
286+
// with the values set by the higher-level construct
287+
bucketResource.addPropertyOverride('LoggingConfiguration.DestinationBucketName', otherBucket.bucketName);
288+
289+
// it is also possible to assign a `null` value
290+
bucketResource.addPropertyOverride('Foo.Bar', null);
291+
292+
Synthesizes to:
293+
294+
.. code-block:: json
295+
296+
{
297+
"Type": "AWS::S3::SpecialBucket",
298+
"Properties": {
299+
"Foo": {
300+
"Bar": null
301+
},
302+
"VersioningConfiguration": {
303+
"Status": "NewStatus"
304+
},
305+
"LoggingConfiguration": {
306+
"DestinationBucketName": {
307+
"Ref": "Other34654A52"
308+
}
309+
}
310+
}
311+
}
312+
313+
Use ``undefined``, :py:meth:`cdk.Resource.addDeletionOverride <@aws-cdk/cdk.Resource.addDeletionOverride>`
314+
or :py:meth:`cdk.Resource.addPropertyDeletionOverride <@aws-cdk/cdk.Resource.addPropertyDeletionOverride>`
315+
to delete values:
316+
317+
.. code-block:: ts
318+
319+
const bucket = new s3.Bucket(this, 'MyBucket', {
320+
versioned: true,
321+
encryption: s3.BucketEncryption.KmsManaged
322+
});
323+
324+
const bucketResource = bucket.findChild('Resource') as s3.cloudformation.BucketResource;
325+
bucketResource.addPropertyOverride('BucketEncryption.ServerSideEncryptionConfiguration.0.EncryptEverythingAndAlways', true);
326+
bucketResource.addPropertyDeletionOverride('BucketEncryption.ServerSideEncryptionConfiguration.0.ServerSideEncryptionByDefault');
327+
328+
Synthesizes to:
329+
330+
.. code-block:: json
331+
332+
"MyBucketF68F3FF0": {
333+
"Type": "AWS::S3::Bucket",
334+
"Properties": {
335+
"BucketEncryption": {
336+
"ServerSideEncryptionConfiguration": [
337+
{
338+
"EncryptEverythingAndAlways": true
339+
}
340+
]
341+
},
342+
"VersioningConfiguration": {
343+
"Status": "Enabled"
344+
}
345+
}
346+
}
347+
348+
Directly Defining CloudFormation Resources
349+
-------------------------------------------
350+
351+
It is also possible to explicitly define CloudFormation resources in your stack.
352+
To that end, instantiate one of the constructs under the ``cloudformation``
353+
namespace of the dedicated library.
354+
355+
.. code-block:: ts
356+
357+
new s3.cloudformation.BucketResource(this, 'MyBucket', {
358+
analyticsConfigurations: [
359+
// ...
360+
]
361+
});
362+
363+
In the rare case where you want to define a resource that doesn't have a
364+
corresponding ``cloudformation`` class (such as a new resource that was not yet
365+
published in the CloudFormation resource specification), you can instantiate the
366+
:py:class:`cdk.Resource <@aws-cdk/cdk.Resource>` object:
367+
368+
.. code-block:: ts
369+
370+
new cdk.Resource(this, 'MyBucket', {
371+
type: 'AWS::S3::Bucket',
372+
properties: {
373+
AnalyticsConfiguration: [ /* ... */ ] // note the PascalCase here
374+
}
375+
});
108376
109-
Every module in the AWS Construct Library includes a ``cloudformation`` namespace which contains
110-
low-level constructs which represent the low-level AWS CloudFormation semantics of this service.
111-
See :py:doc:`cloudformation` for details.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"app": "node index",
3+
"context": {
4+
"availability-zones:993655754359:us-east-1": [
5+
"us-east-1a",
6+
"us-east-1b",
7+
"us-east-1c",
8+
"us-east-1d",
9+
"us-east-1e",
10+
"us-east-1f"
11+
],
12+
"availability-zones:585695036304:us-east-1": [
13+
"us-east-1a",
14+
"us-east-1b",
15+
"us-east-1c",
16+
"us-east-1d",
17+
"us-east-1e",
18+
"us-east-1f"
19+
],
20+
"ssm:585695036304:us-east-1:/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2": "ami-0ff8a91507f77f867"
21+
}
22+
}

0 commit comments

Comments
 (0)