From 467901a917b5f531769580ae3d7be022369e290d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=92=85=20=F0=9F=92=83=20=F0=9F=8C=88=20Miley?= Date: Sun, 5 Aug 2018 14:52:12 -0700 Subject: [PATCH 1/7] Add support for CloudFormation actions in CodePipeline --- Author's note: This work mostly belongs to Max Hall (@hallmaxw) It was at some point commented out during a refactor - this is just adding it back. Most of the credit should go to him. --- Anywho - this adds the ability for customers to use CloudForamtion actions in CodePipeline. This follows the same pattern as previous CodePipeline actions by making it it's own package. --- .../.gitignore | 15 ++ .../.npmignore | 11 + .../aws-cloudformation-codepipeline/LICENSE | 201 +++++++++++++++ .../aws-cloudformation-codepipeline/NOTICE | 2 + .../aws-cloudformation-codepipeline/README.md | 50 ++++ .../lib/index.ts | 1 + .../lib/pipeline-action.ts | 231 ++++++++++++++++++ .../package.json | 68 ++++++ .../test/integ.pipeline.expected.json | 211 ++++++++++++++++ .../test/integ.pipeline.ts | 42 ++++ .../test/test.pipeline-action.ts | 193 +++++++++++++++ .../tsconfig.json | 27 ++ .../tslint.json | 38 +++ .../@aws-cdk/aws-codepipeline/lib/actions.ts | 20 +- .../lib/cloudformation-actions.ts | 186 -------------- 15 files changed, 1100 insertions(+), 196 deletions(-) create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/.gitignore create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/.npmignore create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/LICENSE create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/NOTICE create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/README.md create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/lib/index.ts create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-action.ts create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/package.json create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.expected.json create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.ts create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-action.ts create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/tsconfig.json create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/tslint.json delete mode 100644 packages/@aws-cdk/aws-codepipeline/lib/cloudformation-actions.ts diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/.gitignore b/packages/@aws-cdk/aws-cloudformation-codepipeline/.gitignore new file mode 100644 index 0000000000000..acfc6e27248fe --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/.gitignore @@ -0,0 +1,15 @@ +*.js +tsconfig.json +tslint.json +*.js.map +*.d.ts +*.generated.ts +dist +lib/generated/resources.ts +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/.npmignore b/packages/@aws-cdk/aws-cloudformation-codepipeline/.npmignore new file mode 100644 index 0000000000000..f536f042b8cb6 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/.npmignore @@ -0,0 +1,11 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/LICENSE b/packages/@aws-cdk/aws-cloudformation-codepipeline/LICENSE new file mode 100644 index 0000000000000..1739faaebb745 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/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/aws-cloudformation-codepipeline/NOTICE b/packages/@aws-cdk/aws-cloudformation-codepipeline/NOTICE new file mode 100644 index 0000000000000..95fd48569c743 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/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/aws-cloudformation-codepipeline/README.md b/packages/@aws-cdk/aws-cloudformation-codepipeline/README.md new file mode 100644 index 0000000000000..8d24a652f7d35 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/README.md @@ -0,0 +1,50 @@ +## AWS CodePipline Actions for AWS CloudFormation + +This module contains an Action that allows you to use CloudFormation actions from CodePipeline. + +Example usage: + +```ts +import { Repository } from '@aws-cdk/aws-codecommit'; +import { PipelineSource } from '@aws-cdk/aws-codecommit-codepipeline'; +import { ArtifactPath, Pipeline, Stage } from '@aws-cdk/aws-codepipeline'; +import { Role } from '@aws-cdk/aws-iam'; +import { PolicyStatement, ServicePrincipal } from '@aws-cdk/cdk'; +import { CreateReplaceChangeSet, ExecuteChangeSet } from '../lib/pipeline-action'; + +const pipeline = new Pipeline(stack, 'DeploymentPipeline'); + +const changeSetExecRole = new Role(stack, 'ChangeSetRole', { + assumedBy: new ServicePrincipal('cloudformation.amazonaws.com'), +}); + +/** Source! */ +const repo = new Repository(stack, 'MyVeryImportantRepo', { repositoryName: 'my-very-important-repo' }); + +const sourceStage = new Stage(pipeline, 'source'); + +const source = new PipelineSource(sourceStage, 'source', { + artifactName: 'SourceArtifact', + repository: repo, +}); + +/** Deploy! */ +const prodStage = new Stage(pipeline, 'prod'); +const stackName = 'AStack'; +const changeSetName = 'AChangeSet'; + +new CreateReplaceChangeSet(prodStage, 'MakeChangeSetProd', { + stackName, + changeSetName, + roleArn: changeSetExecRole.roleArn, + templatePath: new ArtifactPath(source.artifact, 'some_template.yaml'), +}); + +new ExecuteChangeSet(prodStage, 'ExecuteChangeSetProd', { + stackName, + changeSetName, +}); +``` + +See [the AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline.html) +for more details about using CloudFormation in CodePipeline. diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/index.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/index.ts new file mode 100644 index 0000000000000..890162998e10d --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/index.ts @@ -0,0 +1 @@ +export * from './pipeline-action'; diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-action.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-action.ts new file mode 100644 index 0000000000000..0be86928ef158 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-action.ts @@ -0,0 +1,231 @@ +import { ArtifactPath, DeployAction, Stage, } from '@aws-cdk/aws-codepipeline'; +import { RoleArn } from '@aws-cdk/aws-iam'; + +export enum CloudFormationCapabilities { + IAM = 'CAPABILITY_IAM', + NamedIAM = 'CAPABILITY_NAMED_IAM' +} + +export interface CloudFormationCommonOptions { + /** + * For stacks that contain certain resources, explicit acknowledgement that AWS CloudFormation + * might create or update those resources. For example, you must specify CAPABILITY_IAM if your + * stack template contains AWS Identity and Access Management (IAM) resources. For more + * information, see Acknowledging IAM Resources in AWS CloudFormation Templates. + */ + capabilities?: CloudFormationCapabilities[]; + + /** + * A name for the output file, such as CreateStackOutput.json. AWS CodePipeline adds the file to + * the output artifact after performing the specified action. + */ + outputFileName?: string; + + /** + * A JSON object that specifies values for template parameters. If you specify parameters that + * are also specified in the template configuration file, these values override them. All + * parameter names must be present in the stack template. + * + * Note: There is a maximum size limit of 1 kilobyte for the JSON object that can be stored in + * the ParameterOverrides property. + * + * We recommend that you use the template configuration file to specify most of your parameter + * values. Use parameter overrides to specify only dynamic parameter values (values that are + * unknown until you run the pipeline). + */ + parameterOverrides?: { [name: string]: any }; + + /** + * The template configuration file can contain template parameter values and a stack policy. + * Note that if you include sensitive information, such as passwords, restrict access to this + * file. For more information, see AWS CloudFormation Artifacts. + */ + templateConfiguration?: ArtifactPath; +} + +export abstract class CloudFormationAction extends DeployAction { + constructor(parent: Stage, name: string, configuration?: any) { + super(parent, name, 'CloudFormation', { minInputs: 0, maxInputs: 10, minOutputs: 0, maxOutputs: 1 }, configuration); + } +} + +export interface ExecuteChangeSetOptions extends CloudFormationCommonOptions { + + /** + * The stack name to execute a change set against. + */ + stackName: string; + + /** + * The name of an existing change set or a new change set that you want to create for the + * specified stack. + */ + changeSetName: string; +} + +/** + * Executes a change set. + */ +export class ExecuteChangeSet extends CloudFormationAction { + constructor(parent: Stage, name: string, options: ExecuteChangeSetOptions) { + super(parent, name, { + ActionMode: 'CHANGE_SET_EXECUTE', + Capabilities: options.capabilities, + ChangeSetName: options.changeSetName, + OutputFileName: options.outputFileName, + }); + } +} + +export interface CreateReplaceChangeSetOptions extends CloudFormationCommonOptions { + + /** + * The stack name to create/replace a change set for. + */ + stackName: string; + + /** + * The name of an existing change set or a new change set that you want to create for the + * specified stack. + */ + changeSetName: string; + + /** + * The arn of the IAM service role that AWS CloudFormation assumes + * when it operates on resources in the specified stack. + */ + roleArn: RoleArn; + + /** + * The path to the template to pass to CloudFormation during the change set operation. + */ + templatePath: ArtifactPath; +} + +/** + * Creates the change set if it doesn't exist based on the stack name and template that you submit. + * If the change set exists, AWS CloudFormation deletes it, and then creates a new one. + */ +export class CreateReplaceChangeSet extends CloudFormationAction { + constructor(parent: Stage, name: string, options: CreateReplaceChangeSetOptions) { + super(parent, name, { + ActionMode: 'CHANGE_SET_REPLACE', + Capabilities: options.capabilities, + ChangeSetName: options.changeSetName, + OutputFileName: options.outputFileName, + ParameterOverrides: options.parameterOverrides, + RoleArn: options.roleArn, + StackName: options.stackName, + TemplateConfiguration: options.templateConfiguration, + TemplatePath: options.templatePath.location, + }); + } +} + +export interface CreateUpdateOptions extends CloudFormationCommonOptions { + /** + * The arn of the IAM service role that AWS CloudFormation assumes + * when it operates on resources in the specified stack. + */ + roleArn: RoleArn; + + /** + * The CloudFormation stack name to create or update + */ + stackName: string; + + /** + * The path to the CloudFormation template file (relative to an input artifact) + */ + templatePath: ArtifactPath; +} + +/** + * Creates the stack if the specified stack doesn't exist. If the stack exists, AWS CloudFormation + * updates the stack. Use this action to update existing stacks. AWS CodePipeline won't replace the + * stack. + */ +export class CreateUpdate extends CloudFormationAction { + constructor(parent: Stage, name: string, options: CreateUpdateOptions) { + super(parent, name, { + ActionMode: 'CREATE_UPDATE', + Capabilities: options.capabilities, + OutputFileName: options.outputFileName, + ParameterOverrides: options.parameterOverrides, + RoleArn: options.roleArn, + StackName: options.stackName, + TemplateConfiguration: options.templateConfiguration, + TemplatePath: options.templatePath.location + }); + } +} + +export interface DeleteOnlyOptions extends CloudFormationCommonOptions { + /** + * The arn of the IAM service role that AWS CloudFormation assumes + * when it operates on resources in the specified stack. + */ + roleArn: RoleArn; + + /** + * The CloudFormation stack name to delete + */ + stackName: string; +} + +/** + * Deletes a stack. If you specify a stack that doesn't exist, the action completes successfully + * without deleting a stack. + */ +export class DeleteOnly extends CloudFormationAction { + constructor(parent: Stage, name: string, options: DeleteOnlyOptions) { + super(parent, name, { + ActionMode: 'DELETE_ONLY', + Capabilities: options.capabilities, + OutputFileName: options.outputFileName, + RoleArn: options.roleArn, + StackName: options.stackName, + }); + } +} + +export interface ReplaceOnFailureOptions extends CloudFormationCommonOptions { + /** + * The arn of the IAM service role that AWS CloudFormation assumes + * when it operates on resources in the specified stack. + */ + roleArn: RoleArn; + + /** + * The CloudFormation stack name to replace/create + */ + stackName: string; + + /** + * The path to the CloudFormation template file (relative to an input artifact) + */ + templatePath: ArtifactPath; +} + +/** + * Creates a stack if the specified stack doesn't exist. If the stack exists and is in a failed + * state (reported as ROLLBACK_COMPLETE, ROLLBACK_FAILED, CREATE_FAILED, DELETE_FAILED, or + * UPDATE_ROLLBACK_FAILED), AWS CloudFormation deletes the stack and then creates a new stack. If + * the stack isn't in a failed state, AWS CloudFormation updates it. Use this action to + * automatically replace failed stacks without recovering or troubleshooting them. You would + * typically choose this mode for testing. + */ +export class ReplaceOnFailure extends CloudFormationAction { + constructor(parent: Stage, name: string, options: ReplaceOnFailureOptions) { + super(parent, name, { + ActionMode: 'REPLACE_ON_FAILURE', + Capabilities: options.capabilities, + OutputFileName: options.outputFileName, + ParameterOverrides: options.parameterOverrides, + RoleArn: options.roleArn, + StackName: options.stackName, + TemplateConfiguration: options.templateConfiguration, + TemplatePath: options.templatePath.location, + }); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json b/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json new file mode 100644 index 0000000000000..5c64677bfc9c5 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json @@ -0,0 +1,68 @@ +{ + "name": "@aws-cdk/aws-cloudformation-codepipeline", + "version": "0.8.0", + "description": "AWS CodePipeline Actions for AWS CloudFormation", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.aws.cloudformation.codepipeline", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "aws.cloudformation.codepipeline" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.AWS.CloudFormationCodePipeline" + } + } + }, + "repository": { + "type": "git", + "url": "git://github.com/awslabs/aws-cdk" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package" + }, + "nyc": { + "lines": 60, + "branches": 30 + }, + "keywords": [ + "aws", + "cdk", + "codepipeline", + "constructs", + "cloudformation" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com" + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "^0.8.0", + "cdk-build-tools": "^0.8.0", + "cdk-integ-tools": "^0.8.0", + "pkglint": "^0.8.0" + }, + "dependencies": { + "@aws-cdk/aws-codepipeline": "^0.8.0", + "@aws-cdk/aws-iam": "^0.8.0", + "@aws-cdk/cdk": "^0.8.0", + "@aws-cdk/aws-codebuild-codepipeline": "^0.8.0", + "@aws-cdk/aws-s3": "^0.8.0", + "@aws-cdk/aws-codebuild": "^0.8.0", + "@aws-cdk/aws-codecommit": "^0.8.0", + "@aws-cdk/aws-codecommit-codepipeline": "^0.8.0" + }, + "homepage": "https://github.com/awslabs/aws-cdk" +} diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.expected.json b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.expected.json new file mode 100644 index 0000000000000..9c83c4b2b85ad --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.expected.json @@ -0,0 +1,211 @@ +{ + "Resources": { + "PipelineArtifactsBucket22248F97": { + "Type": "AWS::S3::Bucket", + "DeletionPolicy": "Retain" + }, + "PipelineRoleD68726F7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codepipeline.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineRoleDefaultPolicyC7A05455": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:PutObject*", + "s3:DeleteObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/", + "*" + ] + ] + } + ] + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineBucketB967BD35", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineBucketB967BD35", + "Arn" + ] + }, + "/", + "*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineRoleDefaultPolicyC7A05455", + "Roles": [ + { + "Ref": "PipelineRoleD68726F7" + } + ] + } + }, + "PipelineC660917D": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "ArtifactStore": { + "Location": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "Type": "S3" + }, + "RoleArn": { + "Fn::GetAtt": [ + "PipelineRoleD68726F7", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "S3", + "Version": "1" + }, + "Configuration": { + "S3Bucket": { + "Ref": "PipelineBucketB967BD35" + }, + "S3ObjectKey": "key", + "PollForSourceChanges": true + }, + "InputArtifacts": [], + "Name": "Source", + "OutputArtifacts": [ + { + "Name": "SourceArtifact" + } + ], + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "ActionMode": "CHANGE_SET_REPLACE", + "ChangeSetName": "ChangeSetIntegTest", + "RoleArn": { + "Fn::GetAtt": [ + "CfnChangeSetRole6F05F6FC", + "Arn" + ] + }, + "StackName": "IntegTest-TestActionStack", + "TemplatePath": "SourceArtifact::test.yaml" + }, + "InputArtifacts": [], + "Name": "DeployCFN", + "OutputArtifacts": [], + "RunOrder": 1 + } + ], + "Name": "CFN" + } + ] + }, + "DependsOn": [ + "PipelineRoleD68726F7", + "PipelineRoleDefaultPolicyC7A05455" + ] + }, + "PipelineBucketB967BD35": { + "Type": "AWS::S3::Bucket", + "Properties": { + "VersioningConfiguration": { + "Status": "Enabled" + } + } + }, + "CfnChangeSetRole6F05F6FC": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codepipeline.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.ts new file mode 100644 index 0000000000000..6667cd9ffaf14 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.ts @@ -0,0 +1,42 @@ +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import { ArtifactPath } from '@aws-cdk/aws-codepipeline'; +import { Role } from '@aws-cdk/aws-iam'; +import s3 = require('@aws-cdk/aws-s3'); +import cdk = require('@aws-cdk/cdk'); +import { ServicePrincipal } from '@aws-cdk/cdk'; + +import cfn_codepipeline = require('../lib'); + +const app = new cdk.App(process.argv); + +const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-cloudformation'); + +const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); + +const sourceStage = new codepipeline.Stage(pipeline, 'Source'); +const bucket = new s3.Bucket(stack, 'PipelineBucket', { + versioned: true, +}); +const source = new codepipeline.AmazonS3Source(sourceStage, 'Source', { + artifactName: 'SourceArtifact', + bucket, + bucketKey: 'key', +}); + +const cfnStage = new codepipeline.Stage(pipeline, 'CFN'); + +const changeSetName = "ChangeSetIntegTest"; +const stackName = "IntegTest-TestActionStack"; + +const role = new Role(stack, 'CfnChangeSetRole', { + assumedBy: new ServicePrincipal('cloudformation.amazonaws.com'), +}); + +new cfn_codepipeline.CreateReplaceChangeSet(cfnStage, 'DeployCFN', { + changeSetName, + stackName, + roleArn: role.roleArn, + templatePath: new ArtifactPath(source.artifact, 'test.yaml') +}); + +process.stdout.write(app.run()); diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-action.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-action.ts new file mode 100644 index 0000000000000..252dffd9539a5 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-action.ts @@ -0,0 +1,193 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import { CodePipelineBuildArtifacts, CodePipelineSource, Project } from '@aws-cdk/aws-codebuild'; +import { PipelineBuildAction } from '@aws-cdk/aws-codebuild-codepipeline'; +import { Repository } from '@aws-cdk/aws-codecommit'; +import { PipelineSource } from '@aws-cdk/aws-codecommit-codepipeline'; +import { ArtifactPath, Pipeline, Stage } from '@aws-cdk/aws-codepipeline'; +import { Role } from '@aws-cdk/aws-iam'; +import cdk = require('@aws-cdk/cdk'); +import { PolicyStatement, ServicePrincipal } from '@aws-cdk/cdk'; +import { Test } from 'nodeunit'; +import { CreateReplaceChangeSet, ExecuteChangeSet } from '../lib/pipeline-action'; + +// tslint:disable:object-literal-key-quotes + +export = { + 'CreateChangeSetAction can be used to make a change set from a CodePipeline'(test: Test) { + const stack = new cdk.Stack(); + + const pipeline = new Pipeline(stack, 'MagicPipeline'); + + const changeSetExecRole = new Role(stack, 'ChangeSetRole', { + assumedBy: new ServicePrincipal('cloudformation.amazonaws.com'), + }); + + /** Source! */ + const repo = new Repository(stack, 'MyVeryImportantRepo', { repositoryName: 'my-very-important-repo' }); + + const sourceStage = new Stage(pipeline, 'source'); + + const source = new PipelineSource(sourceStage, 'source', { + artifactName: 'SourceArtifact', + repository: repo, + }); + + /** Build! */ + + const buildStage = new Stage(pipeline, 'build'); + const buildArtifacts = new CodePipelineBuildArtifacts(); + const project = new Project(stack, 'MyBuildProject', { + source: new CodePipelineSource(), + artifacts: buildArtifacts, + }); + + const buildAction = new PipelineBuildAction(buildStage, 'build', { + project, + inputArtifact: source.artifact, + artifactName: "OutputYo" + }); + + /** Deploy! */ + + // To execute a change set - yes, you probably do need *:* 🤷‍♀️ + changeSetExecRole.addToPolicy(new PolicyStatement().addAllResources().addAction("*")); + + const prodStage = new Stage(pipeline, 'prod'); + const stackName = 'BrelandsStack'; + const changeSetName = 'MyMagicalChangeSet'; + + new CreateReplaceChangeSet(prodStage, 'BuildChangeSetProd', { + stackName, + changeSetName, + roleArn: changeSetExecRole.roleArn, + // Dat artifact path tho (LEGAACYYY) + templatePath: new ArtifactPath(buildAction.artifact!, 'build/sam/template.yaml'), + }); + + new ExecuteChangeSet(prodStage, 'ExecuteChangeSetProd', { + stackName, + changeSetName, + }); + + expect(stack).to(haveResource('AWS::CodePipeline::Pipeline', { + "ArtifactStore": { + "Location": { + "Ref": "MagicPipelineArtifactsBucket212FE7BF" + }, + "Type": "S3" + }, "RoleArn": { + "Fn::GetAtt": ["MagicPipelineRoleFB2BD6DE", + "Arn" + ] + }, + "Stages": [{ + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", "Provider": "CodeCommit", "Version": "1" + }, + "Configuration": { + "RepositoryName": { + "Fn::GetAtt": [ + "MyVeryImportantRepo11BC3EBD", + "Name" + ] + }, + "BranchName": "master", + "PollForSourceChanges": true + }, + "InputArtifacts": [], + "Name": "source", + "OutputArtifacts": [ + { + "Name": "SourceArtifact" + } + ], + "RunOrder": 1 + } + ], + "Name": "source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Ref": "MyBuildProject30DB9D6E" + } + }, + "InputArtifacts": [ + { + "Name": "SourceArtifact" + } + ], + "Name": "build", + "OutputArtifacts": [ + { + "Name": "OutputYo" + } + ], + "RunOrder": 1 + } + ], + "Name": "build" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "ActionMode": "CHANGE_SET_REPLACE", + "ChangeSetName": "MyMagicalChangeSet", + "RoleArn": { + "Fn::GetAtt": [ + "ChangeSetRole0BCF99E6", + "Arn" + ] + }, + "StackName": "BrelandsStack", + "TemplatePath": "OutputYo::build/sam/template.yaml" + }, + "InputArtifacts": [], + "Name": "BuildChangeSetProd", + "OutputArtifacts": [], + "RunOrder": 1 + }, + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "ActionMode": "CHANGE_SET_EXECUTE", + "ChangeSetName": "MyMagicalChangeSet" + }, + "InputArtifacts": [], + "Name": "ExecuteChangeSetProd", + "OutputArtifacts": [], + "RunOrder": 1 + } + ], + "Name": "prod" + } + ] + })); + + test.done(); + + }, +}; diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/tsconfig.json b/packages/@aws-cdk/aws-cloudformation-codepipeline/tsconfig.json new file mode 100644 index 0000000000000..5ae4f32b6ed34 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es2018", + "lib": [ + "es2016", + "es2017.object", + "es2017.string" + ], + "module": "commonjs", + "declaration": true, + "strict": true, + "strictPropertyInitialization": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "inlineSourceMap": true, + "experimentalDecorators": true, + "jsx": "react", + "jsxFactory": "jsx.create" + }, + "_generated_by_jsii_": "generated by jsii - you can delete, and ideally add to your .gitignore" +} diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/tslint.json b/packages/@aws-cdk/aws-cloudformation-codepipeline/tslint.json new file mode 100644 index 0000000000000..ddd9bc8e0f437 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/tslint.json @@ -0,0 +1,38 @@ +{ + "extends": "tslint:recommended", + "rules": { + "semicolon": [ + true, + "always", + "ignore-interfaces" + ], + "no-invalid-template-strings": false, + "quotemark": false, + "interface-name": false, + "max-classes-per-file": false, + "member-access": { + "severity": "warning" + }, + "interface-over-type-literal": false, + "eofline": false, + "arrow-parens": false, + "no-namespace": false, + "max-line-length": [ + true, + 150 + ], + "object-literal-sort-keys": false, + "trailing-comma": false, + "no-unused-expression": [ + true, + "allow-new" + ], + "variable-name": [ + true, + "ban-keywords", + "check-format", + "allow-leading-underscore", + "allow-pascal-case" + ] + } +} diff --git a/packages/@aws-cdk/aws-codepipeline/lib/actions.ts b/packages/@aws-cdk/aws-codepipeline/lib/actions.ts index 0a25581f778d7..5b89af927f753 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/actions.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/actions.ts @@ -460,16 +460,16 @@ export class ApprovalAction extends Action { // } // } -// export class DeployAction extends Action { -// constructor(parent: Stage, name: string, provider: string, artifactBounds: ActionArtifactBounds, configuration?: any) { -// super(parent, name, { -// category: ActionCategory.Deploy, -// provider, -// artifactBounds, -// configuration -// }); -// } -// } +export class DeployAction extends Action { + constructor(parent: Stage, name: string, provider: string, artifactBounds: ActionArtifactBounds, configuration?: any) { + super(parent, name, { + category: ActionCategory.Deploy, + provider, + artifactBounds, + configuration + }); + } +} // export class CodeDeploy extends DeployAction { // constructor(parent: Stage, name: string, applicationName: string, deploymentGroupName: string) { diff --git a/packages/@aws-cdk/aws-codepipeline/lib/cloudformation-actions.ts b/packages/@aws-cdk/aws-codepipeline/lib/cloudformation-actions.ts deleted file mode 100644 index 4db9b930e7c7f..0000000000000 --- a/packages/@aws-cdk/aws-codepipeline/lib/cloudformation-actions.ts +++ /dev/null @@ -1,186 +0,0 @@ -// import { iam } from '@aws-cdk/resources'; -// import { ArtifactPath } from '.'; -// import { DeployAction } from './actions'; -// import { Stage } from './pipeline'; - -// // TODO: rework these according to new model - -// export enum CloudFormationCapabilities { -// IAM = 'CAPABILITY_IAM', -// NamedIAM = 'CAPABILITY_NAMED_IAM' -// } - -// export class CloudFormationCommonOptions { -// /** -// * For stacks that contain certain resources, explicit acknowledgement that AWS CloudFormation -// * might create or update those resources. For example, you must specify CAPABILITY_IAM if your -// * stack template contains AWS Identity and Access Management (IAM) resources. For more -// * information, see Acknowledging IAM Resources in AWS CloudFormation Templates. -// */ -// public capabilities?: CloudFormationCapabilities[]; - -// /** -// * A name for the output file, such as CreateStackOutput.json. AWS CodePipeline adds the file to -// * the output artifact after performing the specified action. -// */ -// public outputFileName?: string; - -// /** -// * A JSON object that specifies values for template parameters. If you specify parameters that -// * are also specified in the template configuration file, these values override them. All -// * parameter names must be present in the stack template. -// * -// * Note: There is a maximum size limit of 1 kilobyte for the JSON object that can be stored in -// * the ParameterOverrides property. -// * -// * We recommend that you use the template configuration file to specify most of your parameter -// * values. Use parameter overrides to specify only dynamic parameter values (values that are -// * unknown until you run the pipeline). -// */ -// public parameterOverrides?: { [name: string]: any }; - -// /** -// * The template configuration file can contain template parameter values and a stack policy. -// * Note that if you include sensitive information, such as passwords, restrict access to this -// * file. For more information, see AWS CloudFormation Artifacts. -// */ -// public templateConfiguration?: ArtifactPath; -// } - -// export class CloudFormationAction extends DeployAction { -// constructor(parent: Stage, name: string, configuration?: any) { -// super(parent, name, 'CloudFormation', { minInputs: 0, maxInputs: 10, minOutputs: 0, maxOutputs: 1 }, configuration); -// } -// } - -// export class ExecuteChangeSetOptions extends CloudFormationCommonOptions { -// public stackName: string; - -// /** -// * The name of an existing change set or a new change set that you want to create for the -// * specified stack. -// */ -// public changeSetName: string; -// } - -// /** -// * Executes a change set. -// */ -// export class ExecuteChangeSet extends CloudFormationAction { -// constructor(parent: Stage, name: string, options: ExecuteChangeSetOptions) { -// super(parent, name, { -// ActionMode: 'CHANGE_SET_EXECUTE', -// Capabilities: options.capabilities, -// ChangeSetName: options.changeSetName, -// OutputFileName: options.outputFileName, -// }); -// } -// } - -// export class ChangeSetReplaceOptions extends CloudFormationCommonOptions { -// public stackName: string; - -// /** -// * The name of an existing change set or a new change set that you want to create for the -// * specified stack. -// */ -// public changeSetName: string; -// public roleArn: iam.RoleArnAttribute; -// public templatePath: ArtifactPath; -// } - -// /** -// * Creates the change set if it doesn't exist based on the stack name and template that you submit. -// * If the change set exists, AWS CloudFormation deletes it, and then creates a new one. -// */ -// export class ChangeSetReplace extends CloudFormationAction { -// constructor(parent: Stage, name: string, options: ChangeSetReplaceOptions) { -// super(parent, name, { -// ActionMode: 'CHANGE_SET_REPLACE', -// Capabilities: options.capabilities, -// ChangeSetName: options.changeSetName, -// OutputFileName: options.outputFileName, -// ParameterOverrides: options.parameterOverrides, -// RoleArn: options.roleArn, -// StackName: options.stackName, -// TemplateConfiguration: options.templateConfiguration, -// TemplatePath: options.templatePath, -// }); -// } -// } - -// export class CreateUpdateOptions extends CloudFormationCommonOptions { -// public roleArn: iam.RoleArnAttribute; -// public stackName: string; -// public templatePath: ArtifactPath; -// } - -// /** -// * Creates the stack if the specified stack doesn't exist. If the stack exists, AWS CloudFormation -// * updates the stack. Use this action to update existing stacks. AWS CodePipeline won't replace the -// * stack. -// */ -// export class CreateUpdate extends CloudFormationAction { -// constructor(parent: Stage, name: string, options: CreateUpdateOptions) { -// super(parent, name, { -// ActionMode: 'CREATE_UPDATE', -// Capabilities: options.capabilities, -// OutputFileName: options.outputFileName, -// ParameterOverrides: options.parameterOverrides, -// RoleArn: options.roleArn, -// StackName: options.stackName, -// TemplateConfiguration: options.templateConfiguration, -// TemplatePath: options.templatePath -// }); -// } -// } - -// export class DeleteOnlyOptions extends CloudFormationCommonOptions { -// public roleArn: iam.RoleArnAttribute; -// public stackName: string; -// } - -// /** -// * Deletes a stack. If you specify a stack that doesn't exist, the action completes successfully -// * without deleting a stack. -// */ -// export class DeleteOnly extends CloudFormationAction { -// constructor(parent: Stage, name: string, options: DeleteOnlyOptions) { -// super(parent, name, { -// ActionMode: 'DELETE_ONLY', -// Capabilities: options.capabilities, -// OutputFileName: options.outputFileName, -// RoleArn: options.roleArn, -// StackName: options.stackName, -// }); -// } -// } - -// export class ReplaceOnFailureOptions extends CloudFormationCommonOptions { -// public roleArn: iam.RoleArnAttribute; -// public stackName: string; -// public templatePath: ArtifactPath; -// } - -// /** -// * Creates a stack if the specified stack doesn't exist. If the stack exists and is in a failed -// * state (reported as ROLLBACK_COMPLETE, ROLLBACK_FAILED, CREATE_FAILED, DELETE_FAILED, or -// * UPDATE_ROLLBACK_FAILED), AWS CloudFormation deletes the stack and then creates a new stack. If -// * the stack isn't in a failed state, AWS CloudFormation updates it. Use this action to -// * automatically replace failed stacks without recovering or troubleshooting them. You would -// * typically choose this mode for testing. -// */ -// export class ReplaceOnFailure extends CloudFormationAction { -// constructor(parent: Stage, name: string, options: ReplaceOnFailureOptions) { -// super(parent, name, { -// ActionMode: 'REPLACE_ON_FAILURE', -// Capabilities: options.capabilities, -// OutputFileName: options.outputFileName, -// ParameterOverrides: options.parameterOverrides, -// RoleArn: options.roleArn, -// StackName: options.stackName, -// TemplateConfiguration: options.templateConfiguration, -// TemplatePath: options.templatePath, -// }); -// } -// } From b9ddbaa204c83620fbdd392a3f33a3f9803e44e7 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 Aug 2018 15:16:47 +0200 Subject: [PATCH 2/7] Address review comments, fix input/output artifacts issue, expand README --- .../aws-cloudformation-codepipeline/README.md | 58 +-- .../lib/pipeline-action.ts | 371 +++++++++++------- .../package.json | 21 +- .../test/integ.pipeline.expected.json | 12 +- .../test/integ.pipeline.ts | 2 +- ...integ.template-from-repo.lit.expected.json | 231 +++++++++++ .../test/integ.template-from-repo.lit.ts | 42 ++ .../test/test.pipeline-action.ts | 149 ++++++- 8 files changed, 681 insertions(+), 205 deletions(-) create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.expected.json create mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.ts diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/README.md b/packages/@aws-cdk/aws-cloudformation-codepipeline/README.md index 8d24a652f7d35..4960c60e24674 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/README.md +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/README.md @@ -1,50 +1,24 @@ ## AWS CodePipline Actions for AWS CloudFormation -This module contains an Action that allows you to use CloudFormation actions from CodePipeline. +This module contains Actions that allows you to deploy to AWS CloudFormation from AWS CodePipeline. -Example usage: +For example, the following code fragment defines a pipeline that automatically deploys a CloudFormation template +directly from a CodeCommit repository, with a manual approval step in between to confirm the changes: -```ts -import { Repository } from '@aws-cdk/aws-codecommit'; -import { PipelineSource } from '@aws-cdk/aws-codecommit-codepipeline'; -import { ArtifactPath, Pipeline, Stage } from '@aws-cdk/aws-codepipeline'; -import { Role } from '@aws-cdk/aws-iam'; -import { PolicyStatement, ServicePrincipal } from '@aws-cdk/cdk'; -import { CreateReplaceChangeSet, ExecuteChangeSet } from '../lib/pipeline-action'; +[example Pipeline to deploy CloudFormation](test/integ.template-from-repo.lit.ts) -const pipeline = new Pipeline(stack, 'DeploymentPipeline'); - -const changeSetExecRole = new Role(stack, 'ChangeSetRole', { - assumedBy: new ServicePrincipal('cloudformation.amazonaws.com'), -}); - -/** Source! */ -const repo = new Repository(stack, 'MyVeryImportantRepo', { repositoryName: 'my-very-important-repo' }); - -const sourceStage = new Stage(pipeline, 'source'); - -const source = new PipelineSource(sourceStage, 'source', { - artifactName: 'SourceArtifact', - repository: repo, -}); - -/** Deploy! */ -const prodStage = new Stage(pipeline, 'prod'); -const stackName = 'AStack'; -const changeSetName = 'AChangeSet'; +See [the AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline.html) +for more details about using CloudFormation in CodePipeline. -new CreateReplaceChangeSet(prodStage, 'MakeChangeSetProd', { - stackName, - changeSetName, - roleArn: changeSetExecRole.roleArn, - templatePath: new ArtifactPath(source.artifact, 'some_template.yaml'), -}); +### Actions defined by this package -new ExecuteChangeSet(prodStage, 'ExecuteChangeSetProd', { - stackName, - changeSetName, -}); -``` +This package defines the following actions: -See [the AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline.html) -for more details about using CloudFormation in CodePipeline. +* **CreateUpdateStack** - Deploy a CloudFormation template directly from the pipeline. The indicated stack is created, + or updated if it already exists. If the stack is in a failure state, deployment will fail (unless `replaceOnFailure` + is set to `true`, in which case it will be destroyed and recreated). +* **DeleteStackOnly** - Delete the stack with the given name. +* **CreateReplaceChangeSet** - Prepare a change set to be applied later. You will typically use change sets if you want + to manually verify the changes that are being staged, or if you want to separate the people (or system) preparing the + changes from the people (or system) applying the changes. +* **ExecuteChangeSet** - Execute a change set prepared previously. diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-action.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-action.ts index 0be86928ef158..17f7000f2fe87 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-action.ts @@ -1,231 +1,318 @@ -import { ArtifactPath, DeployAction, Stage, } from '@aws-cdk/aws-codepipeline'; -import { RoleArn } from '@aws-cdk/aws-iam'; +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import iam = require('@aws-cdk/aws-iam'); +import cdk = require('@aws-cdk/cdk'); -export enum CloudFormationCapabilities { - IAM = 'CAPABILITY_IAM', - NamedIAM = 'CAPABILITY_NAMED_IAM' -} - -export interface CloudFormationCommonOptions { +// tslint:disable:max-line-length +/** + * Properties common to all CloudFormation actions + */ +export interface CloudFormationCommonProps { /** - * For stacks that contain certain resources, explicit acknowledgement that AWS CloudFormation - * might create or update those resources. For example, you must specify CAPABILITY_IAM if your - * stack template contains AWS Identity and Access Management (IAM) resources. For more - * information, see Acknowledging IAM Resources in AWS CloudFormation Templates. + * The name of the stack to apply this action to */ - capabilities?: CloudFormationCapabilities[]; + stackName: string; /** - * A name for the output file, such as CreateStackOutput.json. AWS CodePipeline adds the file to - * the output artifact after performing the specified action. + * A name for the filename in the output artifact to store the AWS CloudFormation call's result. + * + * The file will contain the result of the call to AWS CloudFormation (for example + * the call to UpdateStack or CreateChangeSet). + * + * AWS CodePipeline adds the file to the output artifact after performing + * the specified action. + * + * @default No output artifact generated */ outputFileName?: string; /** - * A JSON object that specifies values for template parameters. If you specify parameters that - * are also specified in the template configuration file, these values override them. All - * parameter names must be present in the stack template. + * The name of the output artifact to generate * - * Note: There is a maximum size limit of 1 kilobyte for the JSON object that can be stored in - * the ParameterOverrides property. + * Only applied if `outputFileName` is set as well. * - * We recommend that you use the template configuration file to specify most of your parameter - * values. Use parameter overrides to specify only dynamic parameter values (values that are - * unknown until you run the pipeline). + * @default Automatically generated artifact name. */ - parameterOverrides?: { [name: string]: any }; + outputArtifactName?: string; +} +/** + * Base class for Actions that execute CloudFormation + */ +export abstract class CloudFormationAction extends codepipeline.DeployAction { /** - * The template configuration file can contain template parameter values and a stack policy. - * Note that if you include sensitive information, such as passwords, restrict access to this - * file. For more information, see AWS CloudFormation Artifacts. + * Output artifact containing the CloudFormation call response + * + * Only present if configured by passing `outputFileName`. */ - templateConfiguration?: ArtifactPath; -} + public artifact?: codepipeline.Artifact; + + constructor(parent: codepipeline.Stage, id: string, props: CloudFormationCommonProps, configuration?: any) { + super(parent, id, 'CloudFormation', { minInputs: 0, maxInputs: 10, minOutputs: 0, maxOutputs: 1 }, { + ...configuration, + StackName: props.stackName, + OutputFileName: props.outputFileName, + }); -export abstract class CloudFormationAction extends DeployAction { - constructor(parent: Stage, name: string, configuration?: any) { - super(parent, name, 'CloudFormation', { minInputs: 0, maxInputs: 10, minOutputs: 0, maxOutputs: 1 }, configuration); + if (props.outputFileName) { + this.artifact = this.addOutputArtifact(props.outputArtifactName || (this.parent!.name + this.name + 'Artifact')); + } } } -export interface ExecuteChangeSetOptions extends CloudFormationCommonOptions { - - /** - * The stack name to execute a change set against. - */ - stackName: string; - +/** + * Properties for the ExecuteChangeSet action. + */ +export interface ExecuteChangeSetProps extends CloudFormationCommonProps { /** - * The name of an existing change set or a new change set that you want to create for the - * specified stack. + * Name of the change set to execute. */ changeSetName: string; } /** - * Executes a change set. + * CodePipeline action to execute a prepared change set. */ export class ExecuteChangeSet extends CloudFormationAction { - constructor(parent: Stage, name: string, options: ExecuteChangeSetOptions) { - super(parent, name, { + constructor(parent: codepipeline.Stage, id: string, props: ExecuteChangeSetProps) { + super(parent, id, props, { ActionMode: 'CHANGE_SET_EXECUTE', - Capabilities: options.capabilities, - ChangeSetName: options.changeSetName, - OutputFileName: options.outputFileName, + ChangeSetName: props.changeSetName, }); } } -export interface CreateReplaceChangeSetOptions extends CloudFormationCommonOptions { +/** + * Properties common to CloudFormation actions that stage deployments + */ +export interface CloudFormationDeploymentActionCommonProps extends CloudFormationCommonProps { + /** + * IAM role to assume when deploying changes. + * + * If not specified, a fresh role is created. The role is created with zero + * permissions unless `trustTemplate` is true, in which case the role will have + * full permissions. + * + * @default A fresh role with full or no permissions (depending on the value of `trustTemplate`). + */ + role?: iam.Role; /** - * The stack name to create/replace a change set for. + * Acknowledge certain changes made as part of deployment + * + * For stacks that contain certain resources, explicit acknowledgement that AWS CloudFormation + * might create or update those resources. For example, you must specify CAPABILITY_IAM if your + * stack template contains AWS Identity and Access Management (IAM) resources. For more + * information, see [Acknowledging IAM Resources in AWS CloudFormation Templates](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities). + * + * @default No capabitilities passed, unless `trustTemplate` is true */ - stackName: string; + capabilities?: CloudFormationCapabilities[]; /** - * The name of an existing change set or a new change set that you want to create for the - * specified stack. + * Whether the deployed templates are trusted. + * + * If `true`, the default `role` will have full permissions and the default + * `capabilities` will be set to `CloudFormationCapabilities.NamedIAM.` + * + * If `false`, you will have to set the appropriate IAM permissions and capabilities + * manually. + * + * @default false */ - changeSetName: string; + trustTemplate?: boolean; /** - * The arn of the IAM service role that AWS CloudFormation assumes - * when it operates on resources in the specified stack. + * Input artifact to use for template parameters values and stack policy. + * + * The template configuration file should contain a JSON object that should look like this: + * `{ "Parameters": {...}, "Tags": {...}, "StackPolicy": {... }}`. For more information, + * see [AWS CloudFormation Artifacts](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline-cfn-artifacts.html). + * + * Note that if you include sensitive information, such as passwords, restrict access to this + * file. + * + * @default No template configuration based on input artifacts */ - roleArn: RoleArn; + templateConfiguration?: codepipeline.ArtifactPath; /** - * The path to the template to pass to CloudFormation during the change set operation. + * Additional template parameters. + * + * Template parameters specified here take precedence over template parameters + * found in the artifact specified by the `templateConfiguration` property. + * + * We recommend that you use the template configuration file to specify + * most of your parameter values. Use parameter overrides to specify only + * dynamic parameter values (values that are unknown until you run the + * pipeline). + * + * All parameter names must be present in the stack template. + * + * Note: the entire object cannot be more than 1kB. + * + * @default No overrides */ - templatePath: ArtifactPath; + parameterOverrides?: { [name: string]: any }; } /** - * Creates the change set if it doesn't exist based on the stack name and template that you submit. - * If the change set exists, AWS CloudFormation deletes it, and then creates a new one. + * Base class for all CloudFormation actions that execute or stage deployments. */ -export class CreateReplaceChangeSet extends CloudFormationAction { - constructor(parent: Stage, name: string, options: CreateReplaceChangeSetOptions) { - super(parent, name, { - ActionMode: 'CHANGE_SET_REPLACE', - Capabilities: options.capabilities, - ChangeSetName: options.changeSetName, - OutputFileName: options.outputFileName, - ParameterOverrides: options.parameterOverrides, - RoleArn: options.roleArn, - StackName: options.stackName, - TemplateConfiguration: options.templateConfiguration, - TemplatePath: options.templatePath.location, +export abstract class CloudFormationDeploymentAction extends CloudFormationAction { + public readonly role: iam.Role; + + constructor(parent: codepipeline.Stage, id: string, props: CloudFormationDeploymentActionCommonProps, configuration: any) { + super(parent, id, props, { + Capabilities: props.trustTemplate ? [CloudFormationCapabilities.NamedIAM] : props.capabilities, + RoleArn: new cdk.Token(() => this.role.roleArn), + ParameterOverrides: props.parameterOverrides, + TemplateConfiguration: props.templateConfiguration, + StackName: props.stackName, + ...configuration, }); + + if (props.role) { + this.role = props.role; + } else { + this.role = new iam.Role(this, 'Role', { + assumedBy: new cdk.ServicePrincipal('cloudformation.amazonaws.com') + }); + + if (props.trustTemplate) { + this.role.addToPolicy(new cdk.PolicyStatement().addAction('*').addAllResources()); + } + } } -} -export interface CreateUpdateOptions extends CloudFormationCommonOptions { /** - * The arn of the IAM service role that AWS CloudFormation assumes - * when it operates on resources in the specified stack. + * Add statement to the service role assumed by CloudFormation while executing this action. */ - roleArn: RoleArn; + public addToRolePolicy(statement: cdk.PolicyStatement) { + return this.role.addToPolicy(statement); + } +} +/** + * Properties for the CreateReplaceChangeSet action. + */ +export interface CreateReplaceChangeSetProps extends CloudFormationDeploymentActionCommonProps { /** - * The CloudFormation stack name to create or update + * Name of the change set to create or update. */ - stackName: string; + changeSetName: string; /** - * The path to the CloudFormation template file (relative to an input artifact) + * Input artifact with the ChangeSet's CloudFormation template */ - templatePath: ArtifactPath; + templatePath: codepipeline.ArtifactPath; } /** - * Creates the stack if the specified stack doesn't exist. If the stack exists, AWS CloudFormation - * updates the stack. Use this action to update existing stacks. AWS CodePipeline won't replace the - * stack. + * CodePipeline action to prepare a change set. + * + * Creates the change set if it doesn't exist based on the stack name and template that you submit. + * If the change set exists, AWS CloudFormation deletes it, and then creates a new one. */ -export class CreateUpdate extends CloudFormationAction { - constructor(parent: Stage, name: string, options: CreateUpdateOptions) { - super(parent, name, { - ActionMode: 'CREATE_UPDATE', - Capabilities: options.capabilities, - OutputFileName: options.outputFileName, - ParameterOverrides: options.parameterOverrides, - RoleArn: options.roleArn, - StackName: options.stackName, - TemplateConfiguration: options.templateConfiguration, - TemplatePath: options.templatePath.location +export class CreateReplaceChangeSet extends CloudFormationDeploymentAction { + constructor(parent: codepipeline.Stage, id: string, props: CreateReplaceChangeSetProps) { + super(parent, id, props, { + ActionMode: 'CHANGE_SET_REPLACE', + ChangeSetName: props.changeSetName, + TemplatePath: props.templatePath.location, }); + + this.addInputArtifact(props.templatePath.artifact); } } -export interface DeleteOnlyOptions extends CloudFormationCommonOptions { +/** + * Properties for the CreateUpdate action + */ +export interface CreateUpdateProps extends CloudFormationDeploymentActionCommonProps { /** - * The arn of the IAM service role that AWS CloudFormation assumes - * when it operates on resources in the specified stack. + * Input artifact with the CloudFormation template to deploy */ - roleArn: RoleArn; + templatePath: codepipeline.ArtifactPath; /** - * The CloudFormation stack name to delete + * Replace the stack if it's in a failed state. + * + * If this is set to true and the stack is in a failed state (one of + * ROLLBACK_COMPLETE, ROLLBACK_FAILED, CREATE_FAILED, DELETE_FAILED, or + * UPDATE_ROLLBACK_FAILED), AWS CloudFormation deletes the stack and then + * creates a new stack. + * + * If this is not set to true and the stack is in a failed state, + * the deployment fails. + * + * @default false */ - stackName: string; + replaceOnFailure?: boolean; } /** + * CodePipeline action to deploy a stack. + * + * Creates the stack if the specified stack doesn't exist. If the stack exists, + * AWS CloudFormation updates the stack. Use this action to update existing + * stacks. + * + * AWS CodePipeline won't replace the stack, and will fail deployment if the + * stack is in a failed state. Use `ReplaceOnFailure` for an action that + * will delete and recreate the stack to try and recover from failed states. + * + * Use this action to automatically replace failed stacks without recovering or + * troubleshooting them. You would typically choose this mode for testing. + */ +export class CreateUpdateStack extends CloudFormationDeploymentAction { + constructor(parent: codepipeline.Stage, id: string, props: CreateUpdateProps) { + super(parent, id, props, { + ActionMode: props.replaceOnFailure ? 'REPLACE_ON_FAILURE' : 'CREATE_UPDATE', + TemplatePath: props.templatePath.location + }); + this.addInputArtifact(props.templatePath.artifact); + } +} + +/** + * Properties for the DeleteOnly action + */ +// tslint:disable-next-line:no-empty-interface +export interface DeleteStackOnlyProps extends CloudFormationDeploymentActionCommonProps { +} + +/** + * CodePipeline action to delete a stack. + * * Deletes a stack. If you specify a stack that doesn't exist, the action completes successfully * without deleting a stack. */ -export class DeleteOnly extends CloudFormationAction { - constructor(parent: Stage, name: string, options: DeleteOnlyOptions) { - super(parent, name, { +export class DeleteStackOnly extends CloudFormationDeploymentAction { + constructor(parent: codepipeline.Stage, id: string, props: DeleteStackOnlyProps) { + super(parent, id, props, { ActionMode: 'DELETE_ONLY', - Capabilities: options.capabilities, - OutputFileName: options.outputFileName, - RoleArn: options.roleArn, - StackName: options.stackName, }); } } -export interface ReplaceOnFailureOptions extends CloudFormationCommonOptions { - /** - * The arn of the IAM service role that AWS CloudFormation assumes - * when it operates on resources in the specified stack. - */ - roleArn: RoleArn; - +/** + * Capabilities that affect whether CloudFormation is allowed to change IAM resources + */ +export enum CloudFormationCapabilities { /** - * The CloudFormation stack name to replace/create + * Capability to create anonymous IAM resources + * + * Pass this capability if you're only creating anonymous resources. */ - stackName: string; + IAM = 'CAPABILITY_IAM', /** - * The path to the CloudFormation template file (relative to an input artifact) + * Capability to create named IAM resources. + * + * Pass this capability if you're creating IAM resources that have physical + * names. + * + * `CloudFormationCapabilities.NamedIAM` implies `CloudFormationCapabilities.IAM`; you don't have to pass both. */ - templatePath: ArtifactPath; + NamedIAM = 'CAPABILITY_NAMED_IAM' } - -/** - * Creates a stack if the specified stack doesn't exist. If the stack exists and is in a failed - * state (reported as ROLLBACK_COMPLETE, ROLLBACK_FAILED, CREATE_FAILED, DELETE_FAILED, or - * UPDATE_ROLLBACK_FAILED), AWS CloudFormation deletes the stack and then creates a new stack. If - * the stack isn't in a failed state, AWS CloudFormation updates it. Use this action to - * automatically replace failed stacks without recovering or troubleshooting them. You would - * typically choose this mode for testing. - */ -export class ReplaceOnFailure extends CloudFormationAction { - constructor(parent: Stage, name: string, options: ReplaceOnFailureOptions) { - super(parent, name, { - ActionMode: 'REPLACE_ON_FAILURE', - Capabilities: options.capabilities, - OutputFileName: options.outputFileName, - ParameterOverrides: options.parameterOverrides, - RoleArn: options.roleArn, - StackName: options.stackName, - TemplateConfiguration: options.templateConfiguration, - TemplatePath: options.templatePath.location, - }); - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json b/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json index 5c64677bfc9c5..a7eba87498e02 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json @@ -8,10 +8,10 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.awscdk.aws.cloudformation.codepipeline", + "package": "software.amazon.awscdk.services.cloudformation.codepipeline", "maven": { "groupId": "software.amazon.awscdk", - "artifactId": "aws.cloudformation.codepipeline" + "artifactId": "cloudformation-codepipeline" } }, "dotnet": { @@ -21,7 +21,7 @@ }, "repository": { "type": "git", - "url": "git://github.com/awslabs/aws-cdk" + "url": "https://github.com/awslabs/aws-cdk.git" }, "scripts": { "build": "cdk-build", @@ -45,24 +45,25 @@ ], "author": { "name": "Amazon Web Services", - "url": "https://aws.amazon.com" + "url": "https://aws.amazon.com", + "organization": true }, "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "^0.8.0", "cdk-build-tools": "^0.8.0", "cdk-integ-tools": "^0.8.0", - "pkglint": "^0.8.0" - }, - "dependencies": { - "@aws-cdk/aws-codepipeline": "^0.8.0", - "@aws-cdk/aws-iam": "^0.8.0", - "@aws-cdk/cdk": "^0.8.0", + "pkglint": "^0.8.0", "@aws-cdk/aws-codebuild-codepipeline": "^0.8.0", "@aws-cdk/aws-s3": "^0.8.0", "@aws-cdk/aws-codebuild": "^0.8.0", "@aws-cdk/aws-codecommit": "^0.8.0", "@aws-cdk/aws-codecommit-codepipeline": "^0.8.0" }, + "dependencies": { + "@aws-cdk/aws-codepipeline": "^0.8.0", + "@aws-cdk/aws-iam": "^0.8.0", + "@aws-cdk/cdk": "^0.8.0" + }, "homepage": "https://github.com/awslabs/aws-cdk" } diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.expected.json b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.expected.json index 9c83c4b2b85ad..0629b00573d66 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.expected.json +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.expected.json @@ -156,8 +156,6 @@ "Version": "1" }, "Configuration": { - "ActionMode": "CHANGE_SET_REPLACE", - "ChangeSetName": "ChangeSetIntegTest", "RoleArn": { "Fn::GetAtt": [ "CfnChangeSetRole6F05F6FC", @@ -165,9 +163,15 @@ ] }, "StackName": "IntegTest-TestActionStack", + "ActionMode": "CHANGE_SET_REPLACE", + "ChangeSetName": "ChangeSetIntegTest", "TemplatePath": "SourceArtifact::test.yaml" }, - "InputArtifacts": [], + "InputArtifacts": [ + { + "Name": "SourceArtifact" + } + ], "Name": "DeployCFN", "OutputArtifacts": [], "RunOrder": 1 @@ -199,7 +203,7 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "codepipeline.amazonaws.com" + "Service": "cloudformation.amazonaws.com" } } ], diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.ts index 6667cd9ffaf14..cceeb7f2a45a6 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.ts +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.ts @@ -35,7 +35,7 @@ const role = new Role(stack, 'CfnChangeSetRole', { new cfn_codepipeline.CreateReplaceChangeSet(cfnStage, 'DeployCFN', { changeSetName, stackName, - roleArn: role.roleArn, + role, templatePath: new ArtifactPath(source.artifact, 'test.yaml') }); diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.expected.json b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.expected.json new file mode 100644 index 0000000000000..322e1392548cf --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.expected.json @@ -0,0 +1,231 @@ +{ + "Resources": { + "PipelineArtifactsBucket22248F97": { + "Type": "AWS::S3::Bucket", + "DeletionPolicy": "Retain" + }, + "PipelineRoleD68726F7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codepipeline.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineRoleDefaultPolicyC7A05455": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:PutObject*", + "s3:DeleteObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/", + "*" + ] + ] + } + ] + }, + { + "Action": [ + "codecommit:GetBranch", + "codecommit:GetCommit", + "codecommit:UploadArchive", + "codecommit:GetUploadArchiveStatus", + "codecommit:CancelUploadArchive" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "TemplateRepo2326F199", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineRoleDefaultPolicyC7A05455", + "Roles": [ + { + "Ref": "PipelineRoleD68726F7" + } + ] + } + }, + "PipelineC660917D": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "ArtifactStore": { + "Location": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "Type": "S3" + }, + "RoleArn": { + "Fn::GetAtt": [ + "PipelineRoleD68726F7", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "CodeCommit", + "Version": "1" + }, + "Configuration": { + "RepositoryName": { + "Fn::GetAtt": [ + "TemplateRepo2326F199", + "Name" + ] + }, + "BranchName": "master", + "PollForSourceChanges": true + }, + "InputArtifacts": [], + "Name": "Source", + "OutputArtifacts": [ + { + "Name": "SourceArtifact" + } + ], + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "RoleArn": { + "Fn::GetAtt": [ + "PipelineDeployPrepareChangesRoleD28C853C", + "Arn" + ] + }, + "StackName": "OurStack", + "ActionMode": "CHANGE_SET_REPLACE", + "ChangeSetName": "StagedChangeSet", + "TemplatePath": "SourceArtifact::template.yaml" + }, + "InputArtifacts": [ + { + "Name": "SourceArtifact" + } + ], + "Name": "PrepareChanges", + "OutputArtifacts": [], + "RunOrder": 1 + }, + { + "ActionTypeId": { + "Category": "Approval", + "Owner": "AWS", + "Provider": "Manual", + "Version": "1" + }, + "InputArtifacts": [], + "Name": "ApproveChanges", + "OutputArtifacts": [], + "RunOrder": 1 + }, + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "ActionMode": "CHANGE_SET_EXECUTE", + "ChangeSetName": "StagedChangeSet", + "StackName": "OurStack" + }, + "InputArtifacts": [], + "Name": "ExecuteChanges", + "OutputArtifacts": [], + "RunOrder": 1 + } + ], + "Name": "Deploy" + } + ] + }, + "DependsOn": [ + "PipelineRoleD68726F7", + "PipelineRoleDefaultPolicyC7A05455" + ] + }, + "PipelineDeployPrepareChangesRoleD28C853C": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "cloudformation.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TemplateRepo2326F199": { + "Type": "AWS::CodeCommit::Repository", + "Properties": { + "RepositoryName": "template-repo", + "Triggers": [] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.ts new file mode 100644 index 0000000000000..0523239f2c843 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.ts @@ -0,0 +1,42 @@ +import codecommit = require('@aws-cdk/aws-codecommit'); +import codecommitpl = require('@aws-cdk/aws-codecommit-codepipeline'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import cdk = require('@aws-cdk/cdk'); +import cfnpl = require('../lib'); + +const app = new cdk.App(process.argv); +const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-cloudformation'); + +/// !show +const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); + +// Source stage: read from repository +const repo = new codecommit.Repository(stack, 'TemplateRepo', { + repositoryName: 'template-repo' +}); +const sourceStage = new codepipeline.Stage(pipeline, 'Source'); +const source = new codecommitpl.PipelineSource(sourceStage, 'Source', { + repository: repo, + artifactName: 'SourceArtifact', +}); + +// Deployment stage: create and deploy changeset with manual approval +const prodStage = new codepipeline.Stage(pipeline, 'Deploy'); +const stackName = 'OurStack'; +const changeSetName = 'StagedChangeSet'; + +new cfnpl.CreateReplaceChangeSet(prodStage, 'PrepareChanges', { + stackName, + changeSetName, + templatePath: source.artifact.subartifact('template.yaml'), +}); + +new codepipeline.ApprovalAction(prodStage, 'ApproveChanges'); + +new cfnpl.ExecuteChangeSet(prodStage, 'ExecuteChanges', { + stackName, + changeSetName, +}); +/// !hide + +process.stdout.write(app.run()); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-action.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-action.ts index 252dffd9539a5..053af72f2e9b3 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-action.ts +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-action.ts @@ -8,7 +8,7 @@ import { Role } from '@aws-cdk/aws-iam'; import cdk = require('@aws-cdk/cdk'); import { PolicyStatement, ServicePrincipal } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { CreateReplaceChangeSet, ExecuteChangeSet } from '../lib/pipeline-action'; +import { CreateReplaceChangeSet, CreateUpdateStack, ExecuteChangeSet } from '../lib/pipeline-action'; // tslint:disable:object-literal-key-quotes @@ -59,9 +59,8 @@ export = { new CreateReplaceChangeSet(prodStage, 'BuildChangeSetProd', { stackName, changeSetName, - roleArn: changeSetExecRole.roleArn, - // Dat artifact path tho (LEGAACYYY) - templatePath: new ArtifactPath(buildAction.artifact!, 'build/sam/template.yaml'), + role: changeSetExecRole, + templatePath: new ArtifactPath(buildAction.artifact!, 'template.yaml'), }); new ExecuteChangeSet(prodStage, 'ExecuteChangeSetProd', { @@ -158,9 +157,9 @@ export = { ] }, "StackName": "BrelandsStack", - "TemplatePath": "OutputYo::build/sam/template.yaml" + "TemplatePath": "OutputYo::template.yaml" }, - "InputArtifacts": [], + "InputArtifacts": [{"Name": "OutputYo"}], "Name": "BuildChangeSetProd", "OutputArtifacts": [], "RunOrder": 1 @@ -190,4 +189,142 @@ export = { test.done(); }, + + 'trustTemplate leads to admin role and full IAM capabilities'(test: Test) { + // GIVEN + const stack = new TestFixture(); + + // WHEN + new CreateUpdateStack(stack.deployStage, 'CreateUpdate', { + stackName: 'MyStack', + templatePath: stack.source.artifact.subartifact('template.yaml'), + trustTemplate: true, + }); + + const roleId = "PipelineDeployCreateUpdateRole515CB7D4"; + + // THEN: Action in Pipeline has named IAM capabilities + expect(stack).to(haveResource('AWS::CodePipeline::Pipeline', { + "Stages": [ + { "Name": "Source" /* don't care about the rest */ }, + { + "Name": "Deploy", + "Actions": [ + { + "Configuration": { + "Capabilities": [ "CAPABILITY_NAMED_IAM" ], + "RoleArn": { "Fn::GetAtt": [ roleId, "Arn" ] }, + "ActionMode": "CREATE_UPDATE", + "StackName": "MyStack", + "TemplatePath": "SourceArtifact::template.yaml" + }, + "InputArtifacts": [{"Name": "SourceArtifact"}], + "Name": "CreateUpdate", + }, + ], + } + ] + })); + + // THEN: Role is created with full permissions + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: "*", + Resource: "*" + } + ], + }, + Roles: [{ Ref: roleId }] + })); + + test.done(); + }, + + 'outputFileName leads to creation of output artifact'(test: Test) { + // GIVEN + const stack = new TestFixture(); + + // WHEN + new CreateUpdateStack(stack.deployStage, 'CreateUpdate', { + stackName: 'MyStack', + templatePath: stack.source.artifact.subartifact('template.yaml'), + outputFileName: 'CreateResponse.json', + }); + + // THEN: Action has output artifacts + expect(stack).to(haveResource('AWS::CodePipeline::Pipeline', { + "Stages": [ + { "Name": "Source" /* don't care about the rest */ }, + { + "Name": "Deploy", + "Actions": [ + { + "OutputArtifacts": [{"Name": "DeployCreateUpdateArtifact"}], + "Name": "CreateUpdate", + }, + ], + } + ] + })); + + test.done(); + }, + + 'replaceOnFailure switches action type'(test: Test) { + // GIVEN + const stack = new TestFixture(); + + // WHEN + new CreateUpdateStack(stack.deployStage, 'CreateUpdate', { + stackName: 'MyStack', + templatePath: stack.source.artifact.subartifact('template.yaml'), + replaceOnFailure: true, + }); + + // THEN: Action has output artifacts + expect(stack).to(haveResource('AWS::CodePipeline::Pipeline', { + "Stages": [ + { "Name": "Source" /* don't care about the rest */ }, + { + "Name": "Deploy", + "Actions": [ + { + "Configuration": { + "ActionMode": "REPLACE_ON_FAILURE", + }, + "Name": "CreateUpdate", + }, + ], + } + ] + })); + + test.done(); + }, }; + +/** + * A test stack with a half-prepared pipeline ready to add CloudFormation actions to + */ +class TestFixture extends cdk.Stack { + public readonly pipeline: Pipeline; + public readonly sourceStage: Stage; + public readonly deployStage: Stage; + public readonly repo: Repository; + public readonly source: PipelineSource; + + constructor() { + super(); + + this.pipeline = new Pipeline(this, 'Pipeline'); + this.sourceStage = new Stage(this.pipeline, 'Source'); + this.deployStage = new Stage(this.pipeline, 'Deploy'); + this.repo = new Repository(this, 'MyVeryImportantRepo', { repositoryName: 'my-very-important-repo' }); + this.source = new PipelineSource(this.sourceStage, 'Source', { + artifactName: 'SourceArtifact', + repository: this.repo, + }); + } +} \ No newline at end of file From ed3f01e173645ce4a3228e03b4a203e4fe24bf31 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 Aug 2018 15:49:38 +0200 Subject: [PATCH 3/7] Address review comments --- .../lib/index.ts | 2 +- ...pipeline-action.ts => pipeline-actions.ts} | 7 ++--- .../package.json | 3 --- ...ine-action.ts => test.pipeline-actions.ts} | 0 .../tsconfig.json | 27 ------------------- 5 files changed, 5 insertions(+), 34 deletions(-) rename packages/@aws-cdk/aws-cloudformation-codepipeline/lib/{pipeline-action.ts => pipeline-actions.ts} (97%) rename packages/@aws-cdk/aws-cloudformation-codepipeline/test/{test.pipeline-action.ts => test.pipeline-actions.ts} (100%) delete mode 100644 packages/@aws-cdk/aws-cloudformation-codepipeline/tsconfig.json diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/index.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/index.ts index 890162998e10d..9b3db900186ee 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/index.ts @@ -1 +1 @@ -export * from './pipeline-action'; +export * from './pipeline-actions'; diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-action.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-actions.ts similarity index 97% rename from packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-action.ts rename to packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-actions.ts index 17f7000f2fe87..44c4e4d93b1a0 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-actions.ts @@ -2,7 +2,6 @@ import codepipeline = require('@aws-cdk/aws-codepipeline'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); -// tslint:disable:max-line-length /** * Properties common to all CloudFormation actions */ @@ -81,6 +80,7 @@ export class ExecuteChangeSet extends CloudFormationAction { } } +// tslint:disable:max-line-length Because of long URLs in documentation /** * Properties common to CloudFormation actions that stage deployments */ @@ -154,6 +154,7 @@ export interface CloudFormationDeploymentActionCommonProps extends CloudFormatio */ parameterOverrides?: { [name: string]: any }; } +// tslint:enable:max-line-length /** * Base class for all CloudFormation actions that execute or stage deployments. @@ -163,12 +164,12 @@ export abstract class CloudFormationDeploymentAction extends CloudFormationActio constructor(parent: codepipeline.Stage, id: string, props: CloudFormationDeploymentActionCommonProps, configuration: any) { super(parent, id, props, { - Capabilities: props.trustTemplate ? [CloudFormationCapabilities.NamedIAM] : props.capabilities, + ...configuration, + Capabilities: props.trustTemplate && props.capabilities === undefined ? [CloudFormationCapabilities.NamedIAM] : props.capabilities, RoleArn: new cdk.Token(() => this.role.roleArn), ParameterOverrides: props.parameterOverrides, TemplateConfiguration: props.templateConfiguration, StackName: props.stackName, - ...configuration, }); if (props.role) { diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json b/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json index a7eba87498e02..c86e73416f179 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json @@ -13,9 +13,6 @@ "groupId": "software.amazon.awscdk", "artifactId": "cloudformation-codepipeline" } - }, - "dotnet": { - "namespace": "Amazon.CDK.AWS.CloudFormationCodePipeline" } } }, diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-action.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-actions.ts similarity index 100% rename from packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-action.ts rename to packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-actions.ts diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/tsconfig.json b/packages/@aws-cdk/aws-cloudformation-codepipeline/tsconfig.json deleted file mode 100644 index 5ae4f32b6ed34..0000000000000 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "target": "es2018", - "lib": [ - "es2016", - "es2017.object", - "es2017.string" - ], - "module": "commonjs", - "declaration": true, - "strict": true, - "strictPropertyInitialization": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noImplicitThis": true, - "alwaysStrict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "inlineSourceMap": true, - "experimentalDecorators": true, - "jsx": "react", - "jsxFactory": "jsx.create" - }, - "_generated_by_jsii_": "generated by jsii - you can delete, and ideally add to your .gitignore" -} From 5c239839151b0674ae6213d37d852a40abd8de3a Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 Aug 2018 16:00:20 +0200 Subject: [PATCH 4/7] Improve naming and documentation around "full" permissions. --- .../lib/pipeline-actions.ts | 22 ++++++++++++------- .../test/integ.template-from-repo.lit.ts | 1 + 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-actions.ts index 44c4e4d93b1a0..1b5587df4c288 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-actions.ts @@ -109,17 +109,23 @@ export interface CloudFormationDeploymentActionCommonProps extends CloudFormatio capabilities?: CloudFormationCapabilities[]; /** - * Whether the deployed templates are trusted. + * Whether to grant full permissions to CloudFormation while deploying this template. * - * If `true`, the default `role` will have full permissions and the default - * `capabilities` will be set to `CloudFormationCapabilities.NamedIAM.` + * Setting this to `true` affects the defaults for `role` and `capabilities`, if you + * don't specify any alternatives. * - * If `false`, you will have to set the appropriate IAM permissions and capabilities - * manually. + * The default role that will be created for you will have full (i.e., `*`) + * permissions on all resources, and the deployment will have named IAM + * capabilities (i.e., able to create all IAM resources). + * + * This is a shorthand that you can use if you fully trust the templates that + * are deployed in this pipeline. If you want more fine-grained permissions, + * use `addToRolePolicy` and `capabilities` to control what the CloudFormation + * deployment is allowed to do. * * @default false */ - trustTemplate?: boolean; + fullPermissions?: boolean; /** * Input artifact to use for template parameters values and stack policy. @@ -165,7 +171,7 @@ export abstract class CloudFormationDeploymentAction extends CloudFormationActio constructor(parent: codepipeline.Stage, id: string, props: CloudFormationDeploymentActionCommonProps, configuration: any) { super(parent, id, props, { ...configuration, - Capabilities: props.trustTemplate && props.capabilities === undefined ? [CloudFormationCapabilities.NamedIAM] : props.capabilities, + Capabilities: props.fullPermissions && props.capabilities === undefined ? [CloudFormationCapabilities.NamedIAM] : props.capabilities, RoleArn: new cdk.Token(() => this.role.roleArn), ParameterOverrides: props.parameterOverrides, TemplateConfiguration: props.templateConfiguration, @@ -179,7 +185,7 @@ export abstract class CloudFormationDeploymentAction extends CloudFormationActio assumedBy: new cdk.ServicePrincipal('cloudformation.amazonaws.com') }); - if (props.trustTemplate) { + if (props.fullPermissions) { this.role.addToPolicy(new cdk.PolicyStatement().addAction('*').addAllResources()); } } diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.ts index 0523239f2c843..033c4143c4210 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.ts +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.ts @@ -28,6 +28,7 @@ const changeSetName = 'StagedChangeSet'; new cfnpl.CreateReplaceChangeSet(prodStage, 'PrepareChanges', { stackName, changeSetName, + fullPermissions: true, templatePath: source.artifact.subartifact('template.yaml'), }); From bc3fa64a3c63df8db2ab977170121274e1980515 Mon Sep 17 00:00:00 2001 From: Romain Marcadier-Muller Date: Wed, 8 Aug 2018 16:08:24 +0200 Subject: [PATCH 5/7] Add missing s --- .../test/test.pipeline-actions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-actions.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-actions.ts index 053af72f2e9b3..0ad588a9f536a 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-actions.ts +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-actions.ts @@ -8,7 +8,7 @@ import { Role } from '@aws-cdk/aws-iam'; import cdk = require('@aws-cdk/cdk'); import { PolicyStatement, ServicePrincipal } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { CreateReplaceChangeSet, CreateUpdateStack, ExecuteChangeSet } from '../lib/pipeline-action'; +import { CreateReplaceChangeSet, CreateUpdateStack, ExecuteChangeSet } from '../lib/pipeline-actions'; // tslint:disable:object-literal-key-quotes @@ -327,4 +327,4 @@ class TestFixture extends cdk.Stack { repository: this.repo, }); } -} \ No newline at end of file +} From 116c4bb9e97b161b0df303a29236749da1a89fa3 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 Aug 2018 16:14:27 +0200 Subject: [PATCH 6/7] Final comments, make integ test use fullPermissions flag --- .../lib/pipeline-actions.ts | 7 +++-- .../package.json | 1 + .../test/integ.pipeline.expected.json | 8 ++--- ...integ.template-from-repo.lit.expected.json | 30 ++++++++++++++++--- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-actions.ts index 1b5587df4c288..7b5dee76168eb 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/lib/pipeline-actions.ts @@ -53,7 +53,7 @@ export abstract class CloudFormationAction extends codepipeline.DeployAction { }); if (props.outputFileName) { - this.artifact = this.addOutputArtifact(props.outputArtifactName || (this.parent!.name + this.name + 'Artifact')); + this.artifact = this.addOutputArtifact(props.outputArtifactName || (parent.name + this.name + 'Artifact')); } } } @@ -169,9 +169,12 @@ export abstract class CloudFormationDeploymentAction extends CloudFormationActio public readonly role: iam.Role; constructor(parent: codepipeline.Stage, id: string, props: CloudFormationDeploymentActionCommonProps, configuration: any) { + const capabilities = props.fullPermissions && props.capabilities === undefined ? [CloudFormationCapabilities.NamedIAM] : props.capabilities; + super(parent, id, props, { ...configuration, - Capabilities: props.fullPermissions && props.capabilities === undefined ? [CloudFormationCapabilities.NamedIAM] : props.capabilities, + // This must be a string, so flatten the list to a comma-separated string. + Capabilities: (capabilities && capabilities.join(',')) || undefined, RoleArn: new cdk.Token(() => this.role.roleArn), ParameterOverrides: props.parameterOverrides, TemplateConfiguration: props.templateConfiguration, diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json b/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json index c86e73416f179..833d099c56086 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/package.json @@ -7,6 +7,7 @@ "jsii": { "outdir": "dist", "targets": { + "sphinx": {}, "java": { "package": "software.amazon.awscdk.services.cloudformation.codepipeline", "maven": { diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.expected.json b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.expected.json index 0629b00573d66..f9ad5997041fd 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.expected.json +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.pipeline.expected.json @@ -156,16 +156,16 @@ "Version": "1" }, "Configuration": { + "ActionMode": "CHANGE_SET_REPLACE", + "ChangeSetName": "ChangeSetIntegTest", + "TemplatePath": "SourceArtifact::test.yaml", "RoleArn": { "Fn::GetAtt": [ "CfnChangeSetRole6F05F6FC", "Arn" ] }, - "StackName": "IntegTest-TestActionStack", - "ActionMode": "CHANGE_SET_REPLACE", - "ChangeSetName": "ChangeSetIntegTest", - "TemplatePath": "SourceArtifact::test.yaml" + "StackName": "IntegTest-TestActionStack" }, "InputArtifacts": [ { diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.expected.json b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.expected.json index 322e1392548cf..91fde8381e96d 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.expected.json +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/integ.template-from-repo.lit.expected.json @@ -144,16 +144,17 @@ "Version": "1" }, "Configuration": { + "ActionMode": "CHANGE_SET_REPLACE", + "ChangeSetName": "StagedChangeSet", + "TemplatePath": "SourceArtifact::template.yaml", + "Capabilities": "CAPABILITY_NAMED_IAM", "RoleArn": { "Fn::GetAtt": [ "PipelineDeployPrepareChangesRoleD28C853C", "Arn" ] }, - "StackName": "OurStack", - "ActionMode": "CHANGE_SET_REPLACE", - "ChangeSetName": "StagedChangeSet", - "TemplatePath": "SourceArtifact::template.yaml" + "StackName": "OurStack" }, "InputArtifacts": [ { @@ -220,6 +221,27 @@ } } }, + "PipelineDeployPrepareChangesRoleDefaultPolicy8CDCCD73": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineDeployPrepareChangesRoleDefaultPolicy8CDCCD73", + "Roles": [ + { + "Ref": "PipelineDeployPrepareChangesRoleD28C853C" + } + ] + } + }, "TemplateRepo2326F199": { "Type": "AWS::CodeCommit::Repository", "Properties": { From 166f732c2cad5c07567e92365157efd9682b18f5 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 Aug 2018 16:22:32 +0200 Subject: [PATCH 7/7] Update test to match new property name --- .../test/test.pipeline-actions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-actions.ts b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-actions.ts index 0ad588a9f536a..51d6f0bfc41d6 100644 --- a/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-actions.ts +++ b/packages/@aws-cdk/aws-cloudformation-codepipeline/test/test.pipeline-actions.ts @@ -190,7 +190,7 @@ export = { }, - 'trustTemplate leads to admin role and full IAM capabilities'(test: Test) { + 'fullPermissions leads to admin role and full IAM capabilities'(test: Test) { // GIVEN const stack = new TestFixture(); @@ -198,7 +198,7 @@ export = { new CreateUpdateStack(stack.deployStage, 'CreateUpdate', { stackName: 'MyStack', templatePath: stack.source.artifact.subartifact('template.yaml'), - trustTemplate: true, + fullPermissions: true, }); const roleId = "PipelineDeployCreateUpdateRole515CB7D4"; @@ -212,7 +212,7 @@ export = { "Actions": [ { "Configuration": { - "Capabilities": [ "CAPABILITY_NAMED_IAM" ], + "Capabilities": "CAPABILITY_NAMED_IAM", "RoleArn": { "Fn::GetAtt": [ roleId, "Arn" ] }, "ActionMode": "CREATE_UPDATE", "StackName": "MyStack",