diff --git a/packages/@aws-cdk/aws-certificatemanager/README.md b/packages/@aws-cdk/aws-certificatemanager/README.md index 2356395be099d..a6f669167f261 100644 --- a/packages/@aws-cdk/aws-certificatemanager/README.md +++ b/packages/@aws-cdk/aws-certificatemanager/README.md @@ -37,7 +37,7 @@ const certificate = new Certificate(this, 'Certificate', { ### Importing -Import a certificate either manually, if you know the ARN: +Import a certificate manually, if you know the ARN: ```ts const certificate = Certificate.import(this, 'Certificate', { @@ -45,13 +45,13 @@ const certificate = Certificate.import(this, 'Certificate', { }); ``` -Or use exporting and importing mechanisms between stacks: +### Sharing between Stacks -```ts -const certRef = certStack.certificate.export(); +To share the certificate between stacks in the same CDK application, simply +pass the `Certificate` object between the stacks. -const certificate = Certificate.import(this, 'Certificate', certRef); -``` -> We should probably also make a Custom Resource that can looks up the certificate ARN -> by domain name by querying ACM. +## TODO + +- [ ] Custom Resource that can looks up the certificate ARN by domain name by querying ACM. +- [ ] Custom Resource to automate certificate validation through Route53. diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 7633dd72d859c..579c899b307a2 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -172,13 +172,15 @@ The `VpcNetwork` above will have the exact same subnet definitions as listed above. However, this time the VPC will have only 1 NAT Gateway and all Application subnets will route to the NAT Gateway. -#### Sharing VPCs across stacks +#### Sharing VPCs between stacks If you are creating multiple `Stack`s inside the same CDK application, you can reuse a VPC defined in one Stack in another by using `export()` and `import()`: -[sharing VPCs between stacks](test/example.share-vpcs.lit.ts) +[sharing VPCs between stacks](test/integ.share-vpcs.lit.ts) + +#### Importing an existing VPC If your VPC is created outside your CDK app, you can use `importFromContext()`: diff --git a/packages/@aws-cdk/aws-ec2/test/integ.share-vpcs.lit.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.share-vpcs.lit.expected.json new file mode 100644 index 0000000000000..7d4965de66ccb --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.share-vpcs.lit.expected.json @@ -0,0 +1,503 @@ +[ + { + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet3Subnet631C5E25": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PublicSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VPCPublicSubnet3RouteTable98AE0E14": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3RouteTableAssociation427FE0C6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + } + } + }, + "VPCPublicSubnet3DefaultRouteA0D29D46": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet3EIPAD4BC883": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VPCPublicSubnet3NATGatewayD3048F5C": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet3EIPAD4BC883", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCPrivateSubnet3Subnet3EDCD457": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PrivateSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VPCPrivateSubnet3RouteTable192186F8": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC/PrivateSubnet3" + } + ] + } + }, + "VPCPrivateSubnet3RouteTableAssociationC28D144E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + } + }, + "VPCPrivateSubnet3DefaultRoute27F311AE": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet3NATGatewayD3048F5C" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "Stack1/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + } + } + }, + {} +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/example.share-vpcs.lit.ts b/packages/@aws-cdk/aws-ec2/test/integ.share-vpcs.lit.ts similarity index 69% rename from packages/@aws-cdk/aws-ec2/test/example.share-vpcs.lit.ts rename to packages/@aws-cdk/aws-ec2/test/integ.share-vpcs.lit.ts index 5f2d2b20f08f3..b64036ce05013 100644 --- a/packages/@aws-cdk/aws-ec2/test/example.share-vpcs.lit.ts +++ b/packages/@aws-cdk/aws-ec2/test/integ.share-vpcs.lit.ts @@ -14,39 +14,40 @@ class ConstructThatTakesAVpc extends cdk.Construct { } /// !show +/** + * Stack1 creates the VPC + */ class Stack1 extends cdk.Stack { - public readonly vpcProps: ec2.VpcNetworkImportProps; + public readonly vpc: ec2.VpcNetwork; constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); - const vpc = new ec2.VpcNetwork(this, 'VPC'); - - // Export the VPC to a set of properties - this.vpcProps = vpc.export(); + this.vpc = new ec2.VpcNetwork(this, 'VPC'); } } interface Stack2Props extends cdk.StackProps { - vpcProps: ec2.VpcNetworkImportProps; + vpc: ec2.IVpcNetwork; } +/** + * Stack2 consumes the VPC + */ class Stack2 extends cdk.Stack { constructor(scope: cdk.App, id: string, props: Stack2Props) { super(scope, id, props); - // Import the VPC from a set of properties - const vpc = ec2.VpcNetwork.import(this, 'VPC', props.vpcProps); - + // Pass the VPC to a construct that needs it new ConstructThatTakesAVpc(this, 'Construct', { - vpc + vpc: props.vpc }); } } const stack1 = new Stack1(app, 'Stack1'); const stack2 = new Stack2(app, 'Stack2', { - vpcProps: stack1.vpcProps + vpc: stack1.vpc, }); /// !hide diff --git a/packages/@aws-cdk/aws-kms/README.md b/packages/@aws-cdk/aws-kms/README.md index d73c00dc2bfeb..e4e931155218e 100644 --- a/packages/@aws-cdk/aws-kms/README.md +++ b/packages/@aws-cdk/aws-kms/README.md @@ -16,30 +16,26 @@ key.addAlias('alias/foo'); key.addAlias('alias/bar'); ``` -### Importing and exporting keys +### Sharing keys between stacks -To use a KMS key that is not defined within this stack, use the -`EncryptionKey.import(parent, name, ref)` factory method: +To use a KMS key in a different stack in the same CDK application, +pass the construct to the other stack: -```ts -const key = EncryptionKey.import(this, 'MyImportedKey', { - keyArn: new KeyArn('arn:aws:...') -}); +[sharing key between stacks](test/integ.key-sharing.lit.ts) -// you can do stuff with this imported key. -key.addAlias('alias/foo'); -``` -To export a key from a stack and import it in another stack, use `key.export` -which returns an `EncryptionKeyRef`, which can later be used to import: +### Importing existing keys + +To use a KMS key that is not defined in this CDK app, but is created through other means, use +`EncryptionKey.import(parent, name, ref)`: ```ts -// in stackA -const myKey = new EncryptionKey(stackA, 'MyKey'); -const myKeyRef = myKey.export(); +const myKeyImported = EncryptionKey.import(this, 'MyImportedKey', { + keyArn: 'arn:aws:...' +}); -// meanwhile in stackB -const myKeyImported = EncryptionKey.import(stackB, 'MyKeyImported', myKeyRef); +// you can do stuff with this imported key. +key.addAlias('alias/foo'); ``` Note that a call to `.addToPolicy(statement)` on `myKeyImported` will not have diff --git a/packages/@aws-cdk/aws-kms/test/integ.key-sharing.lit.expected.json b/packages/@aws-cdk/aws-kms/test/integ.key-sharing.lit.expected.json new file mode 100644 index 0000000000000..6f327ba4ab49f --- /dev/null +++ b/packages/@aws-cdk/aws-kms/test/integ.key-sharing.lit.expected.json @@ -0,0 +1,66 @@ +[ + { + "Resources": { + "MyKey6AB29FA6": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "DeletionPolicy": "Retain" + }, + "MyKeyAlias1B45D9DA": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/foo", + "TargetKeyId": { + "Fn::GetAtt": [ + "MyKey6AB29FA6", + "Arn" + ] + } + } + } + } + }, + {} +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kms/test/integ.key-sharing.lit.ts b/packages/@aws-cdk/aws-kms/test/integ.key-sharing.lit.ts new file mode 100644 index 0000000000000..de46032822e13 --- /dev/null +++ b/packages/@aws-cdk/aws-kms/test/integ.key-sharing.lit.ts @@ -0,0 +1,40 @@ +import cdk = require('@aws-cdk/cdk'); +import kms = require('../lib'); + +const app = new cdk.App(); + +/// !show + +/** + * Stack that defines the key + */ +class KeyStack extends cdk.Stack { + public readonly key: kms.EncryptionKey; + + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + this.key = new kms.EncryptionKey(this, 'MyKey'); + } +} + +interface UseStackProps extends cdk.StackProps { + key: kms.IEncryptionKey; // Use IEncryptionKey here +} + +/** + * Stack that uses the key + */ +class UseStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props: UseStackProps) { + super(scope, id, props); + + // Use the IEncryptionKey object here. + props.key.addAlias('alias/foo'); + } +} + +const keyStack = new KeyStack(app, 'KeyStack'); +new UseStack(app, 'UseStack', { key: keyStack.key }); +/// !hide + +app.run(); diff --git a/packages/@aws-cdk/aws-s3/README.md b/packages/@aws-cdk/aws-s3/README.md index 667b29e1c7444..c70f58d600df9 100644 --- a/packages/@aws-cdk/aws-s3/README.md +++ b/packages/@aws-cdk/aws-s3/README.md @@ -112,77 +112,26 @@ const sourceAction = sourceBucket.addToPipeline(sourceStage, 'S3Source', { }); ``` -### Importing and Exporting Buckets +### Sharing buckets between stacks -You can create a `Bucket` construct that represents an external/existing/unowned bucket by using the `Bucket.import` factory method. +To use a bucket in a different stack in the same CDK application, pass the object to the other stack: -This method accepts an object that adheres to `BucketRef` which basically include tokens to bucket's attributes. +[sharing bucket between stacks](test/integ.bucket-sharing.lit.ts) -This means that you can define a `BucketRef` using token literals: +### Importing existing buckets + +To import an existing bucket into your CDK application, use the `Bucket.import` factory method. This method accepts a +`BucketImportProps` which describes the properties of the already existing bucket: ```ts const bucket = Bucket.import(this, { - bucketArn: new BucketArn('arn:aws:s3:::my-bucket') + bucketArn: 'arn:aws:s3:::my-bucket' }); // now you can just call methods on the bucket bucket.grantReadWrite(user); ``` -The `bucket.export()` method can be used to "export" the bucket from the current stack. It returns a `BucketRef` object that can later be used in a call to `Bucket.import` in another stack. - -Here's an example. - -Let's define a stack with an S3 bucket and export it using `bucket.export()`. - -```ts -class Producer extends Stack { - public readonly myBucketRef: BucketRef; - - constructor(parent: App, name: string) { - super(parent, name); - - const bucket = new Bucket(this, 'MyBucket'); - this.myBucketRef = bucket.export(); - } -} -``` - -Now let's define a stack that requires a BucketRef as an input and uses -`Bucket.import` to create a `Bucket` object that represents this external -bucket. Grant a user principal created within this consuming stack read/write -permissions to this bucket and contents. - -```ts -interface ConsumerProps { - public userBucketRef: BucketRef; -} - -class Consumer extends Stack { - constructor(parent: App, name: string, props: ConsumerProps) { - super(parent, name); - - const user = new User(this, 'MyUser'); - const userBucket = Bucket.import(this, props.userBucketRef); - userBucket.grantReadWrite(user); - } -} -``` - -Now, let's define our CDK app to bind these together: - -```ts -const app = new App(); - -const producer = new Producer(app, 'produce'); - -new Consumer(app, 'consume', { - userBucketRef: producer.myBucketRef -}); - -app.run(); -``` - ### Bucket Notifications The Amazon S3 notification feature enables you to receive notifications when diff --git a/packages/@aws-cdk/aws-s3/test/integ.bucket-sharing.lit.expected.json b/packages/@aws-cdk/aws-s3/test/integ.bucket-sharing.lit.expected.json new file mode 100644 index 0000000000000..833bb0ef0c07b --- /dev/null +++ b/packages/@aws-cdk/aws-s3/test/integ.bucket-sharing.lit.expected.json @@ -0,0 +1,72 @@ +[ + { + "Resources": { + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket" + } + }, + "Outputs": { + "ExportsOutputFnGetAttMyBucketF68F3FF0Arn0F7E8E58": { + "Value": { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "Export": { + "Name": "ProducerStack:ExportsOutputFnGetAttMyBucketF68F3FF0Arn0F7E8E58" + } + } + } + }, + { + "Resources": { + "MyUserDC45028B": { + "Type": "AWS::IAM::User" + }, + "MyUserDefaultPolicy7B897426": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::ImportValue": "ProducerStack:ExportsOutputFnGetAttMyBucketF68F3FF0Arn0F7E8E58" + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::ImportValue": "ProducerStack:ExportsOutputFnGetAttMyBucketF68F3FF0Arn0F7E8E58" + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyUserDefaultPolicy7B897426", + "Users": [ + { + "Ref": "MyUserDC45028B" + } + ] + } + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/test/integ.bucket-sharing.lit.ts b/packages/@aws-cdk/aws-s3/test/integ.bucket-sharing.lit.ts new file mode 100644 index 0000000000000..bb2d807cf55ec --- /dev/null +++ b/packages/@aws-cdk/aws-s3/test/integ.bucket-sharing.lit.ts @@ -0,0 +1,45 @@ +import iam = require('@aws-cdk/aws-iam'); +import cdk = require('@aws-cdk/cdk'); +import s3 = require('../lib'); + +const app = new cdk.App(); + +/// !show + +/** + * Stack that defines the bucket + */ +class Producer extends cdk.Stack { + public readonly myBucket: s3.Bucket; + + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const bucket = new s3.Bucket(this, 'MyBucket', { + removalPolicy: cdk.RemovalPolicy.Destroy + }); + this.myBucket = bucket; + } +} + +interface ConsumerProps extends cdk.StackProps { + userBucket: s3.IBucket; +} + +/** + * Stack that consumes the bucket + */ +class Consumer extends cdk.Stack { + constructor(scope: cdk.App, id: string, props: ConsumerProps) { + super(scope, id, props); + + const user = new iam.User(this, 'MyUser'); + props.userBucket.grantReadWrite(user); + } +} + +const producer = new Producer(app, 'ProducerStack'); +new Consumer(app, 'ConsumerStack', { userBucket: producer.myBucket }); +/// !hide + +app.run(); \ No newline at end of file diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index e869181ad8f51..59bae9d445759 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -56,7 +56,7 @@ async function parseCommandLineArguments() { .option('exclusively', { type: 'boolean', alias: 'e', desc: 'only deploy requested stacks, don\'t include dependencies' }) .option('interactive', { type: 'boolean', alias: 'i', desc: 'interactively watch and show template updates' }) .option('output', { type: 'string', alias: 'o', desc: 'write CloudFormation template for requested stacks to the given directory' }) - .option('numbered', { type: 'boolean', alias: 'n', desc: 'Prefix filenames with numbers to indicate deployment ordering' })) + .option('numbered', { type: 'boolean', alias: 'n', desc: 'prefix filenames with numbers to indicate deployment ordering' })) .command('bootstrap [ENVIRONMENTS..]', 'Deploys the CDK toolkit stack into an AWS environment') .command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', yargs => yargs .option('exclusively', { type: 'boolean', alias: 'e', desc: 'only deploy requested stacks, don\'t include dependencies' }) @@ -246,7 +246,7 @@ async function initCommandLine() { doInteractive: boolean, outputDir: string|undefined, json: boolean, - numbered: boolean): Promise { + numbered: boolean): Promise { const stacks = await appStacks.selectStacks(stackNames, exclusively ? ExtendedStackSelection.None : ExtendedStackSelection.Upstream); renames.validateSelectedStacks(stacks); @@ -257,13 +257,27 @@ async function initCommandLine() { return await interactive(stacks[0], argv.verbose, (stack) => appStacks.synthesizeStack(stack)); } - if (stacks.length > 1 && outputDir == null) { + // This is a slight hack; in integ mode we allow multiple stacks to be synthesized to stdout sequentially. + // This is to make it so that we can support multi-stack integ test expectations, without so drastically + // having to change the synthesis format that we have to rerun all integ tests. + // + // Because this feature is not useful to consumers (the output is missing + // the stack names), it's not exposed as a CLI flag. Instead, it's hidden + // behind an environment variable. + const isIntegMode = process.env.CDK_INTEG_MODE === '1'; + + if (stacks.length > 1 && outputDir == null && !isIntegMode) { // tslint:disable-next-line:max-line-length throw new Error(`Multiple stacks selected (${listStackNames(stacks)}), but output is directed to stdout. Either select one stack, or use --output to send templates to a directory.`); } if (outputDir == null) { - return stacks[0].template; // Will be printed in main() + // What we return here will be printed in 'main' + if (stacks.length > 1) { + // Only possible in integ mode + return stacks.map(s => s.template); + } + return stacks[0].template; } fs.mkdirpSync(outputDir); diff --git a/tools/cdk-integ-tools/lib/integ-helpers.ts b/tools/cdk-integ-tools/lib/integ-helpers.ts index 741d5cc253985..19b3bcbd41132 100644 --- a/tools/cdk-integ-tools/lib/integ-helpers.ts +++ b/tools/cdk-integ-tools/lib/integ-helpers.ts @@ -84,7 +84,7 @@ export class IntegrationTest { return exec([cdk, '-a', `node ${this.name}`].concat(args), { cwd: this.directory, json: options.json, - verbose: options.verbose + verbose: options.verbose, }); } finally { this.deleteCdkConfig(); @@ -137,6 +137,10 @@ export const STATIC_TEST_CONTEXT = { function exec(commandLine: string[], options: { cwd?: string, json?: boolean, verbose?: boolean} = { }): any { const proc = spawnSync(commandLine[0], commandLine.slice(1), { stdio: [ 'ignore', 'pipe', options.verbose ? 'inherit' : 'pipe' ], // inherit STDERR in verbose mode + env: { + ...process.env, + CDK_INTEG_MODE: '1' + }, cwd: options.cwd });