From ceaf54ab9613f8ed40475659c7df648358c3a271 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Wed, 29 May 2019 17:40:14 -0700 Subject: [PATCH] fix(codepipeline-actions): correctly serialize the userParameters passed to the Lambda invoke Action. (#2537) BREAKING CHANGE: removed the `addPutJobResultPolicy` property when creating LambdaInvokeAction. --- .../lib/lambda/invoke-action.ts | 47 ++----- .../test/lambda/test.lambda-invoke-action.ts | 129 ++++++++++++++++++ .../test/test.pipeline.ts | 2 - 3 files changed, 143 insertions(+), 35 deletions(-) create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/lambda/test.lambda-invoke-action.ts diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts index 10aa6f00c8abd..2063ca58c8d32 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts @@ -20,8 +20,6 @@ export interface LambdaInvokeActionProps extends codepipeline.CommonActionProps */ readonly inputs?: codepipeline.Artifact[]; - // tslint:enable:max-line-length - /** * The optional names of the output Artifacts of the Action. * A Lambda Action can have up to 5 outputs. @@ -34,30 +32,14 @@ export interface LambdaInvokeActionProps extends codepipeline.CommonActionProps readonly outputs?: codepipeline.Artifact[]; /** - * String to be used in the event data parameter passed to the Lambda - * function - * - * See an example JSON event in the CodePipeline documentation. + * A set of key-value pairs that will be accessible to the invoked Lambda + * inside the event that the Pipeline will call it with. * - * https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html#actions-invoke-lambda-function-json-event-example + * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html#actions-invoke-lambda-function-json-event-example */ - readonly userParameters?: any; + readonly userParameters?: { [key: string]: 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 - */ - readonly addPutJobResultPolicy?: boolean; + // tslint:enable:max-line-length /** * The lambda function to invoke. @@ -86,8 +68,8 @@ export class LambdaInvokeAction extends codepipeline.Action { }, configuration: { FunctionName: props.lambda.functionName, - UserParameters: props.userParameters - } + UserParameters: props.lambda.node.stringifyJson(props.userParameters), + }, }); this.props = props; @@ -104,13 +86,12 @@ export class LambdaInvokeAction extends codepipeline.Action { .addAction('lambda:InvokeFunction') .addResource(this.props.lambda.functionArn)); - // allow lambda to put job results for this pipeline. - const addToPolicy = this.props.addPutJobResultPolicy !== undefined ? this.props.addPutJobResultPolicy : true; - if (addToPolicy) { - this.props.lambda.addToRolePolicy(new iam.PolicyStatement() - .addAllResources() // to avoid cycles (see docs) - .addAction('codepipeline:PutJobSuccessResult') - .addAction('codepipeline:PutJobFailureResult')); - } + // allow lambda to put job results for this pipeline + // CodePipeline requires this to be granted to '*' + // (the Pipeline ARN will not be enough) + this.props.lambda.addToRolePolicy(new iam.PolicyStatement() + .addAllResources() + .addAction('codepipeline:PutJobSuccessResult') + .addAction('codepipeline:PutJobFailureResult')); } } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/test.lambda-invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/test.lambda-invoke-action.ts new file mode 100644 index 0000000000000..3e8e40894cde4 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/test.lambda-invoke-action.ts @@ -0,0 +1,129 @@ +import { expect, haveResourceLike } from "@aws-cdk/assert"; +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import lambda = require('@aws-cdk/aws-lambda'); +import { Aws, SecretValue, Stack, Token } from "@aws-cdk/cdk"; +import { Test } from 'nodeunit'; +import cpactions = require('../../lib'); + +// tslint:disable:object-literal-key-quotes + +export = { + 'Lambda invoke Action': { + 'properly serializes the object passed in userParameters'(test: Test) { + const stack = stackIncludingLambdaInvokeCodePipeline({ + key: 1234, + }); + + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + 'Stages': [ + {}, + { + 'Actions': [ + { + 'Configuration': { + 'UserParameters': '{"key":1234}', + }, + }, + ], + }, + ], + })); + + test.done(); + }, + + 'properly resolves any Tokens passed in userParameters'(test: Test) { + const stack = stackIncludingLambdaInvokeCodePipeline({ + key: new Token(() => Aws.region), + }); + + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + 'Stages': [ + {}, + { + 'Actions': [ + { + 'Configuration': { + 'UserParameters': { + 'Fn::Join': [ + '', + [ + '{"key":"', + { + 'Ref': 'AWS::Region', + }, + '"}', + ], + ], + }, + }, + }, + ], + }, + ], + })); + + test.done(); + }, + + 'properly resolves any stringified Tokens passed in userParameters'(test: Test) { + const stack = stackIncludingLambdaInvokeCodePipeline({ + key: new Token(() => null).toString(), + }); + + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + 'Stages': [ + {}, + { + 'Actions': [ + { + 'Configuration': { + 'UserParameters': '{"key":null}', + }, + }, + ], + }, + ], + })); + + test.done(); + }, + }, +}; + +function stackIncludingLambdaInvokeCodePipeline(userParams: { [key: string]: any }) { + const stack = new Stack(); + + new codepipeline.Pipeline(stack, 'Pipeline', { + stages: [ + { + name: 'Source', + actions: [ + new cpactions.GitHubSourceAction({ + actionName: 'GitHub', + output: new codepipeline.Artifact(), + oauthToken: SecretValue.plainText('secret'), + owner: 'awslabs', + repo: 'aws-cdk', + }), + ], + }, + { + name: 'Invoke', + actions: [ + new cpactions.LambdaInvokeAction({ + actionName: 'Lambda', + lambda: new lambda.Function(stack, 'Lambda', { + code: lambda.Code.cfnParameters(), + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810, + }), + userParameters: userParams, + }), + ], + }, + ], + }); + + return stack; +} diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts index 3c4bb6a7fb70e..442dd9c082adc 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts @@ -464,7 +464,6 @@ export = { const lambdaAction = new cpactions.LambdaInvokeAction({ actionName: 'InvokeAction', lambda: lambdaFun, - userParameters: 'foo-bar/42', inputs: [ source2Output, source1Output, @@ -510,7 +509,6 @@ export = { "FunctionName": { "Ref": "Function76856677" }, - "UserParameters": "foo-bar/42" }, "InputArtifacts": [ { "Name": "sourceArtifact2" },