From 895e92a8a43e394c9493019e07e199da830c97eb Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Wed, 18 Jul 2018 17:58:56 -0700 Subject: [PATCH] feat(aws-codedeploy): Add a CodeDeploy CodePipeline deployment Action. --- packages/@aws-cdk/aws-codedeploy/README.md | 28 +- packages/@aws-cdk/aws-codedeploy/lib/index.ts | 2 + .../aws-codedeploy/lib/pipeline-action.ts | 84 +++++ packages/@aws-cdk/aws-codedeploy/package.json | 1 + .../aws-codepipeline-api/lib/action.ts | 9 - .../aws-codepipeline-api/lib/deploy-action.ts | 7 + .../@aws-cdk/aws-codepipeline/package.json | 1 + .../integ.pipeline-code-deploy.expected.json | 322 ++++++++++++++++++ .../test/integ.pipeline-code-deploy.ts | 51 +++ 9 files changed, 495 insertions(+), 10 deletions(-) create mode 100644 packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts create mode 100644 packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.expected.json create mode 100644 packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.ts diff --git a/packages/@aws-cdk/aws-codedeploy/README.md b/packages/@aws-cdk/aws-codedeploy/README.md index 08ac5d9c7e66c..00dc75bbcab9e 100644 --- a/packages/@aws-cdk/aws-codedeploy/README.md +++ b/packages/@aws-cdk/aws-codedeploy/README.md @@ -1,2 +1,28 @@ ## The CDK Construct Library for AWS CodeDeploy -This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project. + +### Use in CodePipeline + +This module contains an Action that allows you to use CodeDeploy with AWS CodePipeline. + +Example: + +```ts +import codedeploy = require('@aws-cdk/aws-codedeploy'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); + +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', { + pipelineName: 'MyPipeline', +}); + +// add the source and build Stages to the Pipeline... + +const deployStage = new codepipeline.Stage(this, 'Deploy', { + pipeline, +})); +new codedeploy.PipelineDeployAction(this, 'CodeDeploy', { + stage: deployStage, + inputArtifact: buildAction.artifact, // taken from a build Action in a previous Stage + applicationName: 'YourCodeDeployApplicationName', + deploymentGroupName: 'YourCodeDeployDeploymentGroupName', +}); +``` diff --git a/packages/@aws-cdk/aws-codedeploy/lib/index.ts b/packages/@aws-cdk/aws-codedeploy/lib/index.ts index c58cefebd524e..d5ae70c073569 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/index.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/index.ts @@ -1,2 +1,4 @@ +export * from './pipeline-action'; + // AWS::CodeDeploy CloudFormation Resources: export * from './codedeploy.generated'; diff --git a/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts b/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts new file mode 100644 index 0000000000000..3c70561691b8c --- /dev/null +++ b/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts @@ -0,0 +1,84 @@ +import actions = require('@aws-cdk/aws-codepipeline-api'); +import cdk = require('@aws-cdk/cdk'); + +/** + * Construction properties of the {@link PipelineDeployAction CodeDeploy deploy CodePipeline Action}. + */ +export interface PipelineDeployActionProps extends actions.CommonActionProps { + /** + * The name of the CodeDeploy application to deploy to. + * + * @note this will most likely be changed to a proper CodeDeploy AWS Construct reference + * once that functionality has been implemented for CodeDeploy + */ + applicationName: string; + + /** + * The name of the CodeDeploy deployment group to deploy to. + * + * @note this will most likely be changed to a proper CodeDeploy AWS Construct reference + * once that functionality has been implemented for CodeDeploy + */ + deploymentGroupName: string; + + /** + * The source to use as input for deployment. + */ + inputArtifact: actions.Artifact; +} + +export class PipelineDeployAction extends actions.DeployAction { + constructor(parent: cdk.Construct, id: string, props: PipelineDeployActionProps) { + super(parent, id, { + stage: props.stage, + artifactBounds: { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 }, + provider: 'CodeDeploy', + inputArtifact: props.inputArtifact, + configuration: { + ApplicationName: props.applicationName, + DeploymentGroupName: props.deploymentGroupName, + }, + }); + + // permissions, based on: + // https://docs.aws.amazon.com/codedeploy/latest/userguide/auth-and-access-control-permissions-reference.html + + const applicationArn = cdk.Arn.fromComponents({ + service: 'codedeploy', + resource: 'application', + resourceName: props.applicationName, + sep: ':', + }); + props.stage.pipelineRole.addToPolicy(new cdk.PolicyStatement() + .addResource(applicationArn) + .addActions( + 'codedeploy:GetApplicationRevision', + 'codedeploy:RegisterApplicationRevision', + )); + + const deploymentGroupArn = cdk.Arn.fromComponents({ + service: 'codedeploy', + resource: 'deploymentgroup', + resourceName: `${props.applicationName}/${props.deploymentGroupName}`, + sep: ':', + }); + props.stage.pipelineRole.addToPolicy(new cdk.PolicyStatement() + .addResource(deploymentGroupArn) + .addActions( + 'codedeploy:CreateDeployment', + 'codedeploy:GetDeployment', + )); + + const deployConfigArn = cdk.Arn.fromComponents({ + service: 'codedeploy', + resource: 'deploymentconfig', + resourceName: '*', + sep: ':', + }); + props.stage.pipelineRole.addToPolicy(new cdk.PolicyStatement() + .addResource(deployConfigArn) + .addActions( + 'codedeploy:GetDeploymentConfig', + )); + } +} diff --git a/packages/@aws-cdk/aws-codedeploy/package.json b/packages/@aws-cdk/aws-codedeploy/package.json index 93b3ad7ca5c5a..908da1692e30a 100644 --- a/packages/@aws-cdk/aws-codedeploy/package.json +++ b/packages/@aws-cdk/aws-codedeploy/package.json @@ -52,6 +52,7 @@ "pkglint": "^0.8.2" }, "dependencies": { + "@aws-cdk/aws-codepipeline-api": "^0.8.2", "@aws-cdk/cdk": "^0.8.2" }, "homepage": "https://github.com/awslabs/aws-cdk" diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts index f516619ab948c..ef00f15384163 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts @@ -222,15 +222,6 @@ export abstract class Action extends cdk.Construct { // } // } -// export class CodeDeploy extends DeployAction { -// constructor(parent: Stage, name: string, applicationName: string, deploymentGroupName: string) { -// super(parent, name, 'CodeDeploy', { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 }, { -// ApplicationName: applicationName, -// DeploymentGroupName: deploymentGroupName -// }); -// } -// } - // export class ElasticBeanstalkDeploy extends DeployAction { // constructor(parent: Stage, name: string, applicationName: string, environmentName: string) { // super(parent, name, 'ElasticBeanstalk', { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 }, { diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/deploy-action.ts index 07dad50944857..f88a1ccf2a47a 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/deploy-action.ts @@ -1,11 +1,14 @@ import cdk = require('@aws-cdk/cdk'); import { Action, ActionArtifactBounds, ActionCategory, CommonActionProps } from "./action"; +import { Artifact } from './artifact'; export interface DeployActionProps extends CommonActionProps { provider: string; artifactBounds: ActionArtifactBounds; + inputArtifact?: Artifact; + configuration?: any; } @@ -18,5 +21,9 @@ export abstract class DeployAction extends Action { artifactBounds: props.artifactBounds, configuration: props.configuration, }); + + if (props.inputArtifact) { + this.addInputArtifact(props.inputArtifact); + } } } diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index 9e5c27db99624..621ba215ed725 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -56,6 +56,7 @@ "@aws-cdk/aws-cloudformation": "^0.8.2", "@aws-cdk/aws-codebuild": "^0.8.2", "@aws-cdk/aws-codecommit": "^0.8.2", + "@aws-cdk/aws-codedeploy": "^0.8.2", "@aws-cdk/aws-lambda": "^0.8.2", "@aws-cdk/aws-sns": "^0.8.2", "cdk-build-tools": "^0.8.2", diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.expected.json new file mode 100644 index 0000000000000..5aa1e1a7eff82 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.expected.json @@ -0,0 +1,322 @@ +{ + "Resources": { + "CodeDeployApplication": { + "Type": "AWS::CodeDeploy::Application", + "Properties": { + "ApplicationName": "IntegTestDeployApp", + "ComputePlatform": "Server" + } + }, + "CodeDeployGroupRole9EDBB624": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codedeploy.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole" + ] + } + }, + "CodeDeployGroup": { + "Type": "AWS::CodeDeploy::DeploymentGroup", + "Properties": { + "ApplicationName": "IntegTestDeployApp", + "ServiceRoleArn": { + "Fn::GetAtt": [ + "CodeDeployGroupRole9EDBB624", + "Arn" + ] + }, + "DeploymentGroupName": "IntegTestDeploymentGroup" + } + }, + "CodeDeployPipelineIntegTest9F618D61": { + "Type": "AWS::S3::Bucket", + "Properties": { + "VersioningConfiguration": { + "Status": "Enabled" + } + } + }, + "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": [ + "CodeDeployPipelineIntegTest9F618D61", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CodeDeployPipelineIntegTest9F618D61", + "Arn" + ] + }, + "/", + "*" + ] + ] + } + ] + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "CodeDeployPipelineIntegTest9F618D61", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CodeDeployPipelineIntegTest9F618D61", + "Arn" + ] + }, + "/", + "*" + ] + ] + } + ] + }, + { + "Action": [ + "codedeploy:GetApplicationRevision", + "codedeploy:RegisterApplicationRevision" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn", + ":", + { + "Ref": "AWS::Partition" + }, + ":", + "codedeploy", + ":", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + "application", + ":", + "IntegTestDeployApp" + ] + ] + } + }, + { + "Action": [ + "codedeploy:CreateDeployment", + "codedeploy:GetDeployment" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn", + ":", + { + "Ref": "AWS::Partition" + }, + ":", + "codedeploy", + ":", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + "deploymentgroup", + ":", + "IntegTestDeployApp/IntegTestDeploymentGroup" + ] + ] + } + }, + { + "Action": "codedeploy:GetDeploymentConfig", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn", + ":", + { + "Ref": "AWS::Partition" + }, + ":", + "codedeploy", + ":", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + "deploymentconfig", + ":", + "*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineRoleDefaultPolicyC7A05455", + "Roles": [ + { + "Ref": "PipelineRoleD68726F7" + } + ] + } + }, + "PipelineC660917D": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "ArtifactStore": { + "Location": { + "Ref": "CodeDeployPipelineIntegTest9F618D61" + }, + "Type": "S3" + }, + "RoleArn": { + "Fn::GetAtt": [ + "PipelineRoleD68726F7", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "S3", + "Version": "1" + }, + "Configuration": { + "S3Bucket": { + "Ref": "CodeDeployPipelineIntegTest9F618D61" + }, + "S3ObjectKey": "application.zip", + "PollForSourceChanges": true + }, + "InputArtifacts": [], + "Name": "S3Source", + "OutputArtifacts": [ + { + "Name": "SourceOutput" + } + ], + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CodeDeploy", + "Version": "1" + }, + "Configuration": { + "ApplicationName": "IntegTestDeployApp", + "DeploymentGroupName": "IntegTestDeploymentGroup" + }, + "InputArtifacts": [ + { + "Name": "SourceOutput" + } + ], + "Name": "CodeDeploy", + "OutputArtifacts": [], + "RunOrder": 1 + } + ], + "Name": "Deploy" + } + ] + }, + "DependsOn": [ + "PipelineRoleD68726F7", + "PipelineRoleDefaultPolicyC7A05455" + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.ts new file mode 100644 index 0000000000000..b3346adf3efaf --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.ts @@ -0,0 +1,51 @@ +import codedeploy = require('@aws-cdk/aws-codedeploy'); +import iam = require('@aws-cdk/aws-iam'); +import s3 = require('@aws-cdk/aws-s3'); +import cdk = require('@aws-cdk/cdk'); +import codepipeline = require('../lib'); + +const app = new cdk.App(process.argv); + +const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-codedeploy'); + +new codedeploy.cloudformation.ApplicationResource(stack, 'CodeDeployApplication', { + applicationName: 'IntegTestDeployApp', + computePlatform: 'Server', +}); + +const deploymentGroupRole = new iam.Role(stack, 'CodeDeployGroupRole', { + assumedBy: new cdk.ServicePrincipal('codedeploy.amazonaws.com'), + managedPolicyArns: ['arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole'], +}); + +new codedeploy.cloudformation.DeploymentGroupResource(stack, 'CodeDeployGroup', { + applicationName: 'IntegTestDeployApp', + serviceRoleArn: deploymentGroupRole.roleArn, + deploymentGroupName: 'IntegTestDeploymentGroup', +}); + +const bucket = new s3.Bucket(stack, 'CodeDeployPipelineIntegTest', { + versioned: true, +}); + +const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + artifactBucket: bucket, +}); + +const sourceStage = new codepipeline.Stage(stack, 'Source', { pipeline }); +const sourceAction = new s3.PipelineSource(stack, 'S3Source', { + stage: sourceStage, + bucket, + bucketKey: 'application.zip', + artifactName: 'SourceOutput', +}); + +const deployStage = new codepipeline.Stage(stack, 'Deploy', { pipeline }); +new codedeploy.PipelineDeployAction(stack, 'CodeDeploy', { + stage: deployStage, + inputArtifact: sourceAction.artifact, + applicationName: 'IntegTestDeployApp', + deploymentGroupName: 'IntegTestDeploymentGroup', +}); + +process.stdout.write(app.run());