From 95636bf10671075927b6d60785b1fde37e75d3c1 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Tue, 10 Jul 2018 17:02:33 -0700 Subject: [PATCH] [BREAKING] Move the Lambda Invoke CodePipeline Action. This commit moves the Lambda Invoke Action from the codepipeline module to its dedicated, new lambda-codepipeline module. It's not possible to move it to the lambda module itself, as that would cause a cyclic dependency between the codepipeline, sns and lambda modules. This continues the refactoring of moving out concrete Action typesfrom the codepipeline module. --- .../@aws-cdk/aws-codepipeline/lib/actions.ts | 81 +---- .../@aws-cdk/aws-codepipeline/package.json | 1 - .../aws-codepipeline/test/test.action.ts | 83 ----- .../test/test.general-validation.ts | 86 ++++++ .../test/test.generalValidation.ts | 31 -- .../aws-lambda-codepipeline/.gitignore | 15 + .../aws-lambda-codepipeline/.npmignore | 6 + .../aws-lambda-codepipeline/README.md | 24 ++ .../aws-lambda-codepipeline/lib/index.ts | 1 + .../lib/pipeline-action.ts | 87 ++++++ .../aws-lambda-codepipeline/package.json | 62 ++++ .../test/integ.pipeline.expected.json | 287 ++++++++++++++++++ .../test/integ.pipeline.ts | 37 +++ .../test/test.pipeline-action.ts | 91 ++++++ 14 files changed, 697 insertions(+), 195 deletions(-) create mode 100644 packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts delete mode 100644 packages/@aws-cdk/aws-codepipeline/test/test.generalValidation.ts create mode 100644 packages/@aws-cdk/aws-lambda-codepipeline/.gitignore create mode 100644 packages/@aws-cdk/aws-lambda-codepipeline/.npmignore create mode 100644 packages/@aws-cdk/aws-lambda-codepipeline/README.md create mode 100644 packages/@aws-cdk/aws-lambda-codepipeline/lib/index.ts create mode 100644 packages/@aws-cdk/aws-lambda-codepipeline/lib/pipeline-action.ts create mode 100644 packages/@aws-cdk/aws-lambda-codepipeline/package.json create mode 100644 packages/@aws-cdk/aws-lambda-codepipeline/test/integ.pipeline.expected.json create mode 100644 packages/@aws-cdk/aws-lambda-codepipeline/test/integ.pipeline.ts create mode 100644 packages/@aws-cdk/aws-lambda-codepipeline/test/test.pipeline-action.ts diff --git a/packages/@aws-cdk/aws-codepipeline/lib/actions.ts b/packages/@aws-cdk/aws-codepipeline/lib/actions.ts index 6befcbcbfc840..0a25581f778d7 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/actions.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/actions.ts @@ -1,5 +1,4 @@ import events = require('@aws-cdk/aws-events'); -import lambda = require('@aws-cdk/aws-lambda'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); import { Artifact } from './artifact'; @@ -30,7 +29,7 @@ export interface ActionArtifactBounds { maxOutputs: number; } -function defaultBounds(): ActionArtifactBounds { +export function defaultBounds(): ActionArtifactBounds { return { minInputs: 0, maxInputs: 5, @@ -442,84 +441,6 @@ export class ApprovalAction extends Action { } } -export interface InvokeLambdaProps { - /** - * The lambda function to invoke. - */ - lambda: lambda.LambdaRef; - - /** - * String to be used in the event data parameter passed to the Lambda - * function - * - * See an example JSON event in the CodePipeline documentation. - * - * https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html#actions-invoke-lambda-function-json-event-example - */ - userParameters?: any; - - /** - * Adds the "codepipeline:PutJobSuccessResult" and - * "codepipeline:PutJobFailureResult" for '*' resource to the Lambda - * execution role policy. - * - * NOTE: the reason we can't add the specific pipeline ARN as a resource is - * to avoid a cyclic dependency between the pipeline and the Lambda function - * (the pipeline references) the Lambda and the Lambda needs permissions on - * the pipeline. - * - * @see - * https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html#actions-invoke-lambda-function-create-function - * - * @default true - */ - addPutJobResultPolicy?: boolean; -} -/** - * @link https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html - */ -export class InvokeLambdaAction extends Action { - constructor(parent: Stage, name: string, props: InvokeLambdaProps) { - super(parent, name, { - category: ActionCategory.Invoke, - provider: 'Lambda', - artifactBounds: defaultBounds(), - configuration: { - FunctionName: props.lambda.functionName, - UserParameters: props.userParameters - } - }); - - // allow pipeline to list functions - parent.pipeline.addToRolePolicy(new cdk.PolicyStatement() - .addAction('lambda:ListFunctions') - .addResource('*')); - - // allow pipeline to invoke this lambda functionn - parent.pipeline.addToRolePolicy(new cdk.PolicyStatement() - .addAction('lambda:InvokeFunction') - .addResource(props.lambda.functionArn)); - - // allow lambda to put job results for this pipeline. - const addToPolicy = props.addPutJobResultPolicy !== undefined ? props.addPutJobResultPolicy : true; - if (addToPolicy) { - props.lambda.addToRolePolicy(new cdk.PolicyStatement() - .addResource('*') // to avoid cycles (see docs) - .addAction('codepipeline:PutJobSuccessResult') - .addAction('codepipeline:PutJobFailureResult')); - } - } - - /** - * Add an input artifact - * @param artifact - */ - protected addInputArtifact(artifact: Artifact): Action { - super.addInputArtifact(artifact); - return this; - } -} - // export class TestAction extends Action { // constructor(parent: Stage, name: string, provider: string, artifactBounds: ActionArtifactBounds, configuration?: any) { // super(parent, name, { diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index 9886e74e8b5ce..1a61370c3c8f2 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -60,7 +60,6 @@ "dependencies": { "@aws-cdk/aws-events": "^0.7.3-beta", "@aws-cdk/aws-iam": "^0.7.3-beta", - "@aws-cdk/aws-lambda": "^0.7.3-beta", "@aws-cdk/aws-s3": "^0.7.3-beta", "@aws-cdk/cdk": "^0.7.3-beta", "@aws-cdk/util": "^0.7.3-beta" diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.action.ts b/packages/@aws-cdk/aws-codepipeline/test/test.action.ts index ff5c198fea2d5..f85d83cade9d6 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.action.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.action.ts @@ -1,5 +1,3 @@ -import { expect, haveResource } from '@aws-cdk/assert'; -import lambda = require('@aws-cdk/aws-lambda'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import codepipeline = require('../lib'); @@ -86,87 +84,6 @@ export = { outputArtifacts: [{ name: 'TestOutput' }], runOrder: 1 }); - test.done(); - }, - - 'InvokeLambdaAction can be used to invoke lambda functions from a pipeline'(test: Test) { - const stack = new cdk.Stack(); - - const fction = new lambda.Lambda(stack, 'Function', { - code: new lambda.LambdaInlineCode('bla'), - handler: 'index.handler', - runtime: lambda.LambdaRuntime.NodeJS43, - }); - - const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - - new codepipeline.InvokeLambdaAction(new codepipeline.Stage(pipeline, 'Stage'), 'InvokeAction', { - lambda: fction, - userParameters: 'foo-bar/42' - }); - - expect(stack).to(haveResource('AWS::CodePipeline::Pipeline', { - "ArtifactStore": { - "Location": { - "Ref": "PipelineArtifactsBucket22248F97" - }, - "Type": "S3" - }, - "RoleArn": { - "Fn::GetAtt": [ - "PipelineRoleD68726F7", - "Arn" - ] - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Invoke", - "Owner": "AWS", - "Provider": "Lambda", - "Version": "1" - }, - "Configuration": { - "FunctionName": { - "Ref": "Function76856677" - }, - "UserParameters": "foo-bar/42" - }, - "InputArtifacts": [], - "Name": "InvokeAction", - "OutputArtifacts": [], - "RunOrder": 1 - } - ], - "Name": "Stage" - } - ] - })); - - expect(stack).to(haveResource('AWS::IAM::Policy', { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "codepipeline:PutJobSuccessResult", - "codepipeline:PutJobFailureResult" - ], - "Effect": "Allow", - "Resource": "*" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "FunctionServiceRoleDefaultPolicy2F49994A", - "Roles": [ - { - "Ref": "FunctionServiceRole675BB04A" - } - ] - })); - test.done(); } }; diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts b/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts new file mode 100644 index 0000000000000..04c1bb610729e --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts @@ -0,0 +1,86 @@ +import s3 = require('@aws-cdk/aws-s3'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import { AmazonS3Source } from '../lib/actions'; +import { Pipeline } from '../lib/pipeline'; +import { Stage } from '../lib/stage'; +import { validateName } from '../lib/validation'; + +interface NameValidationTestCase { + name: string; + shouldPassValidation: boolean; + explanation: string; +} + +export = { + 'name validation'(test: Test) { + const cases: NameValidationTestCase[] = [ + { name: 'BlahBleep123.@-_', shouldPassValidation: true, explanation: 'should be valid' }, + { name: '', shouldPassValidation: false, explanation: 'the empty string should be invalid' }, + { name: ' BlahBleep', shouldPassValidation: false, explanation: 'spaces should be invalid' }, + { name: '!BlahBleep', shouldPassValidation: false, explanation: '\'!\' should be invalid' } + ]; + + cases.forEach(testCase => { + const name = testCase.name; + const validationBlock = () => { validateName('test thing', name); }; + if (testCase.shouldPassValidation) { + test.doesNotThrow(validationBlock, Error, `${name} failed validation but ${testCase.explanation}`); + } else { + test.throws(validationBlock, Error, `${name} passed validation but ${testCase.explanation}`); + } + }); + + test.done(); + }, + + 'Stage validation': { + 'should fail if Stage has no Actions'(test: Test) { + const stage = stageForTesting(); + + test.deepEqual(stage.validate().length, 1); + + test.done(); + } + }, + + 'Pipeline validation': { + 'should fail if Pipeline has no Stages'(test: Test) { + const stack = new cdk.Stack(); + const pipeline = new Pipeline(stack, 'Pipeline'); + + test.deepEqual(pipeline.validate().length, 1); + + test.done(); + }, + + 'should fail if Pipeline has a Source Action in a non-first Stage'(test: Test) { + const stack = new cdk.Stack(); + const pipeline = new Pipeline(stack, 'Pipeline'); + const firstStage = new Stage(pipeline, 'FirstStage'); + const secondStage = new Stage(pipeline, 'SecondStage'); + + const bucket = new s3.Bucket(stack, 'PipelineBucket'); + new AmazonS3Source(firstStage, 'FirstAction', { + artifactName: 'FirstArtifact', + bucket, + bucketKey: 'key', + }); + new AmazonS3Source(secondStage, 'SecondAction', { + artifactName: 'SecondAction', + bucket, + bucketKey: 'key', + }); + + test.deepEqual(pipeline.validate().length, 1); + + test.done(); + } + } +}; + +function stageForTesting(): Stage { + const stack = new cdk.Stack(); + const pipeline = new Pipeline(stack, 'pipeline'); + return new Stage(pipeline, 'stage'); +} diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.generalValidation.ts b/packages/@aws-cdk/aws-codepipeline/test/test.generalValidation.ts deleted file mode 100644 index ffb5620315da6..0000000000000 --- a/packages/@aws-cdk/aws-codepipeline/test/test.generalValidation.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Test } from 'nodeunit'; -import { validateName } from '../lib/validation'; - -interface NameValidationTestCase { - name: string; - shouldPassValidation: boolean; - explanation: string; -} - -export = { - 'name validation'(test: Test) { - const cases: NameValidationTestCase[] = [ - { name: 'BlahBleep123.@-_', shouldPassValidation: true, explanation: 'should be valid' }, - { name: '', shouldPassValidation: false, explanation: 'the empty string should be invalid' }, - { name: ' BlahBleep', shouldPassValidation: false, explanation: 'spaces should be invalid' }, - { name: '!BlahBleep', shouldPassValidation: false, explanation: '\'!\' should be invalid' } - ]; - - cases.forEach(testCase => { - const name = testCase.name; - const validationBlock = () => { validateName('test thing', name); }; - if (testCase.shouldPassValidation) { - test.doesNotThrow(validationBlock, Error, `${name} failed validation but ${testCase.explanation}`); - } else { - test.throws(validationBlock, Error, `${name} passed validation but ${testCase.explanation}`); - } - }); - - test.done(); - }, -}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-codepipeline/.gitignore b/packages/@aws-cdk/aws-lambda-codepipeline/.gitignore new file mode 100644 index 0000000000000..1cb34bd516574 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-codepipeline/.gitignore @@ -0,0 +1,15 @@ +*.js +*.js.map +*.d.ts +node_modules +*.generated.ts +dist +tsconfig.json +tslint.json + +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-codepipeline/.npmignore b/packages/@aws-cdk/aws-lambda-codepipeline/.npmignore new file mode 100644 index 0000000000000..414172bb772ec --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-codepipeline/.npmignore @@ -0,0 +1,6 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz diff --git a/packages/@aws-cdk/aws-lambda-codepipeline/README.md b/packages/@aws-cdk/aws-lambda-codepipeline/README.md new file mode 100644 index 0000000000000..1a16b93ca42a0 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-codepipeline/README.md @@ -0,0 +1,24 @@ +## AWS CodePipline Actions for AWS Lambda + +This module contains an Action that allows you to invoke a Lambda function from CodePipeline. + +Example usage: + +```ts +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import lambda = require('@aws-cdk/aws-lambda'); +import lambdaCodepipeline = require('@aws-cdk/aws-lambda-codepipeline'); + +// see the @aws-cdk/aws-lambda module for more documentation on how to create Lamda functions +const lambdaFun = new lambda.Lambda(// ... +); + +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); +const lambdaStage = new codepipeline.Stage(pipeline, 'Lambda'); +new lambdaCodepipeline.PipelineInvokeAction(lambdaStage, 'Lambda', { + lambda: lambdaFun, +}); +``` + +See [the AWS documentation](https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html) +on how to write a Lambda function invoked from CodePipeline. diff --git a/packages/@aws-cdk/aws-lambda-codepipeline/lib/index.ts b/packages/@aws-cdk/aws-lambda-codepipeline/lib/index.ts new file mode 100644 index 0000000000000..890162998e10d --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-codepipeline/lib/index.ts @@ -0,0 +1 @@ +export * from './pipeline-action'; diff --git a/packages/@aws-cdk/aws-lambda-codepipeline/lib/pipeline-action.ts b/packages/@aws-cdk/aws-lambda-codepipeline/lib/pipeline-action.ts new file mode 100644 index 0000000000000..f9959bb3b40bf --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-codepipeline/lib/pipeline-action.ts @@ -0,0 +1,87 @@ +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import lambda = require('@aws-cdk/aws-lambda'); +import cdk = require('@aws-cdk/cdk'); + +/** + * Construction properties of the {@link PipelineInvokeAction Lambda invoke CodePipeline Action}. + */ +export interface PipelineInvokeActionProps { + /** + * The lambda function to invoke. + */ + lambda: lambda.LambdaRef; + + /** + * String to be used in the event data parameter passed to the Lambda + * function + * + * See an example JSON event in the CodePipeline documentation. + * + * https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html#actions-invoke-lambda-function-json-event-example + */ + userParameters?: any; + + /** + * Adds the "codepipeline:PutJobSuccessResult" and + * "codepipeline:PutJobFailureResult" for '*' resource to the Lambda + * execution role policy. + * + * NOTE: the reason we can't add the specific pipeline ARN as a resource is + * to avoid a cyclic dependency between the pipeline and the Lambda function + * (the pipeline references) the Lambda and the Lambda needs permissions on + * the pipeline. + * + * @see + * https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html#actions-invoke-lambda-function-create-function + * + * @default true + */ + addPutJobResultPolicy?: boolean; +} + +/** + * CodePipeline invoke Action that is provided by an AWS Lambda function. + * + * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html + */ +export class PipelineInvokeAction extends codepipeline.Action { + constructor(parent: codepipeline.Stage, name: string, props: PipelineInvokeActionProps) { + super(parent, name, { + category: codepipeline.ActionCategory.Invoke, + provider: 'Lambda', + artifactBounds: codepipeline.defaultBounds(), + configuration: { + FunctionName: props.lambda.functionName, + UserParameters: props.userParameters + } + }); + + // allow pipeline to list functions + parent.pipeline.addToRolePolicy(new cdk.PolicyStatement() + .addAction('lambda:ListFunctions') + .addResource('*')); + + // allow pipeline to invoke this lambda functionn + parent.pipeline.addToRolePolicy(new cdk.PolicyStatement() + .addAction('lambda:InvokeFunction') + .addResource(props.lambda.functionArn)); + + // allow lambda to put job results for this pipeline. + const addToPolicy = props.addPutJobResultPolicy !== undefined ? props.addPutJobResultPolicy : true; + if (addToPolicy) { + props.lambda.addToRolePolicy(new cdk.PolicyStatement() + .addResource('*') // to avoid cycles (see docs) + .addAction('codepipeline:PutJobSuccessResult') + .addAction('codepipeline:PutJobFailureResult')); + } + } + + /** + * Add an input artifact + * @param artifact + */ + public addInputArtifact(artifact: codepipeline.Artifact): PipelineInvokeAction { + super.addInputArtifact(artifact); + return this; + } +} diff --git a/packages/@aws-cdk/aws-lambda-codepipeline/package.json b/packages/@aws-cdk/aws-lambda-codepipeline/package.json new file mode 100644 index 0000000000000..4847ded026bdc --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-codepipeline/package.json @@ -0,0 +1,62 @@ +{ + "name": "@aws-cdk/aws-lambda-codepipeline", + "version": "0.7.3-beta", + "description": "AWS CodePipline Actions for AWS Lambda", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "com.amazonaws.cdk.aws.lambdacodepipeline", + "maven": { + "groupId": "com.amazonaws.cdk", + "artifactId": "aws-lambdacodepipeline" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.AWS.LambdaCodePipeline" + } + } + }, + "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" + }, + "nyc": { + "lines": 60, + "branches": 30 + }, + "keywords": [ + "aws", + "cdk", + "codepipeline", + "constructs", + "lambda" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com" + }, + "license": "LicenseRef-LICENSE", + "devDependencies": { + "@aws-cdk/assert": "^0.7.3-beta", + "cdk-build-tools": "^0.7.3-beta", + "cdk-integ-tools": "^0.7.3-beta", + "pkglint": "^0.7.3-beta" + }, + "dependencies": { + "@aws-cdk/aws-codepipeline": "^0.7.3-beta", + "@aws-cdk/aws-lambda": "^0.7.3-beta", + "@aws-cdk/aws-s3": "^0.7.3-beta", + "@aws-cdk/cdk": "^0.7.3-beta" + } +} diff --git a/packages/@aws-cdk/aws-lambda-codepipeline/test/integ.pipeline.expected.json b/packages/@aws-cdk/aws-lambda-codepipeline/test/integ.pipeline.expected.json new file mode 100644 index 0000000000000..92fce302dca7f --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-codepipeline/test/integ.pipeline.expected.json @@ -0,0 +1,287 @@ +{ + "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" + ] + }, + "/", + "*" + ] + ] + } + ] + }, + { + "Action": "lambda:ListFunctions", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "LambdaFun98622869", + "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": "Invoke", + "Owner": "AWS", + "Provider": "Lambda", + "Version": "1" + }, + "Configuration": { + "FunctionName": { + "Ref": "LambdaFun98622869" + } + }, + "InputArtifacts": [], + "Name": "Lambda", + "OutputArtifacts": [], + "RunOrder": 1 + } + ], + "Name": "Lambda" + } + ] + }, + "DependsOn": [ + "PipelineRoleD68726F7", + "PipelineRoleDefaultPolicyC7A05455" + ] + }, + "PipelineBucketB967BD35": { + "Type": "AWS::S3::Bucket", + "Properties": { + "VersioningConfiguration": { + "Status": "Enabled" + } + } + }, + "LambdaFunServiceRoleF0979767": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn", + ":", + { + "Ref": "AWS::Partition" + }, + ":", + "iam", + ":", + "", + ":", + "aws", + ":", + "policy", + "/", + "service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "LambdaFunServiceRoleDefaultPolicy217FED83": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "codepipeline:PutJobSuccessResult", + "codepipeline:PutJobFailureResult" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunServiceRoleDefaultPolicy217FED83", + "Roles": [ + { + "Ref": "LambdaFunServiceRoleF0979767" + } + ] + } + }, + "LambdaFun98622869": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "\n exports.handler = function () {\n console.log(\"Hello, world!\");\n };\n " + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunServiceRoleF0979767", + "Arn" + ] + }, + "Runtime": "nodejs6.10" + }, + "DependsOn": [ + "LambdaFunServiceRoleF0979767", + "LambdaFunServiceRoleDefaultPolicy217FED83" + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-codepipeline/test/integ.pipeline.ts b/packages/@aws-cdk/aws-lambda-codepipeline/test/integ.pipeline.ts new file mode 100644 index 0000000000000..fd132d3b1dfe1 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-codepipeline/test/integ.pipeline.ts @@ -0,0 +1,37 @@ +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import lambda = require('@aws-cdk/aws-lambda'); +import s3 = require('@aws-cdk/aws-s3'); +import cdk = require('@aws-cdk/cdk'); +import lambda_codepipeline = require('../lib'); + +const app = new cdk.App(process.argv); + +const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-lambda'); + +const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); + +const sourceStage = new codepipeline.Stage(pipeline, 'Source'); +const bucket = new s3.Bucket(stack, 'PipelineBucket', { + versioned: true, +}); +new codepipeline.AmazonS3Source(sourceStage, 'Source', { + artifactName: 'SourceArtifact', + bucket, + bucketKey: 'key', +}); + +const lambdaFun = new lambda.Lambda(stack, 'LambdaFun', { + code: new lambda.LambdaInlineCode(` + exports.handler = function () { + console.log("Hello, world!"); + }; + `), + handler: 'index.handler', + runtime: lambda.LambdaRuntime.NodeJS610, +}); +const lambdaStage = new codepipeline.Stage(pipeline, 'Lambda'); +new lambda_codepipeline.PipelineInvokeAction(lambdaStage, 'Lambda', { + lambda: lambdaFun, +}); + +process.stdout.write(app.run()); diff --git a/packages/@aws-cdk/aws-lambda-codepipeline/test/test.pipeline-action.ts b/packages/@aws-cdk/aws-lambda-codepipeline/test/test.pipeline-action.ts new file mode 100644 index 0000000000000..e2c5ed26a3e2a --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-codepipeline/test/test.pipeline-action.ts @@ -0,0 +1,91 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import lambda = require('@aws-cdk/aws-lambda'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import { PipelineInvokeAction } from '../lib/pipeline-action'; + +// tslint:disable:object-literal-key-quotes + +export = { + 'PipelineInvokeAction can be used to invoke lambda functions from a CodePipeline'(test: Test) { + const stack = new cdk.Stack(); + + const lambdaFun = new lambda.Lambda(stack, 'Function', { + code: new lambda.LambdaInlineCode('bla'), + handler: 'index.handler', + runtime: lambda.LambdaRuntime.NodeJS43, + }); + + const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); + + new PipelineInvokeAction(new codepipeline.Stage(pipeline, 'Stage'), 'InvokeAction', { + lambda: lambdaFun, + userParameters: 'foo-bar/42' + }); + + expect(stack).to(haveResource('AWS::CodePipeline::Pipeline', { + "ArtifactStore": { + "Location": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "Type": "S3" + }, + "RoleArn": { + "Fn::GetAtt": [ + "PipelineRoleD68726F7", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Invoke", + "Owner": "AWS", + "Provider": "Lambda", + "Version": "1" + }, + "Configuration": { + "FunctionName": { + "Ref": "Function76856677" + }, + "UserParameters": "foo-bar/42" + }, + "InputArtifacts": [], + "Name": "InvokeAction", + "OutputArtifacts": [], + "RunOrder": 1 + } + ], + "Name": "Stage" + } + ] + })); + + expect(stack).to(haveResource('AWS::IAM::Policy', { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "codepipeline:PutJobSuccessResult", + "codepipeline:PutJobFailureResult" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "FunctionServiceRoleDefaultPolicy2F49994A", + "Roles": [ + { + "Ref": "FunctionServiceRole675BB04A" + } + ] + })); + + test.done(); + } +};