-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
README.md
1541 lines (1183 loc) · 57.4 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
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# AWS Cloud Development Kit Library
The AWS CDK construct library provides APIs to define your CDK application and add
CDK constructs to the application.
## Usage
### Upgrade from CDK 1.x
When upgrading from CDK 1.x, remove all dependencies to individual CDK packages
from your dependencies file and follow the rest of the sections.
### Installation
To use this package, you need to declare this package and the `constructs` package as
dependencies.
According to the kind of project you are developing:
For projects that are CDK libraries in NPM, declare them both under the `devDependencies` **and** `peerDependencies` sections.
To make sure your library is compatible with the widest range of CDK versions: pick the minimum `aws-cdk-lib` version
that your library requires; declare a range dependency with a caret on that version in peerDependencies, and declare a
point version dependency on that version in devDependencies.
For example, let's say the minimum version your library needs is `2.38.0`. Your `package.json` should look like this:
```javascript
{
"peerDependencies": {
"aws-cdk-lib": "^2.38.0",
"constructs": "^10.0.0"
},
"devDependencies": {
/* Install the oldest version for testing so we don't accidentally use features from a newer version than we declare */
"aws-cdk-lib": "2.38.0"
}
}
```
For CDK apps, declare them under the `dependencies` section. Use a caret so you always get the latest version:
```json
{
"dependencies": {
"aws-cdk-lib": "^2.38.0",
"constructs": "^10.0.0"
}
}
```
### Use in your code
#### Classic import
You can use a classic import to get access to each service namespaces:
```ts nofixture
import { Stack, App, aws_s3 as s3 } from 'aws-cdk-lib';
const app = new App();
const stack = new Stack(app, 'TestStack');
new s3.Bucket(stack, 'TestBucket');
```
#### Barrel import
Alternatively, you can use "barrel" imports:
```ts nofixture
import { App, Stack } from 'aws-cdk-lib';
import { Bucket } from 'aws-cdk-lib/aws-s3';
const app = new App();
const stack = new Stack(app, 'TestStack');
new Bucket(stack, 'TestBucket');
```
<!--BEGIN CORE DOCUMENTATION-->
## Stacks and Stages
A `Stack` is the smallest physical unit of deployment, and maps directly onto
a CloudFormation Stack. You define a Stack by defining a subclass of `Stack`
-- let's call it `MyStack` -- and instantiating the constructs that make up
your application in `MyStack`'s constructor. You then instantiate this stack
one or more times to define different instances of your application. For example,
you can instantiate it once using few and cheap EC2 instances for testing,
and once again using more and bigger EC2 instances for production.
When your application grows, you may decide that it makes more sense to split it
out across multiple `Stack` classes. This can happen for a number of reasons:
- You could be starting to reach the maximum number of resources allowed in a single
stack (this is currently 500).
- You could decide you want to separate out stateful resources and stateless resources
into separate stacks, so that it becomes easy to tear down and recreate the stacks
that don't have stateful resources.
- There could be a single stack with resources (like a VPC) that are shared
between multiple instances of other stacks containing your applications.
As soon as your conceptual application starts to encompass multiple stacks,
it is convenient to wrap them in another construct that represents your
logical application. You can then treat that new unit the same way you used
to be able to treat a single stack: by instantiating it multiple times
for different instances of your application.
You can define a custom subclass of `Stage`, holding one or more
`Stack`s, to represent a single logical instance of your application.
As a final note: `Stack`s are not a unit of reuse. They describe physical
deployment layouts, and as such are best left to application builders to
organize their deployments with. If you want to vend a reusable construct,
define it as a subclasses of `Construct`: the consumers of your construct
will decide where to place it in their own stacks.
## Stack Synthesizers
Each Stack has a *synthesizer*, an object that determines how and where
the Stack should be synthesized and deployed. The synthesizer controls
aspects like:
- How does the stack reference assets? (Either through CloudFormation
parameters the CLI supplies, or because the Stack knows a predefined
location where assets will be uploaded).
- What roles are used to deploy the stack? These can be bootstrapped
roles, roles created in some other way, or just the CLI's current
credentials.
The following synthesizers are available:
- `DefaultStackSynthesizer`: recommended. Uses predefined asset locations and
roles created by the modern bootstrap template. Access control is done by
controlling who can assume the deploy role. This is the default stack
synthesizer in CDKv2.
- `LegacyStackSynthesizer`: Uses CloudFormation parameters to communicate
asset locations, and the CLI's current permissions to deploy stacks. This
is the default stack synthesizer in CDKv1.
- `CliCredentialsStackSynthesizer`: Uses predefined asset locations, and the
CLI's current permissions.
Each of these synthesizers takes configuration arguments. To configure
a stack with a synthesizer, pass it as one of its properties:
```ts
new MyStack(app, 'MyStack', {
synthesizer: new DefaultStackSynthesizer({
fileAssetsBucketName: 'my-orgs-asset-bucket',
}),
});
```
For more information on bootstrapping accounts and customizing synthesis,
see [Bootstrapping in the CDK Developer Guide](https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html).
## Nested Stacks
[Nested stacks](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-nested-stacks.html) are stacks created as part of other stacks. You create a nested stack within another stack by using the `NestedStack` construct.
As your infrastructure grows, common patterns can emerge in which you declare the same components in multiple templates. You can separate out these common components and create dedicated templates for them. Then use the resource in your template to reference other templates, creating nested stacks.
For example, assume that you have a load balancer configuration that you use for most of your stacks. Instead of copying and pasting the same configurations into your templates, you can create a dedicated template for the load balancer. Then, you just use the resource to reference that template from within other templates.
The following example will define a single top-level stack that contains two nested stacks: each one with a single Amazon S3 bucket:
```ts
class MyNestedStack extends cfn.NestedStack {
constructor(scope: Construct, id: string, props?: cfn.NestedStackProps) {
super(scope, id, props);
new s3.Bucket(this, 'NestedBucket');
}
}
class MyParentStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
new MyNestedStack(this, 'Nested1');
new MyNestedStack(this, 'Nested2');
}
}
```
Resources references across nested/parent boundaries (even with multiple levels of nesting) will be wired by the AWS CDK
through CloudFormation parameters and outputs. When a resource from a parent stack is referenced by a nested stack,
a CloudFormation parameter will automatically be added to the nested stack and assigned from the parent; when a resource
from a nested stack is referenced by a parent stack, a CloudFormation output will be automatically be added to the
nested stack and referenced using `Fn::GetAtt "Outputs.Xxx"` from the parent.
Nested stacks also support the use of Docker image and file assets.
## Accessing resources in a different stack
You can access resources in a different stack, as long as they are in the
same account and AWS Region (see [next section](#accessing-resources-in-a-different-stack-and-region) for an exception).
The following example defines the stack `stack1`,
which defines an Amazon S3 bucket. Then it defines a second stack, `stack2`,
which takes the bucket from stack1 as a constructor property.
```ts
const prod = { account: '123456789012', region: 'us-east-1' };
const stack1 = new StackThatProvidesABucket(app, 'Stack1' , { env: prod });
// stack2 will take a property { bucket: IBucket }
const stack2 = new StackThatExpectsABucket(app, 'Stack2', {
bucket: stack1.bucket,
env: prod
});
```
If the AWS CDK determines that the resource is in the same account and
Region, but in a different stack, it automatically synthesizes AWS
CloudFormation
[Exports](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-stack-exports.html)
in the producing stack and an
[Fn::ImportValue](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html)
in the consuming stack to transfer that information from one stack to the
other.
## Accessing resources in a different stack and region
> **This feature is currently experimental**
You can enable the Stack property `crossRegionReferences`
in order to access resources in a different stack _and_ region. With this feature flag
enabled it is possible to do something like creating a CloudFront distribution in `us-east-2` and
an ACM certificate in `us-east-1`.
```ts
const stack1 = new Stack(app, 'Stack1', {
env: {
region: 'us-east-1',
},
crossRegionReferences: true,
});
const cert = new acm.Certificate(stack1, 'Cert', {
domainName: '*.example.com',
validation: acm.CertificateValidation.fromDns(route53.PublicHostedZone.fromHostedZoneId(stack1, 'Zone', 'Z0329774B51CGXTDQV3X')),
});
const stack2 = new Stack(app, 'Stack2', {
env: {
region: 'us-east-2',
},
crossRegionReferences: true,
});
new cloudfront.Distribution(stack2, 'Distribution', {
defaultBehavior: {
origin: new origins.HttpOrigin('example.com'),
},
domainNames: ['dev.example.com'],
certificate: cert,
});
```
When the AWS CDK determines that the resource is in a different stack _and_ is in a different
region, it will "export" the value by creating a custom resource in the producing stack which
creates SSM Parameters in the consuming region for each exported value. The parameters will be
created with the name '/cdk/exports/${consumingStackName}/${export-name}'.
In order to "import" the exports into the consuming stack a [SSM Dynamic reference](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html#dynamic-references-ssm)
is used to reference the SSM parameter which was created.
In order to mimic strong references, a Custom Resource is also created in the consuming
stack which marks the SSM parameters as being "imported". When a parameter has been successfully
imported, the producing stack cannot update the value.
See the [adr](https://github.com/aws/aws-cdk/blob/main/packages/@aws-cdk/core/adr/cross-region-stack-references)
for more details on this feature.
### Removing automatic cross-stack references
The automatic references created by CDK when you use resources across stacks
are convenient, but may block your deployments if you want to remove the
resources that are referenced in this way. You will see an error like:
```text
Export Stack1:ExportsOutputFnGetAtt-****** cannot be deleted as it is in use by Stack1
```
Let's say there is a Bucket in the `stack1`, and the `stack2` references its
`bucket.bucketName`. You now want to remove the bucket and run into the error above.
It's not safe to remove `stack1.bucket` while `stack2` is still using it, so
unblocking yourself from this is a two-step process. This is how it works:
DEPLOYMENT 1: break the relationship
- Make sure `stack2` no longer references `bucket.bucketName` (maybe the consumer
stack now uses its own bucket, or it writes to an AWS DynamoDB table, or maybe you just
remove the Lambda Function altogether).
- In the `stack1` class, call `this.exportValue(this.bucket.bucketName)`. This
will make sure the CloudFormation Export continues to exist while the relationship
between the two stacks is being broken.
- Deploy (this will effectively only change the `stack2`, but it's safe to deploy both).
DEPLOYMENT 2: remove the resource
- You are now free to remove the `bucket` resource from `stack1`.
- Don't forget to remove the `exportValue()` call as well.
- Deploy again (this time only the `stack1` will be changed -- the bucket will be deleted).
## Durations
To make specifications of time intervals unambiguous, a single class called
`Duration` is used throughout the AWS Construct Library by all constructs
that that take a time interval as a parameter (be it for a timeout, a
rate, or something else).
An instance of Duration is constructed by using one of the static factory
methods on it:
```ts
Duration.seconds(300) // 5 minutes
Duration.minutes(5) // 5 minutes
Duration.hours(1) // 1 hour
Duration.days(7) // 7 days
Duration.parse('PT5M') // 5 minutes
```
Durations can be added or subtracted together:
```ts
Duration.minutes(1).plus(Duration.seconds(60)); // 2 minutes
Duration.minutes(5).minus(Duration.seconds(10)); // 290 secondes
```
## Size (Digital Information Quantity)
To make specification of digital storage quantities unambiguous, a class called
`Size` is available.
An instance of `Size` is initialized through one of its static factory methods:
```ts
Size.kibibytes(200) // 200 KiB
Size.mebibytes(5) // 5 MiB
Size.gibibytes(40) // 40 GiB
Size.tebibytes(200) // 200 TiB
Size.pebibytes(3) // 3 PiB
```
Instances of `Size` created with one of the units can be converted into others.
By default, conversion to a higher unit will fail if the conversion does not produce
a whole number. This can be overridden by unsetting `integral` property.
```ts
Size.mebibytes(2).toKibibytes() // yields 2048
Size.kibibytes(2050).toMebibytes({ rounding: SizeRoundingBehavior.FLOOR }) // yields 2
```
## Secrets
To help avoid accidental storage of secrets as plain text, we use the `SecretValue` type to
represent secrets. Any construct that takes a value that should be a secret (such as
a password or an access key) will take a parameter of type `SecretValue`.
The best practice is to store secrets in AWS Secrets Manager and reference them using `SecretValue.secretsManager`:
```ts
const secret = SecretValue.secretsManager('secretId', {
jsonField: 'password', // optional: key of a JSON field to retrieve (defaults to all content),
versionId: 'id', // optional: id of the version (default AWSCURRENT)
versionStage: 'stage', // optional: version stage name (default AWSCURRENT)
});
```
Using AWS Secrets Manager is the recommended way to reference secrets in a CDK app.
`SecretValue` also supports the following secret sources:
- `SecretValue.unsafePlainText(secret)`: stores the secret as plain text in your app and the resulting template (not recommended).
- `SecretValue.secretsManager(secret)`: refers to a secret stored in Secrets Manager
- `SecretValue.ssmSecure(param, version)`: refers to a secret stored as a SecureString in the SSM
Parameter Store. If you don't specify the exact version, AWS CloudFormation uses the latest
version of the parameter.
- `SecretValue.cfnParameter(param)`: refers to a secret passed through a CloudFormation parameter (must have `NoEcho: true`).
- `SecretValue.cfnDynamicReference(dynref)`: refers to a secret described by a CloudFormation dynamic reference (used by `ssmSecure` and `secretsManager`).
- `SecretValue.resourceAttribute(attr)`: refers to a secret returned from a CloudFormation resource creation.
`SecretValue`s should only be passed to constructs that accept properties of type
`SecretValue`. These constructs are written to ensure your secrets will not be
exposed where they shouldn't be. If you try to use a `SecretValue` in a
different location, an error about unsafe secret usage will be thrown at
synthesis time.
If you rotate the secret's value in Secrets Manager, you must also change at
least one property on the resource where you are using the secret, to force
CloudFormation to re-read the secret.
`SecretValue.ssmSecure()` is only supported for a limited set of resources.
[Click here for a list of supported resources and properties](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html#template-parameters-dynamic-patterns-resources).
## ARN manipulation
Sometimes you will need to put together or pick apart Amazon Resource Names
(ARNs). The functions `stack.formatArn()` and `stack.splitArn()` exist for
this purpose.
`formatArn()` can be used to build an ARN from components. It will automatically
use the region and account of the stack you're calling it on:
```ts
declare const stack: Stack;
// Builds "arn:<PARTITION>:lambda:<REGION>:<ACCOUNT>:function:MyFunction"
stack.formatArn({
service: 'lambda',
resource: 'function',
arnFormat: ArnFormat.COLON_RESOURCE_NAME,
resourceName: 'MyFunction'
});
```
`splitArn()` can be used to get a single component from an ARN. `splitArn()`
will correctly deal with both literal ARNs and deploy-time values (tokens),
but in case of a deploy-time value be aware that the result will be another
deploy-time value which cannot be inspected in the CDK application.
```ts
declare const stack: Stack;
// Extracts the function name out of an AWS Lambda Function ARN
const arnComponents = stack.splitArn(arn, ArnFormat.COLON_RESOURCE_NAME);
const functionName = arnComponents.resourceName;
```
Note that the format of the resource separator depends on the service and
may be any of the values supported by `ArnFormat`. When dealing with these
functions, it is important to know the format of the ARN you are dealing with.
For an exhaustive list of ARN formats used in AWS, see [AWS ARNs and
Namespaces](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html)
in the AWS General Reference.
## Dependencies
### Construct Dependencies
Sometimes AWS resources depend on other resources, and the creation of one
resource must be completed before the next one can be started.
In general, CloudFormation will correctly infer the dependency relationship
between resources based on the property values that are used. In the cases where
it doesn't, the AWS Construct Library will add the dependency relationship for
you.
If you need to add an ordering dependency that is not automatically inferred,
you do so by adding a dependency relationship using
`constructA.node.addDependency(constructB)`. This will add a dependency
relationship between all resources in the scope of `constructA` and all
resources in the scope of `constructB`.
If you want a single object to represent a set of constructs that are not
necessarily in the same scope, you can use a `DependencyGroup`. The
following creates a single object that represents a dependency on two
constructs, `constructB` and `constructC`:
```ts
// Declare the dependable object
const bAndC = new DependencyGroup();
bAndC.add(constructB);
bAndC.add(constructC);
// Take the dependency
constructA.node.addDependency(bAndC);
```
### Stack Dependencies
Two different stack instances can have a dependency on one another. This
happens when an resource from one stack is referenced in another stack. In
that case, CDK records the cross-stack referencing of resources,
automatically produces the right CloudFormation primitives, and adds a
dependency between the two stacks. You can also manually add a dependency
between two stacks by using the `stackA.addDependency(stackB)` method.
A stack dependency has the following implications:
- Cyclic dependencies are not allowed, so if `stackA` is using resources from
`stackB`, the reverse is not possible anymore.
- Stacks with dependencies between them are treated specially by the CDK
toolkit:
- If `stackA` depends on `stackB`, running `cdk deploy stackA` will also
automatically deploy `stackB`.
- `stackB`'s deployment will be performed *before* `stackA`'s deployment.
### CfnResource Dependencies
To make declaring dependencies between `CfnResource` objects easier, you can declare dependencies from one `CfnResource` object on another by using the `cfnResource1.addDependency(cfnResource2)` method. This method will work for resources both within the same stack and across stacks as it detects the relative location of the two resources and adds the dependency either to the resource or between the relevant stacks, as appropriate. If more complex logic is in needed, you can similarly remove, replace, or view dependencies between `CfnResource` objects with the `CfnResource` `removeDependency`, `replaceDependency`, and `obtainDependencies` methods, respectively.
## Custom Resources
Custom Resources are CloudFormation resources that are implemented by arbitrary
user code. They can do arbitrary lookups or modifications during a
CloudFormation deployment.
Custom resources are backed by *custom resource providers*. Commonly, these are
Lambda Functions that are deployed in the same deployment as the one that
defines the custom resource itself, but they can also be backed by Lambda
Functions deployed previously, or code responding to SNS Topic events running on
EC2 instances in a completely different account. For more information on custom
resource providers, see the next section.
Once you have a provider, each definition of a `CustomResource` construct
represents one invocation. A single provider can be used for the implementation
of arbitrarily many custom resource definitions. A single definition looks like
this:
```ts
new CustomResource(this, 'MyMagicalResource', {
resourceType: 'Custom::MyCustomResource', // must start with 'Custom::'
// the resource properties
properties: {
Property1: 'foo',
Property2: 'bar'
},
// the ARN of the provider (SNS/Lambda) which handles
// CREATE, UPDATE or DELETE events for this resource type
// see next section for details
serviceToken: 'ARN'
});
```
### Custom Resource Providers
Custom resources are backed by a **custom resource provider** which can be
implemented in one of the following ways. The following table compares the
various provider types (ordered from low-level to high-level):
| Provider | Compute Type | Error Handling | Submit to CloudFormation | Max Timeout | Language | Footprint |
| -------------------------------------------------------------------- | :----------: | :------------: | :----------------------: | :-------------: | :------: | :-------: |
| [sns.Topic](#amazon-sns-topic) | Self-managed | Manual | Manual | Unlimited | Any | Depends |
| [lambda.Function](#aws-lambda-function) | AWS Lambda | Manual | Manual | 15min | Any | Small |
| [core.CustomResourceProvider](#the-corecustomresourceprovider-class) | AWS Lambda | Auto | Auto | 15min | Node.js | Small |
| [custom-resources.Provider](#the-custom-resource-provider-framework) | AWS Lambda | Auto | Auto | Unlimited Async | Any | Large |
Legend:
- **Compute type**: which type of compute can be used to execute the handler.
- **Error Handling**: whether errors thrown by handler code are automatically
trapped and a FAILED response is submitted to CloudFormation. If this is
"Manual", developers must take care of trapping errors. Otherwise, events
could cause stacks to hang.
- **Submit to CloudFormation**: whether the framework takes care of submitting
SUCCESS/FAILED responses to CloudFormation through the event's response URL.
- **Max Timeout**: maximum allows/possible timeout.
- **Language**: which programming languages can be used to implement handlers.
- **Footprint**: how many resources are used by the provider framework itself.
**A NOTE ABOUT SINGLETONS**
When defining resources for a custom resource provider, you will likely want to
define them as a *stack singleton* so that only a single instance of the
provider is created in your stack and which is used by all custom resources of
that type.
Here is a basic pattern for defining stack singletons in the CDK. The following
examples ensures that only a single SNS topic is defined:
```ts
function getOrCreate(scope: Construct): sns.Topic {
const stack = Stack.of(scope);
const uniqueid = 'GloballyUniqueIdForSingleton'; // For example, a UUID from `uuidgen`
const existing = stack.node.tryFindChild(uniqueid);
if (existing) {
return existing as sns.Topic;
}
return new sns.Topic(stack, uniqueid);
}
```
#### Amazon SNS Topic
Every time a resource event occurs (CREATE/UPDATE/DELETE), an SNS notification
is sent to the SNS topic. Users must process these notifications (e.g. through a
fleet of worker hosts) and submit success/failure responses to the
CloudFormation service.
> You only need to use this type of provider if your custom resource cannot run on AWS Lambda, for reasons other than the 15
> minute timeout. If you are considering using this type of provider because you want to write a custom resource provider that may need
> to wait for more than 15 minutes for the API calls to stabilize, have a look at the [`custom-resources`](#the-custom-resource-provider-framework) module first.
>
> Refer to the [CloudFormation Custom Resource documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html) for information on the contract your custom resource needs to adhere to.
Set `serviceToken` to `topic.topicArn` in order to use this provider:
```ts
const topic = new sns.Topic(this, 'MyProvider');
new CustomResource(this, 'MyResource', {
serviceToken: topic.topicArn
});
```
#### AWS Lambda Function
An AWS lambda function is called *directly* by CloudFormation for all resource
events. The handler must take care of explicitly submitting a success/failure
response to the CloudFormation service and handle various error cases.
> **We do not recommend you use this provider type.** The CDK has wrappers around Lambda Functions that make them easier to work with.
>
> If you do want to use this provider, refer to the [CloudFormation Custom Resource documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html) for information on the contract your custom resource needs to adhere to.
Set `serviceToken` to `lambda.functionArn` to use this provider:
```ts
const fn = new lambda.SingletonFunction(this, 'MyProvider', functionProps);
new CustomResource(this, 'MyResource', {
serviceToken: fn.functionArn,
});
```
#### The `core.CustomResourceProvider` class
The class [`@aws-cdk/core.CustomResourceProvider`] offers a basic low-level
framework designed to implement simple and slim custom resource providers. It
currently only supports Node.js-based user handlers, represents permissions as raw
JSON blobs instead of `iam.PolicyStatement` objects, and it does not have
support for asynchronous waiting (handler cannot exceed the 15min lambda
timeout). The `CustomResourceProviderRuntime` supports runtime `nodejs12.x`,
`nodejs14.x`, `nodejs16.x`, `nodejs18.x`.
[`@aws-cdk/core.CustomResourceProvider`]: https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.CustomResourceProvider.html
> **As an application builder, we do not recommend you use this provider type.** This provider exists purely for custom resources that are part of the AWS Construct Library.
>
> The [`custom-resources`](#the-custom-resource-provider-framework) provider is more convenient to work with and more fully-featured.
The provider has a built-in singleton method which uses the resource type as a
stack-unique identifier and returns the service token:
```ts
const serviceToken = CustomResourceProvider.getOrCreate(this, 'Custom::MyCustomResourceType', {
codeDirectory: `${__dirname}/my-handler`,
runtime: CustomResourceProviderRuntime.NODEJS_18_X,
description: "Lambda function created by the custom resource provider",
});
new CustomResource(this, 'MyResource', {
resourceType: 'Custom::MyCustomResourceType',
serviceToken: serviceToken
});
```
The directory (`my-handler` in the above example) must include an `index.js` file. It cannot import
external dependencies or files outside this directory. It must export an async
function named `handler`. This function accepts the CloudFormation resource
event object and returns an object with the following structure:
```js
exports.handler = async function(event) {
const id = event.PhysicalResourceId; // only for "Update" and "Delete"
const props = event.ResourceProperties;
const oldProps = event.OldResourceProperties; // only for "Update"s
switch (event.RequestType) {
case "Create":
// ...
case "Update":
// ...
// if an error is thrown, a FAILED response will be submitted to CFN
throw new Error('Failed!');
case "Delete":
// ...
}
return {
// (optional) the value resolved from `resource.ref`
// defaults to "event.PhysicalResourceId" or "event.RequestId"
PhysicalResourceId: "REF",
// (optional) calling `resource.getAtt("Att1")` on the custom resource in the CDK app
// will return the value "BAR".
Data: {
Att1: "BAR",
Att2: "BAZ"
},
// (optional) user-visible message
Reason: "User-visible message",
// (optional) hides values from the console
NoEcho: true
};
}
```
Here is an complete example of a custom resource that summarizes two numbers:
`sum-handler/index.js`:
```js
exports.handler = async (e) => {
return {
Data: {
Result: e.ResourceProperties.lhs + e.ResourceProperties.rhs,
},
};
};
```
`sum.ts`:
```ts nofixture
import { Construct } from 'constructs';
import {
CustomResource,
CustomResourceProvider,
CustomResourceProviderRuntime,
Token,
} from 'aws-cdk-lib';
export interface SumProps {
readonly lhs: number;
readonly rhs: number;
}
export class Sum extends Construct {
public readonly result: number;
constructor(scope: Construct, id: string, props: SumProps) {
super(scope, id);
const resourceType = 'Custom::Sum';
const serviceToken = CustomResourceProvider.getOrCreate(this, resourceType, {
codeDirectory: `${__dirname}/sum-handler`,
runtime: CustomResourceProviderRuntime.NODEJS_18_X,
});
const resource = new CustomResource(this, 'Resource', {
resourceType: resourceType,
serviceToken: serviceToken,
properties: {
lhs: props.lhs,
rhs: props.rhs
}
});
this.result = Token.asNumber(resource.getAtt('Result'));
}
}
```
Usage will look like this:
```ts fixture=README-custom-resource-provider
const sum = new Sum(this, 'MySum', { lhs: 40, rhs: 2 });
new CfnOutput(this, 'Result', { value: Token.asString(sum.result) });
```
To access the ARN of the provider's AWS Lambda function role, use the `getOrCreateProvider()`
built-in singleton method:
```ts
const provider = CustomResourceProvider.getOrCreateProvider(this, 'Custom::MyCustomResourceType', {
codeDirectory: `${__dirname}/my-handler`,
runtime: CustomResourceProviderRuntime.NODEJS_18_X,
});
const roleArn = provider.roleArn;
```
This role ARN can then be used in resource-based IAM policies.
To add IAM policy statements to this role, use `addToRolePolicy()`:
```ts
const provider = CustomResourceProvider.getOrCreateProvider(this, 'Custom::MyCustomResourceType', {
codeDirectory: `${__dirname}/my-handler`,
runtime: CustomResourceProviderRuntime.NODEJS_18_X,
});
provider.addToRolePolicy({
Effect: 'Allow',
Action: 's3:GetObject',
Resource: '*',
})
```
Note that `addToRolePolicy()` uses direct IAM JSON policy blobs, *not* a
`iam.PolicyStatement` object like you will see in the rest of the CDK.
#### The Custom Resource Provider Framework
The [`@aws-cdk/custom-resources`] module includes an advanced framework for
implementing custom resource providers.
[`@aws-cdk/custom-resources`]: https://docs.aws.amazon.com/cdk/api/latest/docs/custom-resources-readme.html
Handlers are implemented as AWS Lambda functions, which means that they can be
implemented in any Lambda-supported runtime. Furthermore, this provider has an
asynchronous mode, which means that users can provide an `isComplete` lambda
function which is called periodically until the operation is complete. This
allows implementing providers that can take up to two hours to stabilize.
Set `serviceToken` to `provider.serviceToken` to use this type of provider:
```ts
const provider = new customresources.Provider(this, 'MyProvider', {
onEventHandler,
isCompleteHandler, // optional async waiter
});
new CustomResource(this, 'MyResource', {
serviceToken: provider.serviceToken
});
```
See the [documentation](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-cdk-lib.custom_resources-readme.html) for more details.
## AWS CloudFormation features
A CDK stack synthesizes to an AWS CloudFormation Template. This section
explains how this module allows users to access low-level CloudFormation
features when needed.
### Stack Outputs
CloudFormation [stack outputs][cfn-stack-output] and exports are created using
the `CfnOutput` class:
```ts
new CfnOutput(this, 'OutputName', {
value: myBucket.bucketName,
description: 'The name of an S3 bucket', // Optional
exportName: 'TheAwesomeBucket', // Registers a CloudFormation export named "TheAwesomeBucket"
});
```
[cfn-stack-output]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html
### Parameters
CloudFormation templates support the use of [Parameters][cfn-parameters] to
customize a template. They enable CloudFormation users to input custom values to
a template each time a stack is created or updated. While the CDK design
philosophy favors using build-time parameterization, users may need to use
CloudFormation in a number of cases (for example, when migrating an existing
stack to the AWS CDK).
Template parameters can be added to a stack by using the `CfnParameter` class:
```ts
new CfnParameter(this, 'MyParameter', {
type: 'Number',
default: 1337,
// See the API reference for more configuration props
});
```
The value of parameters can then be obtained using one of the `value` methods.
As parameters are only resolved at deployment time, the values obtained are
placeholder tokens for the real value (`Token.isUnresolved()` would return `true`
for those):
```ts
const param = new CfnParameter(this, 'ParameterName', { /* config */ });
// If the parameter is a String
param.valueAsString;
// If the parameter is a Number
param.valueAsNumber;
// If the parameter is a List
param.valueAsList;
```
[cfn-parameters]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html
### Pseudo Parameters
CloudFormation supports a number of [pseudo parameters][cfn-pseudo-params],
which resolve to useful values at deployment time. CloudFormation pseudo
parameters can be obtained from static members of the `Aws` class.
It is generally recommended to access pseudo parameters from the scope's `stack`
instead, which guarantees the values produced are qualifying the designated
stack, which is essential in cases where resources are shared cross-stack:
```ts
// "this" is the current construct
const stack = Stack.of(this);
stack.account; // Returns the AWS::AccountId for this stack (or the literal value if known)
stack.region; // Returns the AWS::Region for this stack (or the literal value if known)
stack.partition; // Returns the AWS::Partition for this stack (or the literal value if known)
```
[cfn-pseudo-params]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html
### Resource Options
CloudFormation resources can also specify [resource
attributes][cfn-resource-attributes]. The `CfnResource` class allows
accessing those through the `cfnOptions` property:
```ts
const rawBucket = new s3.CfnBucket(this, 'Bucket', { /* ... */ });
// -or-
const rawBucketAlt = myBucket.node.defaultChild as s3.CfnBucket;
// then
rawBucket.cfnOptions.condition = new CfnCondition(this, 'EnableBucket', { /* ... */ });
rawBucket.cfnOptions.metadata = {
metadataKey: 'MetadataValue',
};
```
Resource dependencies (the `DependsOn` attribute) is modified using the
`cfnResource.addDependency` method:
```ts
const resourceA = new CfnResource(this, 'ResourceA', resourceProps);
const resourceB = new CfnResource(this, 'ResourceB', resourceProps);
resourceB.addDependency(resourceA);
```
[cfn-resource-attributes]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-product-attribute-reference.html
#### CreationPolicy
Some resources support a [CreationPolicy][creation-policy] to be specified as a CfnOption.
The creation policy is invoked only when AWS CloudFormation creates the associated resource. Currently, the only AWS CloudFormation resources that support creation policies are `CfnAutoScalingGroup`, `CfnInstance`, `CfnWaitCondition` and `CfnFleet`.
The `CfnFleet` resource from the `aws-appstream` module supports specifying `startFleet` as
a property of the creationPolicy on the resource options. Setting it to true will make AWS CloudFormation wait until the fleet is started before continuing with the creation of
resources that depend on the fleet resource.
```ts
const fleet = new appstream.CfnFleet(this, 'Fleet', {
instanceType: 'stream.standard.small',
name: 'Fleet',
computeCapacity: {
desiredInstances: 1,
},
imageName: 'AppStream-AmazonLinux2-09-21-2022',
});
fleet.cfnOptions.creationPolicy = {
startFleet: true,
};
```
The properties passed to the level 2 constructs `AutoScalingGroup` and `Instance` from the
`aws-ec2` module abstract what is passed into the `CfnOption` properties `resourceSignal` and
`autoScalingCreationPolicy`, but when using level 1 constructs you can specify these yourself.
The CfnWaitCondition resource from the `aws-cloudformation` module suppports the `resourceSignal`.
The format of the timeout is `PT#H#M#S`. In the example below AWS Cloudformation will wait for
3 success signals to occur within 15 minutes before the status of the resource will be set to
`CREATE_COMPLETE`.
```ts
declare const resource: CfnResource;
resource.cfnOptions.creationPolicy = {
resourceSignal: {
count: 3,
timeout: 'PR15M',
}
};
```
[creation-policy]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-creationpolicy.html
### Intrinsic Functions and Condition Expressions
CloudFormation supports [intrinsic functions][cfn-intrinsics]. These functions
can be accessed from the `Fn` class, which provides type-safe methods for each
intrinsic function as well as condition expressions:
```ts
declare const myObjectOrArray: any;
declare const myArray: any;
// To use Fn::Base64
Fn.base64('SGVsbG8gQ0RLIQo=');
// To compose condition expressions:
const environmentParameter = new CfnParameter(this, 'Environment');
Fn.conditionAnd(
// The "Environment" CloudFormation template parameter evaluates to "Production"
Fn.conditionEquals('Production', environmentParameter),
// The AWS::Region pseudo-parameter value is NOT equal to "us-east-1"
Fn.conditionNot(Fn.conditionEquals('us-east-1', Aws.REGION)),
);
// To use Fn::ToJsonString
Fn.toJsonString(myObjectOrArray);