Skip to content

Commit 2b160ad

Browse files
committed
chore: examples in aws-ec2 package are compilable
Using the new "rosetta" sample compiler from aws/jsii#925, introduce fixtures and fix up sample code to make all examples in the `aws-ec2` package compile. This serves as a demonstration of how to set up fixtures and how to write the examples.
1 parent d5cefe8 commit 2b160ad

File tree

12 files changed

+219
-65
lines changed

12 files changed

+219
-65
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ coverage/
2828
cdk.context.json
2929
.cdk.staging/
3030
cdk.out/
31-
31+
*.tabl.json

CONTRIBUTING.md

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ and let us know if it's not up-to-date (even better, submit a PR with your corr
3636
- [Updating all Dependencies](#updating-all-dependencies)
3737
- [Running CLI integration tests](#running-cli-integration-tests)
3838
- [API Compatibility Checks](#api-compatibility-checks)
39+
- [Examples](#examples)
3940
- [Feature Flags](#feature-flags)
4041
- [Troubleshooting](#troubleshooting)
4142
- [Debugging](#debugging)
@@ -515,6 +516,62 @@ this API we will not break anyone, because they weren't able to use it. The file
515516
`allowed-breaking-changes.txt` in the root of the repo is an exclusion file that
516517
can be used in these cases.
517518

519+
### Examples
520+
521+
Examples typed in fenced code blocks (looking like `'''ts`, but then with backticks
522+
instead of regular quotes) will be automatically extrated, compiled and translated
523+
to other languages when the bindings are generated.
524+
525+
To successfully do that, they must be compilable. The easiest way to do that is using
526+
a *fixture*, which looks like this:
527+
528+
```
529+
'''ts fixture=with-bucket
530+
bucket.addLifecycleTransition({ ... });
531+
'''
532+
```
533+
534+
While processing the examples, the tool will look for a file called
535+
`rosetta/with-bucket.ts-fixture` in the package directory. This file will be
536+
treated as a regular TypeScript source file, but it must also contain the text
537+
`/// here`, at which point the example will be inserted. The complete file must
538+
compile properly.
539+
540+
Before the `/// here` marker, the fixture should import the necessary packages
541+
and initialize the required variables.
542+
543+
If no fixture is specified, the fixture with the name
544+
`rosetta/default.ts-fixture` will be used if present. `nofixture` can be used to
545+
opt out of that behavior.
546+
547+
In an `@example` block, which is unfenced, the first line of the example can
548+
contain three slashes to achieve the same effect:
549+
550+
```
551+
/**
552+
* @example
553+
* /// fixture=with-bucket
554+
* bucket.addLifecycleTransition({ ... });
555+
*/
556+
```
557+
558+
When including packages in your examples (even the package you're writing the
559+
examples for), use the full package name (e.g. `import s3 =
560+
require('@aws-cdk/aws-s3);`). The example will be compiled in an environment
561+
where all CDK packages are available using their public names. In this way,
562+
it's also possible to import packages that are not in the dependency set of
563+
the current package.
564+
565+
For a practical example of how making sample code compilable works, see the
566+
`aws-ec2` package.
567+
568+
Examples of all packages are extracted and compiled as part of the packaging
569+
step. If you are working on getting rid of example compilation errors of a
570+
single package, you can run `scripts/compile-samples` on the package by itself.
571+
572+
For now, non-compiling examples will not yet block the build, but at some point
573+
in the future they will.
574+
518575
### Feature Flags
519576

520577
Sometimes we want to introduce new breaking behavior because we believe this is
@@ -547,9 +604,9 @@ The pattern is simple:
547604
5. Under `BREAKING CHANGES` in your commit message describe this new behavior:
548605
549606
```
550-
BREAKING CHANGE: template file names for new projects created through "cdk init"
551-
will use the template artifact ID instead of the physical stack name to enable
552-
multiple stacks to use the same name. This is enabled through the flag
607+
BREAKING CHANGE: template file names for new projects created through "cdk init"
608+
will use the template artifact ID instead of the physical stack name to enable
609+
multiple stacks to use the same name. This is enabled through the flag
553610
`@aws-cdk/core:enableStackNameDuplicates` in newly generated `cdk.json` files.
554611
```
555612

pack.sh

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,31 @@ function lerna_scopes() {
2525
done
2626
}
2727

28-
echo "Packaging jsii modules" >&2
28+
# Compile examples with respect to "decdk" directory, as all packages will
29+
# be symlinked there so they can all be included.
30+
echo "Extracting code samples" >&2
31+
node --experimental-worker $(which jsii-rosetta) \
32+
--compile \
33+
--output samples.tabl.json \
34+
--directory packages/decdk \
35+
$(cat $TMPDIR/jsii.txt)
2936

3037
# Jsii packaging (all at once using jsii-pacmak)
38+
echo "Packaging jsii modules" >&2
3139
jsii-pacmak \
3240
--verbose \
33-
--outdir $distdir/ \
41+
--rosetta-tablet samples.tabl.json \
3442
$(cat $TMPDIR/jsii.txt)
3543

3644
# Non-jsii packaging, which means running 'package' in every individual
37-
# module and rsync'ing the result to the shared dist directory.
45+
# module
3846
echo "Packaging non-jsii modules" >&2
3947
lerna run $(lerna_scopes $(cat $TMPDIR/nonjsii.txt)) --sort --concurrency=1 --stream package
4048

49+
# Finally rsync all 'dist' directories together into a global 'dist' directory
4150
for dir in $(find packages -name dist | grep -v node_modules | grep -v run-wrappers); do
42-
echo "Merging ${dir} into ${distdir}"
43-
rsync -av $dir/ ${distdir}/
51+
echo "Merging ${dir} into ${distdir}" >&2
52+
rsync -a $dir/ ${distdir}/
4453
done
4554

4655
# Remove a JSII aggregate POM that may have snuk past

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"jest": "^24.9.0",
2222
"jsii-diff": "^0.20.2",
2323
"jsii-pacmak": "^0.20.2",
24+
"jsii-rosetta": "^0.20.2",
2425
"lerna": "^3.18.4",
2526
"nodeunit": "^0.11.3",
2627
"nyc": "^14.1.1",

packages/@aws-cdk/aws-ec2/README.md

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@
1212
The `@aws-cdk/aws-ec2` package contains primitives for setting up networking and
1313
instances.
1414

15+
```ts nofixture
16+
import ec2 = require('@aws-cdk/aws-ec2');
17+
```
18+
1519
## VPC
1620

1721
Most projects need a Virtual Private Cloud to provide security by means of
1822
network partitioning. This is achieved by creating an instance of
1923
`Vpc`:
2024

2125
```ts
22-
import ec2 = require('@aws-cdk/aws-ec2');
23-
2426
const vpc = new ec2.Vpc(this, 'VPC');
2527
```
2628

@@ -186,7 +188,6 @@ by setting the `reserved` subnetConfiguration property to true, as shown
186188
below:
187189

188190
```ts
189-
import ec2 = require('@aws-cdk/aws-ec2');
190191
const vpc = new ec2.Vpc(this, 'TheVPC', {
191192
natGateways: 1,
192193
subnetConfiguration: [
@@ -263,7 +264,7 @@ which you can add egress traffic rules.
263264

264265
You can manipulate Security Groups directly:
265266

266-
```ts
267+
```ts fixture=with-vpc
267268
const mySecurityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', {
268269
vpc,
269270
description: 'Allow ssh access to ec2 instances',
@@ -281,7 +282,7 @@ have security groups, you have to add an **Egress** rule to one Security Group,
281282
and an **Ingress** rule to the other. The connections object will automatically
282283
take care of this for you:
283284

284-
```ts
285+
```ts fixture=conns
285286
// Allow connections from anywhere
286287
loadBalancer.connections.allowFromAnyIpv4(ec2.Port.tcp(443), 'Allow inbound HTTPS');
287288

@@ -296,23 +297,23 @@ appFleet.connections.allowTo(dbFleet, ec2.Port.tcp(443), 'App can call database'
296297

297298
There are various classes that implement the connection peer part:
298299

299-
```ts
300+
```ts fixture=conns
300301
// Simple connection peers
301302
let peer = ec2.Peer.ipv4("10.0.0.0/16");
302-
let peer = ec2.Peer.anyIpv4();
303-
let peer = ec2.Peer.ipv6("::0/0");
304-
let peer = ec2.Peer.anyIpv6();
305-
let peer = ec2.Peer.prefixList("pl-12345");
306-
fleet.connections.allowTo(peer, ec2.Port.tcp(443), 'Allow outbound HTTPS');
303+
peer = ec2.Peer.anyIpv4();
304+
peer = ec2.Peer.ipv6("::0/0");
305+
peer = ec2.Peer.anyIpv6();
306+
peer = ec2.Peer.prefixList("pl-12345");
307+
appFleet.connections.allowTo(peer, ec2.Port.tcp(443), 'Allow outbound HTTPS');
307308
```
308309

309310
Any object that has a security group can itself be used as a connection peer:
310311

311-
```ts
312+
```ts fixture=conns
312313
// These automatically create appropriate ingress and egress rules in both security groups
313314
fleet1.connections.allowTo(fleet2, ec2.Port.tcp(80), 'Allow between fleets');
314315

315-
fleet.connections.allowFromAnyIpv4(ec2.Port.tcp(80), 'Allow from load balancer');
316+
appFleet.connections.allowFromAnyIpv4(ec2.Port.tcp(80), 'Allow from load balancer');
316317
```
317318

318319
### Port Ranges
@@ -342,12 +343,12 @@ If the object you're calling the peering method on has a default port associated
342343

343344
For example:
344345

345-
```ts
346+
```ts fixture=conns
346347
// Port implicit in listener
347348
listener.connections.allowDefaultPortFromAnyIpv4('Allow public');
348349

349350
// Port implicit in peer
350-
fleet.connections.allowDefaultPortTo(rdsDatabase, 'Fleet can access database');
351+
appFleet.connections.allowDefaultPortTo(rdsDatabase, 'Fleet can access database');
351352
```
352353

353354
## Machine Images (AMIs)
@@ -374,7 +375,7 @@ examples of things you might want to use:
374375
Create your VPC with VPN connections by specifying the `vpnConnections` props (keys are construct `id`s):
375376

376377
```ts
377-
const vpc = new ec2.Vpc(stack, 'MyVpc', {
378+
const vpc = new ec2.Vpc(this, 'MyVpc', {
378379
vpnConnections: {
379380
dynamic: { // Dynamic routing (BGP)
380381
ip: '1.2.3.4'
@@ -393,13 +394,13 @@ const vpc = new ec2.Vpc(stack, 'MyVpc', {
393394
To create a VPC that can accept VPN connections, set `vpnGateway` to `true`:
394395

395396
```ts
396-
const vpc = new ec2.Vpc(stack, 'MyVpc', {
397+
const vpc = new ec2.Vpc(this, 'MyVpc', {
397398
vpnGateway: true
398399
});
399400
```
400401

401402
VPN connections can then be added:
402-
```ts
403+
```ts fixture=with-vpc
403404
vpc.addVpnConnection('Dynamic', {
404405
ip: '1.2.3.4'
405406
});
@@ -408,9 +409,10 @@ vpc.addVpnConnection('Dynamic', {
408409
Routes will be propagated on the route tables associated with the private subnets.
409410

410411
VPN connections expose [metrics (cloudwatch.Metric)](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-cloudwatch/README.md) across all tunnels in the account/region and per connection:
411-
```ts
412+
413+
```ts fixture=with-vpc
412414
// Across all tunnels in the account/region
413-
const allDataOut = VpnConnection.metricAllTunnelDataOut();
415+
const allDataOut = ec2.VpnConnection.metricAllTunnelDataOut();
414416

415417
// For a specific vpn connection
416418
const vpnConnection = vpc.addVpnConnection('Dynamic', {
@@ -431,8 +433,9 @@ By default, interface VPC endpoints create a new security group and traffic is *
431433
automatically allowed from the VPC CIDR.
432434

433435
Use the `connections` object to allow traffic to flow to the endpoint:
434-
```ts
435-
myEndpoint.connections.allowDefaultPortFrom(...);
436+
437+
```ts fixture=conns
438+
myEndpoint.connections.allowDefaultPortFromAnyIpv4();
436439
```
437440

438441
Alternatively, existing security groups can be used by specifying the `securityGroups` prop.
@@ -443,17 +446,17 @@ You can use bastion hosts using a standard SSH connection targetting port 22 on
443446
feature of AWS Systems Manager Session Manager, which does not need an opened security group. (https://aws.amazon.com/about-aws/whats-new/2019/07/session-manager-launches-tunneling-support-for-ssh-and-scp/)
444447

445448
A default bastion host for use via SSM can be configured like:
446-
```ts
449+
```ts fixture=with-vpc
447450
const host = new ec2.BastionHostLinux(this, 'BastionHost', { vpc });
448451
```
449452

450453
If you want to connect from the internet using SSH, you need to place the host into a public subnet. You can then configure allowed source hosts.
451-
```ts
454+
```ts fixture=with-vpc
452455
const host = new ec2.BastionHostLinux(this, 'BastionHost', {
453456
vpc,
454-
subnetSelection: { subnetType: SubnetType.PUBLIC },
457+
subnetSelection: { subnetType: ec2.SubnetType.PUBLIC },
455458
});
456-
host.allowSshAccessFrom(Peer.ipv4('1.2.3.4/32'));
459+
host.allowSshAccessFrom(ec2.Peer.ipv4('1.2.3.4/32'));
457460
```
458461

459462
As there are no SSH public keys deployed on this machine, you need to use [EC2 Instance Connect](https://aws.amazon.com/de/blogs/compute/new-using-amazon-ec2-instance-connect-for-ssh-access-to-your-ec2-instances/)

packages/@aws-cdk/aws-ec2/lib/instance.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,9 @@ export interface InstanceProps {
143143
* The role must be assumable by the service principal `ec2.amazonaws.com`:
144144
*
145145
* @example
146-
*
147-
* const role = new iam.Role(this, 'MyRole', {
148-
* assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com')
149-
* });
146+
* const role = new iam.Role(this, 'MyRole', {
147+
* assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com')
148+
* });
150149
*
151150
* @default - A role will automatically be created, it can be accessed via the `role` property
152151
*/

packages/@aws-cdk/aws-ec2/lib/nat.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ export interface NatInstanceProps {
8585
* If you have a specific AMI ID you want to use, pass a `GenericLinuxImage`. For example:
8686
*
8787
* ```ts
88-
* NatProvider.instance({
89-
* instanceType: new InstanceType('t3.micro'),
90-
* machineImage: new GenericLinuxImage({
88+
* ec2.NatProvider.instance({
89+
* instanceType: new ec2.InstanceType('t3.micro'),
90+
* machineImage: new ec2.GenericLinuxImage({
9191
* 'us-east-2': 'ami-0f9c61b5a562a16af'
9292
* })
9393
* })
@@ -215,4 +215,4 @@ export class NatInstanceImage extends LookupMachineImage {
215215
owners: ['amazon'],
216216
});
217217
}
218-
}
218+
}

0 commit comments

Comments
 (0)