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

fix(lambda-event-sources): cannot add sqs event source to an imported function #21970

Merged
merged 3 commits into from
Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as lambda from '@aws-cdk/aws-lambda';
import * as sqs from '@aws-cdk/aws-sqs';
import { Duration, Names, Token } from '@aws-cdk/core';
import { Duration, Names, Token, Annotations } from '@aws-cdk/core';

export interface SqsEventSourceProps {
/**
Expand Down Expand Up @@ -84,7 +84,14 @@ export class SqsEventSource implements lambda.IEventSource {
});
this._eventSourceMappingId = eventSourceMapping.eventSourceMappingId;

this.queue.grantConsumeMessages(target);
// only grant access if the lambda function has an IAM role
// otherwise the IAM module will throw an error
if (target.role) {
this.queue.grantConsumeMessages(target);
} else {
Annotations.of(target).addWarning(`Function '${target.node.path}' was imported without an IAM role `+
`so it was not granted access to consume messages from '${this.queue.node.path}'`);
Comment on lines +92 to +93
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is already causing an error to be thrown on synth, what is the benefit of adding a warning as well? Aren't these emitted on synth as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the grantConsumeMessages that causes an error to be thrown. So this PR essentially changes the error to a warning.

}
}

/**
Expand Down
109 changes: 109 additions & 0 deletions packages/@aws-cdk/aws-lambda-event-sources/test/sqs.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Template } from '@aws-cdk/assertions';
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import * as sqs from '@aws-cdk/aws-sqs';
import * as cdk from '@aws-cdk/core';
import { App } from '@aws-cdk/core';
import * as sources from '../lib';
import { TestFunction } from './test-function';

Expand Down Expand Up @@ -282,8 +284,115 @@ describe('SQSEventSource', () => {
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::EventSourceMapping', {
'FunctionResponseTypes': ['ReportBatchItemFailures'],
});
});

test('warning added if lambda function imported without role', () => {
const app = new App();
const stack = new cdk.Stack(app);
const fn = lambda.Function.fromFunctionName(stack, 'Handler', 'testFunction');
const q = new sqs.Queue(stack, 'Q');

// WHEN
fn.addEventSource(new sources.SqsEventSource(q));
const assembly = app.synth();

const messages = assembly.getStackArtifact(stack.artifactId).messages;

// THEN
expect(messages.length).toEqual(1);
expect(messages[0]).toMatchObject({
level: 'warning',
id: '/Default/Handler',
entry: {
data: expect.stringMatching(/Function 'Default\/Handler' was imported without an IAM role/),
},
});

// THEN
Template.fromStack(stack).resourceCountIs('AWS::Lambda::EventSourceMapping', 1);
Template.fromStack(stack).resourceCountIs('AWS::IAM::Policy', 0);
});

test('policy added to imported function role', () => {
// GIVEN
const stack = new cdk.Stack();
const fn = lambda.Function.fromFunctionAttributes(stack, 'Handler', {
functionArn: stack.formatArn({
service: 'lambda',
resource: 'function',
resourceName: 'testFunction',
}),
role: iam.Role.fromRoleName(stack, 'Role', 'testFunctionRole'),
});
const q = new sqs.Queue(stack, 'Q');

// WHEN
fn.addEventSource(new sources.SqsEventSource(q));

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
'PolicyDocument': {
'Statement': [
{
'Action': [
'sqs:ReceiveMessage',
'sqs:ChangeMessageVisibility',
'sqs:GetQueueUrl',
'sqs:DeleteMessage',
'sqs:GetQueueAttributes',
],
'Effect': 'Allow',
'Resource': {
'Fn::GetAtt': [
'Q63C6E3AB',
'Arn',
],
},
},
],
'Version': '2012-10-17',
},
'Roles': ['testFunctionRole'],
});

Template.fromStack(stack).hasResourceProperties('AWS::Lambda::EventSourceMapping', {
'EventSourceArn': {
'Fn::GetAtt': [
'Q63C6E3AB',
'Arn',
],
},
'FunctionName': {
'Fn::Select': [
6,
{
'Fn::Split': [
':',
{
'Fn::Join': [
'',
[
'arn:',
{
'Ref': 'AWS::Partition',
},
':lambda:',
{
'Ref': 'AWS::Region',
},
':',
{
'Ref': 'AWS::AccountId',
},
':function/testFunction',
],
],
},
],
},
],
},
});
});

test('adding filter criteria', () => {
Expand Down