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, -// }); -// } -// }