diff --git a/packages/@aws-cdk/app-delivery/.gitignore b/packages/@aws-cdk/app-delivery/.gitignore
new file mode 100644
index 0000000000000..c49007df54187
--- /dev/null
+++ b/packages/@aws-cdk/app-delivery/.gitignore
@@ -0,0 +1,11 @@
+dist
+.LAST_PACKAGE
+.LAST_BUILD
+.jsii
+.nyc_output
+.nycrc
+tsconfig.json
+*.js
+*.d.ts
+*.snk
+coverage
diff --git a/packages/@aws-cdk/app-delivery/.npmignore b/packages/@aws-cdk/app-delivery/.npmignore
new file mode 100644
index 0000000000000..de362b6dbebc8
--- /dev/null
+++ b/packages/@aws-cdk/app-delivery/.npmignore
@@ -0,0 +1,15 @@
+
+dist
+.LAST_PACKAGE
+.LAST_BUILD
+*.ts
+!*.d.ts
+!*.js
+coverage
+.nyc_output
+*.tgz
+*.snk
+
+
+# Include .jsii
+!.jsii
diff --git a/packages/@aws-cdk/app-delivery/LICENSE b/packages/@aws-cdk/app-delivery/LICENSE
new file mode 100644
index 0000000000000..1739faaebb745
--- /dev/null
+++ b/packages/@aws-cdk/app-delivery/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/packages/@aws-cdk/app-delivery/NOTICE b/packages/@aws-cdk/app-delivery/NOTICE
new file mode 100644
index 0000000000000..95fd48569c743
--- /dev/null
+++ b/packages/@aws-cdk/app-delivery/NOTICE
@@ -0,0 +1,2 @@
+AWS Cloud Development Kit (AWS CDK)
+Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
diff --git a/packages/@aws-cdk/app-delivery/README.md b/packages/@aws-cdk/app-delivery/README.md
new file mode 100644
index 0000000000000..f6790ea3e4302
--- /dev/null
+++ b/packages/@aws-cdk/app-delivery/README.md
@@ -0,0 +1,111 @@
+## Continuous Integration / Continuous Delivery for CDK Applications
+This library includes a *CodePipeline* action for deploying AWS CDK Applications.
+
+This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project.
+
+### Limitations
+The construct library in it's current form has the following limitations:
+1. It can only deploy stacks that are hosted in the same AWS account and region as the *CodePipeline* is.
+2. Stacks that make use of `Asset`s cannot be deployed successfully.
+
+### Getting Started
+In order to add the `PipelineDeployStackAction` to your *CodePipeline*, you need to have a *CodePipeline* artifact that
+contains the result of invoking `cdk synth -o
` on your *CDK App*. You can for example achieve this using a
+*CodeBuild* project.
+
+The example below defines a *CDK App* that contains 3 stacks:
+* `CodePipelineStack` manages the *CodePipeline* resources, and self-updates before deploying any other stack
+* `ServiceStackA` and `ServiceStackB` are service infrastructure stacks, and need to be deployed in this order
+
+```
+ ┏━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+ ┃ Source ┃ ┃ Build ┃ ┃ Self-Update ┃ ┃ Deploy ┃
+ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃
+ ┃ ┌────────────┐ ┃ ┃ ┌────────────┐ ┃ ┃ ┌─────────────┐ ┃ ┃ ┌─────────────┐ ┌─────────────┐ ┃
+ ┃ │ GitHub ┣━╋━━╋━▶ CodeBuild ┣━╋━━╋━▶Deploy Stack ┣━╋━━╋━▶Deploy Stack ┣━▶Deploy Stack │ ┃
+ ┃ │ │ ┃ ┃ │ │ ┃ ┃ │PipelineStack│ ┃ ┃ │ServiceStackA│ │ServiceStackB│ ┃
+ ┃ └────────────┘ ┃ ┃ └────────────┘ ┃ ┃ └─────────────┘ ┃ ┃ └─────────────┘ └─────────────┘ ┃
+ ┗━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+```
+
+#### `index.ts`
+```ts
+import codebuild = require('@aws-cdk/aws-codebuild');
+import codepipeline = require('@aws-cdk/aws-codepipeline');
+import cdk = require('@aws-cdk/cdk');
+import cicd = require('@aws-cdk/cicd');
+
+const app = new cdk.App();
+
+// We define a stack that contains the CodePipeline
+const pipelineStack = new cdk.Stack(app, 'PipelineStack');
+const pipeline = new codepipeline.Pipeline(pipelineStack, 'CodePipeline', {
+ // Mutating a CodePipeline can cause the currently propagating state to be
+ // "lost". Ensure we re-run the latest change through the pipeline after it's
+ // been mutated so we're sure the latest state is fully deployed through.
+ restartExecutionOnUpdate: true,
+ /* ... */
+});
+// Configure the CodePipeline source - where your CDK App's source code is hosted
+const source = new codepipeline.GitHubSourceAction(pipelineStack, 'GitHub', {
+ stage: pipeline.addStage('source'),
+ /* ... */
+});
+const project = new codebuild.PipelineProject(pipelineStack, 'CodeBuild', {
+ /* ... */
+});
+const synthesizedApp = project.outputArtifact;
+
+// Optionally, self-update the pipeline stack
+const selfUpdateStage = pipeline.addStage('SelfUpdate');
+new cicd.PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', {
+ stage: selfUpdateStage,
+ stack: pipelineStack,
+ inputArtifact: synthesizedApp,
+});
+
+// Now add our service stacks
+const deployStage = pipeline.addStage('Deploy');
+const serviceStackA = new MyServiceStackA(app, 'ServiceStackA', { /* ... */ });
+const serviceStackB = new MyServiceStackB(app, 'ServiceStackB', { /* ... */ });
+// Add actions to deploy the stacks in the deploy stage:
+new cicd.PipelineDeployStackAction(pipelineStack, 'DeployServiceStackA', {
+ stage: deployStage,
+ stack: serviceStackA,
+ inputArtifact: synthesizedApp,
+});
+new cicd.PipelineDeployStackAction(pipelineStack, 'DeployServiceStackB', {
+ stage: deployStage,
+ stack: serviceStackB,
+ inputArtifact: synthesizedApp,
+ createChangeSetRunOrder: 998,
+});
+```
+
+#### `buildspec.yml`
+The `PipelineDeployStackAction` expects it's `inputArtifact` to contain the result of synthesizing a CDK App using the
+`cdk synth -o ` command.
+
+For example, a *TypeScript* or *Javascript* CDK App can add the following `buildspec.yml` at the root of the repository
+configured in the `Source` stage:
+
+```yml
+version: 0.2
+phases:
+ install:
+ commands:
+ # Installs the npm dependencies as defined by the `package.json` file
+ # present in the root directory of the package
+ # (`cdk init app --language=typescript` would have created one for you)
+ - npm install
+ build:
+ commands:
+ # Builds the CDK App so it can be synthesized
+ - npm run build
+ # Synthesizes the CDK App and puts the resulting artifacts into `dist`
+ - npm run cdk synth -- -o dist
+artifacts:
+ # The output artifact is all the files in the `dist` directory
+ base-directory: dist
+ files: '**/*'
+```
diff --git a/packages/@aws-cdk/app-delivery/lib/index.ts b/packages/@aws-cdk/app-delivery/lib/index.ts
new file mode 100644
index 0000000000000..5d0ab4f1eb92a
--- /dev/null
+++ b/packages/@aws-cdk/app-delivery/lib/index.ts
@@ -0,0 +1 @@
+export * from './pipeline-deploy-stack-action';
diff --git a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts
new file mode 100644
index 0000000000000..8105d354771a8
--- /dev/null
+++ b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts
@@ -0,0 +1,100 @@
+
+import cfn = require('@aws-cdk/aws-cloudformation');
+import codepipeline = require('@aws-cdk/aws-codepipeline-api');
+import cdk = require('@aws-cdk/cdk');
+import cxapi = require('@aws-cdk/cx-api');
+
+export interface PipelineDeployStackActionProps {
+ /**
+ * The CDK stack to be deployed.
+ */
+ stack: cdk.Stack;
+
+ /**
+ * The CodePipeline stage in which to perform the deployment.
+ */
+ stage: codepipeline.IStage;
+
+ /**
+ * The CodePipeline artifact that holds the synthesized app, which is the
+ * contents of the ```` when running ``cdk synth -o ``.
+ */
+ inputArtifact: codepipeline.Artifact;
+
+ /**
+ * The name to use when creating a ChangeSet for the stack.
+ *
+ * @default CDK-CodePipeline-ChangeSet
+ */
+ changeSetName?: string;
+
+ /**
+ * The runOrder for the CodePipeline action creating the ChangeSet.
+ *
+ * @default 1
+ */
+ createChangeSetRunOrder?: number;
+
+ /**
+ * The runOrder for the CodePipeline action executing the ChangeSet.
+ *
+ * @default ``createChangeSetRunOrder + 1``
+ */
+ executeChangeSetRunOrder?: number;
+}
+
+/**
+ * A CodePipeline action to deploy a stack that is part of a CDK App. This
+ * action takes care of preparing and executing a CloudFormation ChangeSet.
+ *
+ * It currently does *not* support stacks that make use of ``Asset``s, and
+ * requires the deployed stack is in the same account and region where the
+ * CodePipeline is hosted.
+ */
+export class PipelineDeployStackAction extends cdk.Construct {
+ private readonly stack: cdk.Stack;
+
+ constructor(parent: cdk.Construct, id: string, props: PipelineDeployStackActionProps) {
+ super(parent, id);
+
+ if (!cdk.environmentEquals(props.stack.env, cdk.Stack.find(this).env)) {
+ // FIXME: Add the necessary to extend to stacks in a different account
+ throw new Error(`Cross-environment deployment is not supported`);
+ }
+
+ const createChangeSetRunOrder = props.createChangeSetRunOrder || 1;
+ const executeChangeSetRunOrder = props.executeChangeSetRunOrder || (createChangeSetRunOrder + 1);
+
+ if (createChangeSetRunOrder >= executeChangeSetRunOrder) {
+ throw new Error(`createChangeSetRunOrder (${createChangeSetRunOrder}) must be < executeChangeSetRunOrder (${executeChangeSetRunOrder})`);
+ }
+
+ this.stack = props.stack;
+ const changeSetName = props.changeSetName || 'CDK-CodePipeline-ChangeSet';
+
+ new cfn.PipelineCreateReplaceChangeSetAction(this, 'ChangeSet', {
+ changeSetName,
+ runOrder: createChangeSetRunOrder,
+ stackName: props.stack.name,
+ stage: props.stage,
+ templatePath: props.inputArtifact.atPath(`${props.stack.name}.template.yaml`),
+ });
+
+ new cfn.PipelineExecuteChangeSetAction(this, 'Execute', {
+ changeSetName,
+ runOrder: executeChangeSetRunOrder,
+ stackName: props.stack.name,
+ stage: props.stage,
+ });
+ }
+
+ public validate(): string[] {
+ const result = super.validate();
+ const assets = this.stack.metadata.filter(md => md.type === cxapi.ASSET_METADATA);
+ if (assets.length > 0) {
+ // FIXME: Implement the necessary actions to publish assets
+ result.push(`Cannot deploy the stack ${this.stack.name} because it references ${assets.length} asset(s)`);
+ }
+ return result;
+ }
+}
diff --git a/packages/@aws-cdk/app-delivery/package.json b/packages/@aws-cdk/app-delivery/package.json
new file mode 100644
index 0000000000000..979118b3765be
--- /dev/null
+++ b/packages/@aws-cdk/app-delivery/package.json
@@ -0,0 +1,64 @@
+{
+ "name": "@aws-cdk/app-delivery",
+ "description": "Continuous Integration / Continuous Delivery for CDK Applications",
+ "version": "0.14.1",
+ "main": "lib/index.js",
+ "types": "lib/index.d.ts",
+ "jsii": {
+ "targets": {
+ "java": {
+ "maven": {
+ "groupId": "software.amazon.awscdk",
+ "artifactId": "cdk-app-delivery"
+ },
+ "package": "software.amazon.awscdk.appdelivery"
+ },
+ "sphinx": {},
+ "dotnet": {
+ "namespace": "Amazon.CDK.AppDelivery",
+ "packageId": "Amazon.CDK.AppDelivery",
+ "signAssembly": true,
+ "assemblyOriginatorKeyFile": "../../key.snk"
+ }
+ },
+ "outdir": "dist"
+ },
+ "scripts": {
+ "build": "cdk-build",
+ "package": "cdk-package",
+ "pkglint": "pkglint -f",
+ "test": "cdk-test",
+ "watch": "cdk-watch",
+ "integ": "cdk-integ"
+ },
+ "dependencies": {
+ "@aws-cdk/aws-cloudformation": "^0.14.1",
+ "@aws-cdk/aws-codebuild": "^0.14.1",
+ "@aws-cdk/aws-codepipeline-api": "^0.14.1",
+ "@aws-cdk/cdk": "^0.14.1",
+ "@aws-cdk/cx-api": "^0.14.1"
+ },
+ "devDependencies": {
+ "@aws-cdk/aws-codepipeline": "^0.14.1",
+ "@aws-cdk/aws-s3": "^0.14.1",
+ "cdk-build-tools": "^0.14.1",
+ "cdk-integ-tools": "^0.14.1",
+ "fast-check": "^1.7.0",
+ "pkglint": "^0.14.1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/awslabs/aws-cdk.git"
+ },
+ "homepage": "https://github.com/awslabs/aws-cdk",
+ "license": "Apache-2.0",
+ "author": {
+ "name": "Amazon Web Services",
+ "url": "https://aws.amazon.com",
+ "organization": true
+ },
+ "keywords": [
+ "aws",
+ "cdk"
+ ]
+}
diff --git a/packages/@aws-cdk/app-delivery/test/integ.cicd.expected.json b/packages/@aws-cdk/app-delivery/test/integ.cicd.expected.json
new file mode 100644
index 0000000000000..e143489256939
--- /dev/null
+++ b/packages/@aws-cdk/app-delivery/test/integ.cicd.expected.json
@@ -0,0 +1,264 @@
+{
+ "Resources": {
+ "ArtifactBucket7410C9EF": {
+ "Type": "AWS::S3::Bucket"
+ },
+ "CodePipelineRoleB3A660B4": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "codepipeline.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ }
+ }
+ },
+ "CodePipelineRoleDefaultPolicy8D520A8D": {
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "s3:GetObject*",
+ "s3:GetBucket*",
+ "s3:List*",
+ "s3:DeleteObject*",
+ "s3:PutObject*",
+ "s3:Abort*"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ {
+ "Fn::GetAtt": [
+ "ArtifactBucket7410C9EF",
+ "Arn"
+ ]
+ },
+ {
+ "Fn::Join": [
+ "",
+ [
+ {
+ "Fn::GetAtt": [
+ "ArtifactBucket7410C9EF",
+ "Arn"
+ ]
+ },
+ "/*"
+ ]
+ ]
+ }
+ ]
+ },
+ {
+ "Action": "iam:PassRole",
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::GetAtt": [
+ "DeployStackChangeSetRole4923A126",
+ "Arn"
+ ]
+ }
+ },
+ {
+ "Action": [
+ "cloudformation:CreateChangeSet",
+ "cloudformation:DeleteChangeSet",
+ "cloudformation:DescribeChangeSet",
+ "cloudformation:DescribeStacks"
+ ],
+ "Condition": {
+ "StringEqualsIfExists": {
+ "cloudformation:ChangeSetName": "CICD-ChangeSet"
+ }
+ },
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":cloudformation:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":stack/CICD/*"
+ ]
+ ]
+ }
+ },
+ {
+ "Action": "cloudformation:ExecuteChangeSet",
+ "Condition": {
+ "StringEquals": {
+ "cloudformation:ChangeSetName": "CICD-ChangeSet"
+ }
+ },
+ "Effect": "Allow",
+ "Resource": {
+ "Fn::Join": [
+ "",
+ [
+ "arn:",
+ {
+ "Ref": "AWS::Partition"
+ },
+ ":cloudformation:",
+ {
+ "Ref": "AWS::Region"
+ },
+ ":",
+ {
+ "Ref": "AWS::AccountId"
+ },
+ ":stack/CICD/*"
+ ]
+ ]
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyName": "CodePipelineRoleDefaultPolicy8D520A8D",
+ "Roles": [
+ {
+ "Ref": "CodePipelineRoleB3A660B4"
+ }
+ ]
+ }
+ },
+ "CodePipelineB74E5936": {
+ "Type": "AWS::CodePipeline::Pipeline",
+ "Properties": {
+ "ArtifactStore": {
+ "Location": {
+ "Ref": "ArtifactBucket7410C9EF"
+ },
+ "Type": "S3"
+ },
+ "RoleArn": {
+ "Fn::GetAtt": [
+ "CodePipelineRoleB3A660B4",
+ "Arn"
+ ]
+ },
+ "Stages": [
+ {
+ "Actions": [
+ {
+ "ActionTypeId": {
+ "Category": "Source",
+ "Owner": "ThirdParty",
+ "Provider": "GitHub",
+ "Version": "1"
+ },
+ "Configuration": {
+ "Owner": "awslabs",
+ "Repo": "aws-cdk",
+ "Branch": "master",
+ "OAuthToken": "DummyToken",
+ "PollForSourceChanges": true
+ },
+ "InputArtifacts": [],
+ "Name": "GitHub",
+ "OutputArtifacts": [
+ {
+ "Name": "Artifact_CICDGitHubF8BA7ADD"
+ }
+ ],
+ "RunOrder": 1
+ }
+ ],
+ "Name": "Source"
+ },
+ {
+ "Actions": [
+ {
+ "ActionTypeId": {
+ "Category": "Deploy",
+ "Owner": "AWS",
+ "Provider": "CloudFormation",
+ "Version": "1"
+ },
+ "Configuration": {
+ "StackName": "CICD",
+ "ActionMode": "CHANGE_SET_REPLACE",
+ "ChangeSetName": "CICD-ChangeSet",
+ "TemplatePath": "Artifact_CICDGitHubF8BA7ADD::CICD.template.yaml",
+ "RoleArn": {
+ "Fn::GetAtt": [
+ "DeployStackChangeSetRole4923A126",
+ "Arn"
+ ]
+ }
+ },
+ "InputArtifacts": [
+ {
+ "Name": "Artifact_CICDGitHubF8BA7ADD"
+ }
+ ],
+ "Name": "ChangeSet",
+ "OutputArtifacts": [],
+ "RunOrder": 10
+ },
+ {
+ "ActionTypeId": {
+ "Category": "Deploy",
+ "Owner": "AWS",
+ "Provider": "CloudFormation",
+ "Version": "1"
+ },
+ "Configuration": {
+ "StackName": "CICD",
+ "ActionMode": "CHANGE_SET_EXECUTE",
+ "ChangeSetName": "CICD-ChangeSet"
+ },
+ "InputArtifacts": [],
+ "Name": "Execute",
+ "OutputArtifacts": [],
+ "RunOrder": 999
+ }
+ ],
+ "Name": "Deploy"
+ }
+ ]
+ },
+ "DependsOn": [
+ "CodePipelineRoleB3A660B4",
+ "CodePipelineRoleDefaultPolicy8D520A8D"
+ ]
+ },
+ "DeployStackChangeSetRole4923A126": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "cloudformation.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/app-delivery/test/integ.cicd.ts b/packages/@aws-cdk/app-delivery/test/integ.cicd.ts
new file mode 100644
index 0000000000000..bf6948588f17e
--- /dev/null
+++ b/packages/@aws-cdk/app-delivery/test/integ.cicd.ts
@@ -0,0 +1,27 @@
+import code = require('@aws-cdk/aws-codepipeline');
+import s3 = require('@aws-cdk/aws-s3');
+import cdk = require('@aws-cdk/cdk');
+import cicd = require('../lib');
+
+const app = new cdk.App();
+
+const stack = new cdk.Stack(app, 'CICD');
+const pipeline = new code.Pipeline(stack, 'CodePipeline', {
+ artifactBucket: new s3.Bucket(stack, 'ArtifactBucket'),
+});
+const source = new code.GitHubSourceAction(stack, 'GitHub', {
+ stage: pipeline.addStage('Source'),
+ owner: 'awslabs',
+ repo: 'aws-cdk',
+ oauthToken: new cdk.Secret('DummyToken'),
+});
+new cicd.PipelineDeployStackAction(stack, 'DeployStack', {
+ stage: pipeline.addStage('Deploy'),
+ stack,
+ changeSetName: 'CICD-ChangeSet',
+ createChangeSetRunOrder: 10,
+ executeChangeSetRunOrder: 999,
+ inputArtifact: source.outputArtifact,
+});
+
+app.run();
diff --git a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts
new file mode 100644
index 0000000000000..5357783f50052
--- /dev/null
+++ b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts
@@ -0,0 +1,103 @@
+import code = require('@aws-cdk/aws-codepipeline');
+import api = require('@aws-cdk/aws-codepipeline-api');
+import cdk = require('@aws-cdk/cdk');
+import cxapi = require('@aws-cdk/cx-api');
+import fc = require('fast-check');
+import nodeunit = require('nodeunit');
+import { PipelineDeployStackAction } from '../lib/pipeline-deploy-stack-action';
+
+const accountId = fc.array(fc.integer(0, 9), 12, 12).map(arr => arr.join());
+
+export = nodeunit.testCase({
+ 'rejects cross-environment deployment'(test: nodeunit.Test) {
+ fc.assert(
+ fc.property(
+ accountId, accountId,
+ (pipelineAccount, stackAccount) => {
+ fc.pre(pipelineAccount !== stackAccount);
+ test.throws(() => {
+ const app = new cdk.App();
+ const stack = new cdk.Stack(app, 'Test', { env: { account: pipelineAccount } });
+ const pipeline = new code.Pipeline(stack, 'Pipeline');
+ const fakeAction = new FakeAction(stack, 'Fake', pipeline);
+ new PipelineDeployStackAction(stack, 'Action', {
+ changeSetName: 'ChangeSet',
+ inputArtifact: fakeAction.outputArtifact,
+ stack: new cdk.Stack(app, 'DeployedStack', { env: { account: stackAccount } }),
+ stage: pipeline.addStage('DeployStage'),
+ });
+ }, 'Cross-environment deployment is not supported');
+ }
+ )
+ );
+ test.done();
+ },
+
+ 'rejects createRunOrder >= executeRunOrder'(test: nodeunit.Test) {
+ fc.assert(
+ fc.property(
+ fc.integer(1, 999), fc.integer(1, 999),
+ (createRunOrder, executeRunOrder) => {
+ fc.pre(createRunOrder >= executeRunOrder);
+ test.throws(() => {
+ const app = new cdk.App();
+ const stack = new cdk.Stack(app, 'Test');
+ const pipeline = new code.Pipeline(stack, 'Pipeline');
+ const fakeAction = new FakeAction(stack, 'Fake', pipeline);
+ new PipelineDeployStackAction(stack, 'Action', {
+ changeSetName: 'ChangeSet',
+ createChangeSetRunOrder: createRunOrder,
+ executeChangeSetRunOrder: executeRunOrder,
+ inputArtifact: fakeAction.outputArtifact,
+ stack: new cdk.Stack(app, 'DeployedStack'),
+ stage: pipeline.addStage('DeployStage'),
+ });
+ }, 'createChangeSetRunOrder must be < executeChangeSetRunOrder');
+ }
+ )
+ );
+ test.done();
+ },
+
+ 'rejects stacks with assets'(test: nodeunit.Test) {
+ fc.assert(
+ fc.property(
+ fc.integer(1, 5),
+ (assetCount) => {
+ const app = new cdk.App();
+ const stack = new cdk.Stack(app, 'Test');
+ const pipeline = new code.Pipeline(stack, 'Pipeline');
+ const fakeAction = new FakeAction(stack, 'Fake', pipeline);
+ const deployedStack = new cdk.Stack(app, 'DeployedStack');
+ const action = new PipelineDeployStackAction(stack, 'Action', {
+ changeSetName: 'ChangeSet',
+ inputArtifact: fakeAction.outputArtifact,
+ stack: deployedStack,
+ stage: pipeline.addStage('DeployStage'),
+ });
+ for (let i = 0 ; i < assetCount ; i++) {
+ deployedStack.addMetadata(cxapi.ASSET_METADATA, {});
+ }
+ test.deepEqual(action.validate(),
+ [`Cannot deploy the stack DeployedStack because it references ${assetCount} asset(s)`]);
+ }
+ )
+ );
+ test.done();
+ }
+});
+
+class FakeAction extends api.Action {
+ public readonly outputArtifact: api.Artifact;
+
+ constructor(parent: cdk.Construct, id: string, pipeline: code.Pipeline) {
+ super(parent, id, {
+ artifactBounds: api.defaultBounds(),
+ category: api.ActionCategory.Test,
+ provider: 'Test',
+ stage: pipeline.addStage('FakeStage'),
+ });
+
+ this.outputArtifact = new api.Artifact(this, 'OutputArtifact');
+ }
+}
diff --git a/packages/@aws-cdk/cdk/lib/environment.ts b/packages/@aws-cdk/cdk/lib/environment.ts
index 2bad88f641cce..e3e0cd76eca21 100644
--- a/packages/@aws-cdk/cdk/lib/environment.ts
+++ b/packages/@aws-cdk/cdk/lib/environment.ts
@@ -14,3 +14,13 @@ export interface Environment {
*/
region?: string;
}
+
+/**
+ * Checks whether two environments are equal.
+ * @param left one of the environments to compare.
+ * @param right the other environment.
+ * @returns ``true`` if both environments are guaranteed to be in the same account and region.
+ */
+export function environmentEquals(left: Environment, right: Environment): boolean {
+ return left.account === right.account && left.region === right.region;
+}