-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
README.md
713 lines (572 loc) · 33.9 KB
/
README.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
# AWS CDK Custom Resources
This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.
## Provider Framework
AWS CloudFormation [custom resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html) are extension points to the provisioning
engine. When CloudFormation needs to create, update or delete a custom resource,
it sends a lifecycle event notification to a **custom resource provider**. The provider
handles the event (e.g. creates a resource) and sends back a response to CloudFormation.
The `@aws-cdk/custom-resources.Provider` construct is a "mini-framework" for
implementing providers for AWS CloudFormation custom resources. The framework offers a high-level API which makes it easier to implement robust
and powerful custom resources. If you are looking to implement a custom resource provider, we recommend
you use this module unless you have good reasons not to. For an overview of different provider types you
could be using, see the [Custom Resource Providers section in the core library documentation](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib-readme.html#custom-resource-providers).
> **N.B.**: if you use the provider framework in this module you will write AWS Lambda Functions that look a lot like, but aren't exactly the same as the Lambda Functions you would write if you wrote CloudFormation Custom Resources directly, without this framework.
>
> Specifically, to report success or failure, have your Lambda Function exit in the right way: return data for success, or throw an
> exception for failure. *Do not* post the success or failure of your custom resource to an HTTPS URL as the CloudFormation
> documentation tells you to do.
The framework has the following capabilities:
* Handles responses to AWS CloudFormation and protects against blocked
deployments
* Validates handler return values to help with correct handler implementation
* Supports asynchronous handlers to enable operations that require a long waiting period for a resource, which can exceed the AWS Lambda timeout
* Implements default behavior for physical resource IDs.
The following code shows how the `Provider` construct is used in conjunction
with a `CustomResource` and a user-provided AWS Lambda function which implements
the actual handler.
```ts
declare const onEvent: lambda.Function;
declare const isComplete: lambda.Function;
declare const myRole: iam.Role;
const myProvider = new cr.Provider(this, 'MyProvider', {
onEventHandler: onEvent,
isCompleteHandler: isComplete, // optional async "waiter"
logGroup: new logs.LogGroup(this, 'MyProviderLogs', {
retention: logs.RetentionDays.ONE_DAY,
}),
role: myRole, // must be assumable by the `lambda.amazonaws.com` service principal
});
new CustomResource(this, 'Resource1', { serviceToken: myProvider.serviceToken });
new CustomResource(this, 'Resource2', { serviceToken: myProvider.serviceToken });
```
Providers are implemented through AWS Lambda functions that are triggered by the
provider framework in response to lifecycle events.
At the minimum, users must define the `onEvent` handler, which is invoked by the
framework for all resource lifecycle events (`Create`, `Update` and `Delete`)
and returns a result which is then submitted to CloudFormation.
The following example is a skeleton for a Python implementation of `onEvent`:
```py
def on_event(event, context):
print(event)
request_type = event['RequestType']
if request_type == 'Create': return on_create(event)
if request_type == 'Update': return on_update(event)
if request_type == 'Delete': return on_delete(event)
raise Exception("Invalid request type: %s" % request_type)
def on_create(event):
props = event["ResourceProperties"]
print("create new resource with props %s" % props)
# add your create code here...
physical_id = ...
return { 'PhysicalResourceId': physical_id }
def on_update(event):
physical_id = event["PhysicalResourceId"]
props = event["ResourceProperties"]
print("update resource %s with props %s" % (physical_id, props))
# ...
def on_delete(event):
physical_id = event["PhysicalResourceId"]
print("delete resource %s" % physical_id)
# ...
```
> When writing your handlers, there are a couple of non-obvious corner cases you need to
> pay attention to. See the [important cases to handle](#important-cases-to-handle) section for more information.
Users may also provide an additional handler called `isComplete`, for cases
where the lifecycle operation cannot be completed immediately. The
`isComplete` handler will be retried asynchronously after `onEvent` until it
returns `IsComplete: true`, or until the total provider timeout has expired.
The following example is a skeleton for a Python implementation of `isComplete`:
```py
def is_complete(event, context):
physical_id = event["PhysicalResourceId"]
request_type = event["RequestType"]
# check if resource is stable based on request_type
is_ready = ...
return { 'IsComplete': is_ready }
```
> **Security Note**: the Custom Resource Provider Framework will write the value of `ResponseURL`,
> which is a pre-signed S3 URL used to report the success or failure of the Custom Resource execution
> back to CloudFormation, in a readable form to the AWS Step Functions execution history.
>
> Anybody who can list and read AWS StepFunction executions in your account will be able to write
> a fake response to this URL and make your CloudFormation deployments fail.
>
> Do not use this library if your threat model requires that you cannot trust actors who are able
> to list StepFunction executions in your account.
### Handling Lifecycle Events: onEvent
The user-defined `onEvent` AWS Lambda function is invoked whenever a resource
lifecycle event occurs. The function is expected to handle the event and return
a response to the framework that, at least, includes the physical resource ID.
If `onEvent` returns successfully, the framework will submit a "SUCCESS" response
to AWS CloudFormation for this resource operation. If the provider is
[asynchronous](#asynchronous-providers-iscomplete) (`isCompleteHandler` is
defined), the framework will only submit a response based on the result of
`isComplete`.
If `onEvent` throws an error, the framework will submit a "FAILED" response to
AWS CloudFormation.
The input event includes the following fields derived from the [Custom Resource
Provider Request]:
| Field | Type | Description |
| ----------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `RequestType` | String | The type of lifecycle event: `Create`, `Update` or `Delete`. |
| `LogicalResourceId` | String | The template developer-chosen name (logical ID) of the custom resource in the AWS CloudFormation template. |
| `PhysicalResourceId` | String | This field will only be present for `Update` and `Delete` events and includes the value returned in `PhysicalResourceId` of the previous operation. |
| `ResourceProperties` | JSON | This field contains the properties defined in the template for this custom resource. |
| `OldResourceProperties` | JSON | This field will only be present for `Update` events and contains the resource properties that were declared previous to the update request. |
| `ResourceType` | String | The resource type defined for this custom resource in the template. A provider may handle any number of custom resource types. |
| `RequestId` | String | A unique ID for the request. |
| `StackId` | String | The ARN that identifies the stack that contains the custom resource. |
The return value from `onEvent` must be a JSON object with the following fields:
| Field | Type | Required | Description |
| -------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PhysicalResourceId` | String | No | The allocated/assigned physical ID of the resource. If omitted for `Create` events, the event's `RequestId` will be used. For `Update`, the current physical ID will be used. If a different value is returned, CloudFormation will follow with a subsequent `Delete` for the previous ID (resource replacement). For `Delete`, it will always return the current physical resource ID, and if the user returns a different one, an error will occur. |
| `Data` | JSON | No | Resource attributes, which can later be retrieved through `Fn::GetAtt` on the custom resource object. |
| `NoEcho` | Boolean | No | Whether to mask the output of the custom resource when retrieved by using the `Fn::GetAtt` function. |
| *any* | *any* | No | Any other field included in the response will be passed through to `isComplete`. This can sometimes be useful to pass state between the handlers. |
[Custom Resource Provider Request]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html#crpg-ref-request-fields
### Asynchronous Providers: isComplete
It is not uncommon for the provisioning of resources to be an asynchronous
operation, which means that the operation does not immediately finish, and we
need to "wait" until the resource stabilizes.
The provider framework makes it easy to implement "waiters" by allowing users to
specify an additional AWS Lambda function in `isCompleteHandler`.
The framework will repeatedly invoke the handler every `queryInterval`. When
`isComplete` returns with `IsComplete: true`, the framework will submit a
"SUCCESS" response to AWS CloudFormation. If `totalTimeout` expires and the
operation has not yet completed, the framework will submit a "FAILED" response
with the message "Operation timed out".
If an error is thrown, the framework will submit a "FAILED" response to AWS
CloudFormation.
The input event to `isComplete` includes all request fields, combined with all
fields returned from `onEvent`. If `PhysicalResourceId` has not been explicitly
returned from `onEvent`, it's value will be calculated based on the heuristics
described above.
The return value must be a JSON object with the following fields:
| Field | Type | Required | Description |
| ------------ | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `IsComplete` | Boolean | Yes | Indicates if the operation has finished or not. |
| `Data` | JSON | No | May only be sent if `IsComplete` is `true` and includes additional resource attributes. These attributes will be **merged** with the ones returned from `onEvent` |
### Physical Resource IDs
Every resource in CloudFormation has a physical resource ID. When a resource is
created, the `PhysicalResourceId` returned from the `Create` operation is stored
by AWS CloudFormation and assigned to the logical ID defined for this resource
in the template. If a `Create` operation returns without a `PhysicalResourceId`,
the framework will use `RequestId` as the default. This is sufficient for
various cases such as "pseudo-resources" which only query data.
For `Update` and `Delete` operations, the resource event will always include the
current `PhysicalResourceId` of the resource.
When an `Update` operation occurs, the default behavior is to return the current
physical resource ID. if the `onEvent` returns a `PhysicalResourceId` which is
different from the current one, AWS CloudFormation will treat this as a
**resource replacement**, and it will issue a subsequent `Delete` operation for
the old resource.
As a rule of thumb, if your custom resource supports configuring a physical name
(e.g. you can specify a `BucketName` when you define an `AWS::S3::Bucket`), you
must return this name in `PhysicalResourceId` and make sure to handle
replacement properly. The `S3File` example demonstrates this
through the `objectKey` property.
### When there are errors
As mentioned above, if any of the user handlers fail (i.e. throws an exception)
or times out (due to their AWS Lambda timing out), the framework will trap these
errors and submit a "FAILED" response to AWS CloudFormation, along with the error
message.
Since errors can occur in multiple places in the provider (framework, `onEvent`,
`isComplete`), it is important to know that there could situations where a
resource operation fails even though the operation technically succeeded (i.e.
isComplete throws an error).
When AWS CloudFormation receives a "FAILED" response, it will attempt to roll
back the stack to it's last state. This has different meanings for different
lifecycle events:
* If a `Create` event fails, the resource provider framework will automatically
ignore the subsequent `Delete` operation issued by AWS CloudFormation. The
framework currently does not support customizing this behavior (see
https://github.com/aws/aws-cdk/issues/5524).
* If an `Update` event fails, CloudFormation will issue an additional `Update`
with the previous properties.
* If a `Delete` event fails, CloudFormation will abandon this resource.
### Important cases to handle
You should keep the following list in mind when writing custom resources to
make sure your custom resource behaves correctly in all cases:
* During `Create`:
* If the create fails, the *provider framework* will make sure you
don't get a subsequent `Delete` event. If your create involves multiple distinct
operations, it is your responsibility to catch and rethrow and clean up
any partial updates that have already been performed. Make sure your
API call timeouts and Lambda timeouts allow for this.
* During `Update`:
* If the update fails, you will get a subsequent `Update` event
to roll back to the previous state (with `ResourceProperties` and
`OldResourceProperties` reversed).
* If you return a different `PhysicalResourceId`, you will subsequently
receive a `Delete` event to clean up the previous state of the resource.
* During `Delete`:
* If the behavior of your custom resource is tied to another AWS resource
(for example, it exists to clean the contents of a stateful resource), keep
in mind that your custom resource may be deleted independently of the other
resource and you must confirm that it is appropriate to perform the action.
* (only if you are *not* using the provider framework) a `Delete` event
may be caused by a failed `Create`. You must be able to handle the case
where the resource you are trying to delete hasn't even been created yet.
* If you update the code of your custom resource and change the format of the
resource properties, be aware that there may still be already-deployed
instances of your custom resource out there, and you may still receive
the *old* property format in `ResourceProperties` (during `Delete` and
rollback `Updates`) or in `OldResourceProperties` (during rollforward
`Update`). You must continue to handle all possible sets of properties
your custom resource could have ever been created with in the past.
### Provider Framework Execution Policy
Similarly to any AWS Lambda function, if the user-defined handlers require
access to AWS resources, you will have to define these permissions
by calling "grant" methods such as `myBucket.grantRead(myHandler)`), using `myHandler.addToRolePolicy`
or specifying an `initialPolicy` when defining the function.
Bear in mind that in most cases, a single provider will be used for multiple
resource instances. This means that the execution policy of the provider must
have the appropriate privileges.
The following example grants the `onEvent` handler `s3:GetObject*` permissions
to all buckets:
```ts
new lambda.Function(this, 'OnEventHandler', {
runtime: lambda.Runtime.NODEJS_LATEST,
handler: 'index.handler',
code: lambda.Code.fromInline('my code'),
initialPolicy: [
new iam.PolicyStatement({ actions: [ 's3:GetObject*' ], resources: [ '*' ] }),
],
});
```
### Timeouts
Users are responsible to define the timeouts for the AWS Lambda functions for
user-defined handlers. It is recommended not to exceed a **14 minutes** timeout,
since all framework functions are configured to time out after 15 minutes, which
is the maximal AWS Lambda timeout.
If your operation takes over **14 minutes**, the recommended approach is to
implement an [asynchronous provider](#asynchronous-providers-iscomplete), and
then configure the timeouts for the asynchronous retries through the
`queryInterval` and the `totalTimeout` options.
### Provider Framework Examples
This module includes a few examples for custom resource implementations:
#### S3File
Provisions an object in an S3 bucket with textual contents. See the source code
for the
[construct](https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/custom-resources/test/provider-framework/integration-test-fixtures/s3-file.ts) and
[handler](https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/custom-resources/test/provider-framework/integration-test-fixtures/s3-file-handler/index.ts).
The following example will create the file `folder/file1.txt` inside `myBucket`
with the contents `hello!`.
```plaintext
// This example exists only for TypeScript
declare const myBucket: s3.Bucket;
new cr.S3File(this, 'MyFile', {
bucket: myBucket,
objectKey: 'folder/file1.txt', // optional
content: 'hello!',
public: true, // optional
});
```
This sample demonstrates the following concepts:
* Synchronous implementation (`isComplete` is not defined)
* Automatically generates the physical name if `objectKey` is not defined
* Handles physical name changes
* Returns resource attributes
* Handles deletions
* Implemented in TypeScript
#### S3Assert
Checks that the textual contents of an S3 object matches a certain value. The check will be retried
for 5 minutes as long as the object is not found or the value is different. See the source code for the
[construct](https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert.ts)
and [handler](https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert-handler/index.py).
The following example defines an `S3Assert` resource which waits until
`myfile.txt` in `myBucket` exists and includes the contents `foo bar`:
```plaintext
// This example exists only for TypeScript
declare const myBucket: s3.Bucket;
new cr.S3Assert(this, 'AssertMyFile', {
bucket: myBucket,
objectKey: 'myfile.txt',
expectedContent: 'foo bar',
});
```
This sample demonstrates the following concepts:
* Asynchronous implementation
* Non-intrinsic physical IDs
* Implemented in Python
### Customizing Provider Function name
In multi-account environments or when the custom resource may be re-utilized across several
stacks it may be useful to manually set a name for the Provider Function Lambda and therefore
have a predefined service token ARN.
```ts
declare const onEvent: lambda.Function;
declare const isComplete: lambda.Function;
declare const myRole: iam.Role;
const myProvider = new cr.Provider(this, 'MyProvider', {
onEventHandler: onEvent,
isCompleteHandler: isComplete,
logGroup: new logs.LogGroup(this, 'MyProviderLogs', {
retention: logs.RetentionDays.ONE_DAY,
}),
role: myRole,
providerFunctionName: 'the-lambda-name', // Optional
});
```
### Customizing Provider Function environment encryption key
Sometimes it may be useful to manually set a AWS KMS key for the Provider Function Lambda and therefore
be able to view, manage and audit the key usage.
```ts
import * as kms from 'aws-cdk-lib/aws-kms';
declare const onEvent: lambda.Function;
declare const isComplete: lambda.Function;
declare const myRole: iam.Role;
const key = new kms.Key(this, 'MyKey');
const myProvider = new cr.Provider(this, 'MyProvider', {
onEventHandler: onEvent,
isCompleteHandler: isComplete,
logGroup: new logs.LogGroup(this, 'MyProviderLogs', {
retention: logs.RetentionDays.ONE_DAY,
}),
role: myRole,
providerFunctionEnvEncryption: key, // Optional
});
```
## Custom Resources for AWS APIs
Sometimes a single API call can fill the gap in the CloudFormation coverage. In
this case you can use the `AwsCustomResource` construct. This construct creates
a custom resource that can be customized to make specific API calls for the
`CREATE`, `UPDATE` and `DELETE` events. Additionally, data returned by the API
call can be extracted and used in other constructs/resources (creating a real
CloudFormation dependency using `Fn::GetAtt` under the hood).
The physical id of the custom resource can be specified or derived from the data
returned by the API call.
The `AwsCustomResource` uses the AWS SDK for JavaScript. Services, actions and
parameters can be found in the [API documentation](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/index.html).
Path to data must be specified using a dot notation, e.g. to get the string value
of the `Title` attribute for the first item returned by `dynamodb.query` it should
be `Items.0.Title.S`.
To make sure that the newest API calls are available the latest AWS SDK v3 is installed
in the Lambda function implementing the custom resource. The installation takes around 60
seconds. If you prefer to optimize for speed, you can disable the installation by setting
the `installLatestAwsSdk` prop to `false`.
### Custom Resource Execution Policy
The `policy` property defines the IAM Policy that will be applied to the API calls. This must be provided
if an existing `role` is not specified and is optional otherwise. The library provides two factory methods
to quickly configure this:
* **`AwsCustomResourcePolicy.fromSdkCalls`** - Use this to auto-generate IAM
Policy statements based on the configured SDK calls. Keep two things in mind
when using this policy:
* This policy variant assumes the IAM policy name has the same name as the API
call. This is true in 99% of cases, but there are exceptions (for example,
S3's `PutBucketLifecycleConfiguration` requires
`s3:PutLifecycleConfiguration` permissions, Lambda's `Invoke` requires
`lambda:InvokeFunction` permissions). Use `fromStatements` if you want to
do a call that requires different IAM action names.
* You will have to either provide specific ARNs, or explicitly use
`AwsCustomResourcePolicy.ANY_RESOURCE` to allow access to any resource.
* **`AwsCustomResourcePolicy.fromStatements`** - Use this to specify your own
custom statements.
The custom resource also implements `iam.IGrantable`, making it possible to use the `grantXxx()` methods.
As this custom resource uses a singleton Lambda function, it's important to note
that the function's role will eventually accumulate the permissions/grants from all
resources.
Chained API calls can be achieved by creating dependencies:
```ts
const awsCustom1 = new cr.AwsCustomResource(this, 'API1', {
onCreate: {
service: '...',
action: '...',
physicalResourceId: cr.PhysicalResourceId.of('...'),
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
const awsCustom2 = new cr.AwsCustomResource(this, 'API2', {
onCreate: {
service: '...',
action: '...',
parameters: {
text: awsCustom1.getResponseField('Items.0.text'),
},
physicalResourceId: cr.PhysicalResourceId.of('...'),
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
```
### Physical Resource Id Parameter
Some AWS APIs may require passing the physical resource id in as a parameter for doing updates and deletes. You can pass it by using `PhysicalResourceIdReference`.
```ts
const awsCustom = new cr.AwsCustomResource(this, 'aws-custom', {
onCreate: {
service: '...',
action: '...',
parameters: {
text: '...',
},
physicalResourceId: cr.PhysicalResourceId.of('...'),
},
onUpdate: {
service: '...',
action: '...',
parameters: {
text: '...',
resourceId: new cr.PhysicalResourceIdReference(),
},
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
})
```
You can omit `PhysicalResourceId` property in `onUpdate` to passthrough the value in `onCreate`. This behavior is useful when using Update APIs that response with an empty body.
> AwsCustomResource.getResponseField() and .getResponseFieldReference() will not work if the Create and Update APIs don't consistently return the same fields.
### Handling Custom Resource Errors
Every error produced by the API call is treated as is and will cause a "FAILED" response to be submitted to CloudFormation.
You can ignore some errors by specifying the `ignoreErrorCodesMatching` property, which accepts a regular expression that is
tested against the `code` property of the response. If matched, a "SUCCESS" response is submitted.
Note that in such a case, the call response data and the `Data` key submitted to CloudFormation would both be an empty JSON object.
Since a successful resource provisioning might or might not produce outputs, this presents us with some limitations:
* `PhysicalResourceId.fromResponse` - Since the call response data might be empty, we cannot use it to extract the physical id.
* `getResponseField` and `getResponseFieldReference` - Since the `Data` key is empty, the resource will not have any attributes, and therefore, invoking these functions will result in an error.
In both the cases, you will get a synth time error if you attempt to use it in conjunction with `ignoreErrorCodesMatching`.
### Customizing the Lambda function implementing the custom resource
Use the `role`, `timeout`, `logGroup`, `functionName` and `removalPolicy` properties to customize
the Lambda function implementing the custom resource:
```ts
declare const myRole: iam.Role;
new cr.AwsCustomResource(this, 'Customized', {
role: myRole, // must be assumable by the `lambda.amazonaws.com` service principal
timeout: Duration.minutes(10), // defaults to 2 minutes
logGroup: new logs.LogGroup(this, 'AwsCustomResourceLogs', {
retention: logs.RetentionDays.ONE_DAY,
}),
functionName: 'my-custom-name', // defaults to a CloudFormation generated name
removalPolicy: RemovalPolicy.RETAIN, // defaults to `RemovalPolicy.DESTROY`
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
```
Additionally, the Lambda function can be placed in a private VPC by using the `vpc`
and `vpcSubnets` properties.
```ts
declare const vpc: ec2.Vpc;
new cr.AwsCustomResource(this, 'CustomizedInVpc', {
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
```
Note that Lambda functions in a VPC
[require Network Address Translation (NAT) in order to access the internet][vpc-internet].
The subnets specified in `vpcSubnets` must be private subnets.
[vpc-internet]: https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html#vpc-internet
### Restricting the output of the Custom Resource
CloudFormation imposes a hard limit of 4096 bytes for custom resources response
objects. If your API call returns an object that exceeds this limit, you can restrict
the data returned by the custom resource to specific paths in the API response:
```ts
new cr.AwsCustomResource(this, 'ListObjects', {
onCreate: {
service: 's3',
action: 'ListObjectsV2',
parameters: {
Bucket: 'my-bucket',
},
physicalResourceId: cr.PhysicalResourceId.of('id'),
outputPaths: ['Contents.0.Key', 'Contents.1.Key'], // Output only the two first keys
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
```
Note that even if you restrict the output of your custom resource you can still use any
path in `PhysicalResourceId.fromResponse()`.
### Custom Resource Examples
#### Get the latest version of a secure SSM parameter
```ts
const getParameter = new cr.AwsCustomResource(this, 'GetParameter', {
onUpdate: { // will also be called for a CREATE event
service: 'SSM',
action: 'GetParameter',
parameters: {
Name: 'my-parameter',
WithDecryption: true,
},
physicalResourceId: cr.PhysicalResourceId.of(Date.now().toString()), // Update physical id to always fetch the latest version
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
// Use the value in another construct with
getParameter.getResponseField('Parameter.Value');
```
#### Associate a PrivateHostedZone with VPC shared from another account
```ts
const getParameter = new cr.AwsCustomResource(this, 'AssociateVPCWithHostedZone', {
onCreate: {
assumedRoleArn: 'arn:aws:iam::OTHERACCOUNT:role/CrossAccount/ManageHostedZoneConnections',
service: 'Route53',
action: 'AssociateVPCWithHostedZone',
parameters: {
HostedZoneId: 'hz-123',
VPC: {
VPCId: 'vpc-123',
VPCRegion: 'region-for-vpc',
},
},
physicalResourceId: cr.PhysicalResourceId.of('${vpcStack.SharedVpc.VpcId}-${vpcStack.Region}-${PrivateHostedZone.HostedZoneId}'),
},
//Will ignore any resource and use the assumedRoleArn as resource and 'sts:AssumeRole' for service:action
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
```
#### Using AWS SDK for JavaScript v3
`AwsCustomResource` uses Node 18 and AWS SDK v3 by default. You can specify the service as either the name of the SDK module, or just the service name. Using API Gateway as an example, the following formats are all accepted for `service`:
* The SDKv3 service name: `api-gateway` (recommended)
* The full SDKv3 package name: `@aws-sdk/client-api-gateway`
* The SDKv2 constructor name: `APIGateway`
* The SDKv2 constructor name in all lower case: `apigateway`
The following formats are accepted for `action`:
* The API call name: `GetRestApi` (recommended)
* The API call name with a lowercase starting letter method name: `getRestApi`
* The SDKv3 command class name: `GetRestApiCommand`
For readability, we recommend using the short forms going forward:
```ts
new cr.AwsCustomResource(this, 'GetParameter', {
resourceType: 'Custom::SSMParameter',
onUpdate: {
service: 'ssm', // 'SSM' in v2
action: 'GetParameter', // 'getParameter' in v2
parameters: {
Name: 'foo',
WithDecryption: true,
},
physicalResourceId: cr.PhysicalResourceId.fromResponse('Parameter.ARN'),
},
});
```
#### Making Cross Account Calls
Example of making a cross account call using an assumed role. If deploying the custom resource in a region where the cross account role is not defined (i.e. an opt-in region that is not enabled in the account owning the role), set the region parameter to a region enabled in that account.
```ts
const crossAccountRoleArn = 'arn:aws:iam::OTHERACCOUNT:role/CrossAccountRoleName'; // arn of role deployed in separate account
const callRegion = 'us-west-1'; // sdk call to be made in specified region (optional)
new cr.AwsCustomResource(this, 'CrossAccount', {
onCreate: {
assumedRoleArn: crossAccountRoleArn,
region: callRegion, // optional
service: 'sts',
action: 'GetCallerIdentity',
physicalResourceId: cr.PhysicalResourceId.of('id'),
},
policy: cr.AwsCustomResourcePolicy.fromStatements([iam.PolicyStatement.fromJson({
Effect: "Allow",
Action: "sts:AssumeRole",
Resource: crossAccountRoleArn,
})]),
});
```