Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(custom-resources): support custom lambda role in provider framework #12131

Merged
merged 9 commits into from
Jun 7, 2021
6 changes: 5 additions & 1 deletion packages/@aws-cdk/custom-resources/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,18 @@ the actual handler.
```ts
import { CustomResource } from '@aws-cdk/core';
import * as logs from '@aws-cdk/aws-logs';
import * as iam from '@aws-cdk/aws-iam';
import * as cr from '@aws-cdk/custom-resources';

const onEvent = new lambda.Function(this, 'MyHandler', { /* ... */ });

const myRole = new iam.Role(this, 'MyRole', { /* ... */ });

const myProvider = new cr.Provider(this, 'MyProvider', {
onEventHandler: onEvent,
isCompleteHandler: isComplete, // optional async "waiter"
logRetention: logs.RetentionDays.ONE_DAY // default is INFINITE
logRetention: logs.RetentionDays.ONE_DAY, // default is INFINITE
role: myRole, // must be assumable by the `lambda.amazonaws.com` service principal
});

new CustomResource(this, 'Resource1', { serviceToken: myProvider.serviceToken });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as path from 'path';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import * as logs from '@aws-cdk/aws-logs';
import { Duration } from '@aws-cdk/core';
Expand Down Expand Up @@ -105,6 +106,15 @@ export interface ProviderProps {
*/
readonly securityGroups?: ec2.ISecurityGroup[];

/**
* AWS Lambda execution role.
*
* The role that will be assumed by the AWS Lambda.
* Must be assumable by the 'lambda.amazonaws.com' service principal.
*
* @default - A default role will be created.
*/
readonly role?: iam.IRole;
}

/**
Expand Down Expand Up @@ -135,6 +145,7 @@ export class Provider extends CoreConstruct implements ICustomResourceProvider {
private readonly vpc?: ec2.IVpc;
private readonly vpcSubnets?: ec2.SubnetSelection;
private readonly securityGroups?: ec2.ISecurityGroup[];
private readonly role?: iam.IRole;

constructor(scope: Construct, id: string, props: ProviderProps) {
super(scope, id);
Expand All @@ -152,6 +163,8 @@ export class Provider extends CoreConstruct implements ICustomResourceProvider {
this.vpcSubnets = props.vpcSubnets;
this.securityGroups = props.securityGroups;

this.role = props.role;

const onEventFunction = this.createFunction(consts.FRAMEWORK_ON_EVENT_HANDLER_NAME);

if (this.isCompleteHandler) {
Expand All @@ -166,7 +179,6 @@ export class Provider extends CoreConstruct implements ICustomResourceProvider {
interval: retry.interval,
maxAttempts: retry.maxAttempts,
});

// the on-event entrypoint is going to start the execution of the waiter
onEventFunction.addEnvironment(consts.WAITER_STATE_MACHINE_ARN_ENV, waiterStateMachine.stateMachineArn);
waiterStateMachine.grantStartExecution(onEventFunction);
Expand Down Expand Up @@ -197,6 +209,7 @@ export class Provider extends CoreConstruct implements ICustomResourceProvider {
vpc: this.vpc,
vpcSubnets: this.vpcSubnets,
securityGroups: this.securityGroups,
role: this.role,
});

fn.addEnvironment(consts.USER_ON_EVENT_FUNCTION_ARN_ENV, this.onEventHandler.functionArn);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as path from 'path';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import * as logs from '@aws-cdk/aws-logs';
import { Duration, Stack } from '@aws-cdk/core';
Expand Down Expand Up @@ -342,3 +343,57 @@ describe('log retention', () => {
expect(stack).not.toHaveResource('Custom::LogRetention');
});
});

describe('role', () => {
it('uses custom role when present', () => {
// GIVEN
const stack = new Stack();

// WHEN
new cr.Provider(stack, 'MyProvider', {
onEventHandler: new lambda.Function(stack, 'MyHandler', {
code: lambda.Code.fromAsset(path.join(__dirname, './integration-test-fixtures/s3-file-handler')),
handler: 'index.onEvent',
runtime: lambda.Runtime.NODEJS_10_X,
}),
role: new iam.Role(stack, 'MyRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')],
}),
});

// THEN
expect(stack).toHaveResourceLike('AWS::Lambda::Function', {
Role: {
'Fn::GetAtt': [
'MyRoleF48FFE04',
'Arn',
],
},
});
});

it('uses default role otherwise', () => {
// GIVEN
const stack = new Stack();

// WHEN
new cr.Provider(stack, 'MyProvider', {
onEventHandler: new lambda.Function(stack, 'MyHandler', {
code: lambda.Code.fromAsset(path.join(__dirname, './integration-test-fixtures/s3-file-handler')),
handler: 'index.onEvent',
runtime: lambda.Runtime.NODEJS_10_X,
}),
});

// THEN
expect(stack).toHaveResourceLike('AWS::Lambda::Function', {
Role: {
'Fn::GetAtt': [
'MyProviderframeworkonEventServiceRole8761E48D',
'Arn',
],
},
});
});
});