Skip to content

Commit

Permalink
Add ResourceName property to the SQSEvent attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
96malhar committed Jun 11, 2024
1 parent 45a5282 commit 0dce29f
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ public static SQSEventAttribute Build(AttributeData att)

foreach (var pair in att.NamedArguments)
{
if (pair.Key == nameof(data.ResourceName) && pair.Value.Value is string resourceName)
{
data.ResourceName = resourceName;
}
if (pair.Key == nameof(data.BatchSize) && pair.Value.Value is uint batchSize)
{
data.BatchSize = batchSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,7 @@ private string ProcessHttpApiAttribute(ILambdaFunctionSerializable lambdaFunctio
/// </summary>
private string ProcessSqsAttribute(ILambdaFunctionSerializable lambdaFunction, SQSEventAttribute att)
{
var eventName = att.Queue.StartsWith("@") ? att.Queue.Substring(1) : GetResourceNameFromArn(att.Queue);
var eventPath = $"Resources.{lambdaFunction.ResourceName}.Properties.Events.{eventName}";
var eventPath = $"Resources.{lambdaFunction.ResourceName}.Properties.Events.{att.ResourceName}";

// Set event type - https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-eventsource.html#sam-function-eventsource-type
_templateWriter.SetToken($"{eventPath}.Type", "SQS");
Expand Down Expand Up @@ -330,7 +329,7 @@ private string ProcessSqsAttribute(ILambdaFunctionSerializable lambdaFunction, S
_templateWriter.SetToken($"{eventPropertiesPath}.ScalingConfig.MaximumConcurrency", att.MaximumConcurrency);
}

return eventName;
return att.ResourceName;
}

/// <summary>
Expand Down Expand Up @@ -545,16 +544,5 @@ private string ExtractCurrentDescriptionSuffix(string templateDescription)

return string.Empty;
}

private string GetResourceNameFromArn(string arn)
{
const string malformedErrorMessage = "ARN is in incorrect format. ARN format is: arn:<partition>:<service>:<region>:<account-id>:<resource>";
var tokens = arn.Split(new char[] { ':' }, 6);
if (tokens.Length != 6)
{
throw new ArgumentException(malformedErrorMessage);
}
return tokens[5];
}
}
}
9 changes: 5 additions & 4 deletions Libraries/src/Amazon.Lambda.Annotations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,7 @@ This example shows how users can use the `SQSEvent` attribute to set up event so

The `SQSEvent` attribute contains the following properties:
* **Queue** (Required) - The SQS queue that will act as the event trigger for the Lambda function. This can either be the queue ARN or reference to the SQS queue resource that is already defined in the serverless template. To reference a SQS queue resource in the serverless template, prefix the resource name with "@" symbol.
* **ResourceName** (Optional) - The CloudFormation resource name for the SQS event source mapping. By default this is set to the SQS queue name if `Queue` is set to an SQS queue ARN. If `Queue` is set to an existing CloudFormation resource, than that is used as the default value without the "@" prefix.
* **Enabled** (Optional) - If set to false, the event source mapping will be disabled and message polling will be paused. Default value is true.
* **BatchSize** (Optional) - The maximum number of messages that will be sent for processing in a single batch. This value must be between 1 to 10000. Default value is 10.
* **MaximumBatchingWindowInSeconds** (Optional) - The maximum amount of time, in seconds, to gather records before invoking the function. This value must be between 0 to 300. Default value is 0.
Expand All @@ -746,10 +747,10 @@ report `ReportBatchItemFailures`
```csharp

[LambdaFunction(ResourceName = "SQSMessageHandler", Policies = "AWSLambdaSQSQueueExecutionRole", PackageType = LambdaPackageType.Image)]
[SQSEvent("@TestQueue", BatchSize = 50, MaximumConcurrency = 5, MaximumBatchingWindowInSeconds = 5, Filters = "{ \"body\" : { \"RequestCode\" : [ \"BBBB\" ] } }")]
[SQSEvent("@TestQueue", ResourceName = "TestQueueEvent", BatchSize = 50, MaximumConcurrency = 5, MaximumBatchingWindowInSeconds = 5, Filters = "{ \"body\" : { \"RequestCode\" : [ \"BBBB\" ] } }")]
public SQSBatchResponse HandleMessage(SQSEvent evnt, ILambdaContext lambdaContext)
{
lambdaContext.Logger.Log($"Received {evnt.Records.Count} messages");
lambdaContext.Logger.Log($"Received {evnt.Records.Count} messages");
return new SQSBatchResponse();
}
```
Expand All @@ -763,7 +764,7 @@ The following SQS event source mapping will be generated for the `SQSMessageHand
"Metadata": {
"Tool": "Amazon.Lambda.Annotations",
"SyncedEvents": [
"TestQueue"
"TestQueueEvent"
]
},
"Properties": {
Expand All @@ -780,7 +781,7 @@ The following SQS event source mapping will be generated for the `SQSMessageHand
]
},
"Events": {
"TestQueue": {
"TestQueueEvent": {
"Type": "SQS",
"Properties": {
"Queue": {
Expand Down
46 changes: 46 additions & 0 deletions Libraries/src/Amazon.Lambda.Annotations/SQS/SQSEventAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Amazon.Lambda.Annotations.SQS
{
Expand All @@ -13,13 +15,45 @@ public class SQSEventAttribute : Attribute
// .NET attributes cannot be nullable. To work around this, we have added nullable backing fields to all optional properties and added an internal Is<PropertyName>Set method to identify which properties were explicitly set the customer.
// These internal methods are used by the CloudFormationWriter while deciding which properties to write in the CF template.

// Only allow alphanumeric characters
private static readonly Regex _resourceNameRegex = new Regex("^[a-zA-Z0-9]+$");

/// <summary>
/// The SQS queue that will act as the event trigger for the Lambda function.
/// This can either be the queue ARN or reference to the SQS queue resource that is already defined in the serverless template.
/// To reference a SQS queue resource in the serverless template, prefix the resource name with "@" symbol.
/// </summary>
public string Queue { get; set; }

/// <summary>
/// The CloudFormation resource name for the SQS event source mapping. By default this is set to the SQS queue name if the <see cref="Queue"/> is set to an SQS queue ARN.
/// If <see cref="Queue"/> is set to an existing CloudFormation resource, than that is used as the default value without the "@" prefix.
/// </summary>
public string ResourceName
{
get
{
if (IsResourceNameSet)
{
return resourceName;
}
if (Queue.StartsWith("@"))
{
return Queue.Substring(1);
}

var arnTokens = Queue.Split(new char[] { ':' }, 6);
var queueName = arnTokens[5];
var sanitizedQueueName = string.Join(string.Empty, queueName.Where(char.IsLetterOrDigit));
return sanitizedQueueName;
}
set => resourceName = value;
}

private string resourceName { get; set; } = null;
internal bool IsResourceNameSet => resourceName != null;


/// <summary>
/// If set to false, the event source mapping will be disabled and message polling will be paused.
/// Default value is true.
Expand Down Expand Up @@ -100,6 +134,18 @@ internal List<string> Validate()
{
validationErrors.Add($"{nameof(SQSEventAttribute.MaximumBatchingWindowInSeconds)} = {MaximumBatchingWindowInSeconds}. It must be between 0 and 300");
}
if (!Queue.StartsWith("@"))
{
var arnTokens = Queue.Split(new char[] { ':' }, 6);
if (arnTokens.Length != 6)
{
validationErrors.Add($"{nameof(SQSEventAttribute.Queue)} = {Queue}. The SQS queue ARN is invalid. The ARN format is 'arn:<partition>:sqs:<region>:<account-id>:<queue-name>'");
}
}
if (IsResourceNameSet && !_resourceNameRegex.IsMatch(ResourceName))
{
validationErrors.Add($"{nameof(SQSEventAttribute.ResourceName)} = {ResourceName}. It must only contain alphanumeric characters and must not be an empty string");
}

return validationErrors;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"Tool": "Amazon.Lambda.Annotations",
"SyncedEvents": [
"queue1",
"queue2"
"queue2",
"myqueue",
"testQueueEvent"
]
},
"Properties": {
Expand Down Expand Up @@ -50,6 +52,23 @@
"Enabled": false,
"MaximumBatchingWindowInSeconds": 5
}
},
"myqueue": {
"Type": "SQS",
"Properties": {
"Queue": "arn:aws:sqs:us-east-2:444455556666:my-queue"
}
},
"testQueueEvent": {
"Type": "SQS",
"Properties": {
"Queue": {
"Fn::GetAtt": [
"testQueue",
"Arn"
]
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1249,7 +1249,19 @@ public async Task VerifyInvalidSQSEvents_ThrowsCompilationErrors()
DiagnosticResult
.CompilerError("AWSLambda0102")
.WithSpan($"TestServerlessApp{Path.DirectorySeparatorChar}SQSEventExamples{Path.DirectorySeparatorChar}InvalidSQSEvents.cs", 37, 9, 43, 10)
.WithMessage("Multiple event attributes on LambdaFunction are not supported")
.WithMessage("Multiple event attributes on LambdaFunction are not supported"),

DiagnosticResult.CompilerError("AWSLambda0116")
.WithSpan($"TestServerlessApp{Path.DirectorySeparatorChar}SQSEventExamples{Path.DirectorySeparatorChar}InvalidSQSEvents.cs", 45, 9, 50, 10)
.WithArguments("Queue = test-queue. The SQS queue ARN is invalid. The ARN format is 'arn:<partition>:sqs:<region>:<account-id>:<queue-name>'"),

DiagnosticResult.CompilerError("AWSLambda0116")
.WithSpan($"TestServerlessApp{Path.DirectorySeparatorChar}SQSEventExamples{Path.DirectorySeparatorChar}InvalidSQSEvents.cs", 52, 9, 57, 10)
.WithArguments("ResourceName = sqs-event-source. It must only contain alphanumeric characters and must not be an empty string"),

DiagnosticResult.CompilerError("AWSLambda0116")
.WithSpan($"TestServerlessApp{Path.DirectorySeparatorChar}SQSEventExamples{Path.DirectorySeparatorChar}InvalidSQSEvents.cs", 59, 9, 64, 10)
.WithArguments("ResourceName = . It must only contain alphanumeric characters and must not be an empty string"),
}
}
}.RunAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ namespace TestServerlessApp.SQSEventExamples
public class InvalidSQSEvents
{
[LambdaFunction]
[SQSEvent("test-queue", BatchSize = 0, MaximumBatchingWindowInSeconds = 302, MaximumConcurrency = 1)]
[SQSEvent("@testQueue", BatchSize = 0, MaximumBatchingWindowInSeconds = 302, MaximumConcurrency = 1)]
public void ProcessMessageWithInvalidSQSEventAttributes(SQSEvent evnt)
{
Console.WriteLine($"Event processed: {evnt}");
}

[LambdaFunction]
[SQSEvent("test-queue")]
[SQSEvent("@testQueue")]
public void ProcessMessageWithInvalidParameters(SQSEvent evnt, bool invalidParameter1, int invalidParameter2)
{
Console.WriteLine($"Event processed: {evnt}");
}

[LambdaFunction]
[SQSEvent("test-queue")]
[SQSEvent("@testQueue")]
public bool ProcessMessageWithInvalidReturnType(SQSEvent evnt)
{
Console.WriteLine($"Event processed: {evnt}");
Expand All @@ -36,10 +36,31 @@ namespace TestServerlessApp.SQSEventExamples

[LambdaFunction]
[RestApi(LambdaHttpMethod.Get, "/")]
[SQSEvent("test-queue")]
[SQSEvent("@testQueue")]
public void ProcessMessageWithMultipleEventTypes(SQSEvent evnt)
{
Console.WriteLine($"Event processed: {evnt}");
}

[LambdaFunction]
[SQSEvent("test-queue")]
public void ProcessMessageWithInvalidQueueArn(SQSEvent evnt)
{
Console.WriteLine($"Event processed: {evnt}");
}

[LambdaFunction]
[SQSEvent("@testQueue", ResourceName = "sqs-event-source")]
public void ProcessMessageWithInvalidResourceName(SQSEvent evnt)
{
Console.WriteLine($"Event processed: {evnt}");
}

[LambdaFunction]
[SQSEvent("@testQueue", ResourceName = "")]
public void ProcessMessageWithEmptyResourceName(SQSEvent evnt)
{
Console.WriteLine($"Event processed: {evnt}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace TestServerlessApp.SQSEventExamples
[LambdaFunction]
[SQSEvent("arn:aws:sqs:us-east-2:444455556666:queue1", BatchSize = 50, MaximumConcurrency = 30, Filters = "My-Filter-1; My-Filter-2")]
[SQSEvent("arn:aws:sqs:us-east-2:444455556666:queue2", MaximumBatchingWindowInSeconds = 5, Enabled = false)]
[SQSEvent("arn:aws:sqs:us-east-2:444455556666:my-queue")]
[SQSEvent("@testQueue", ResourceName = "testQueueEvent")]
public void ProcessMessages(SQSEvent evnt)
{
Console.WriteLine($"Event processed: {evnt}");
Expand Down
2 changes: 1 addition & 1 deletion Libraries/test/TestServerlessApp/SqsMessageProcessing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace TestServerlessApp
public class SqsMessageProcessing
{
[LambdaFunction(ResourceName = "SQSMessageHandler", Policies = "AWSLambdaSQSQueueExecutionRole", PackageType = LambdaPackageType.Image)]
[SQSEvent("@TestQueue", BatchSize = 50, MaximumConcurrency = 5, MaximumBatchingWindowInSeconds = 5, Filters = "{ \"body\" : { \"RequestCode\" : [ \"BBBB\" ] } }")]
[SQSEvent("@TestQueue", ResourceName = "TestQueueEvent", BatchSize = 50, MaximumConcurrency = 5, MaximumBatchingWindowInSeconds = 5, Filters = "{ \"body\" : { \"RequestCode\" : [ \"BBBB\" ] } }")]
public SQSBatchResponse HandleMessage(SQSEvent evnt, ILambdaContext lambdaContext)
{
lambdaContext.Logger.Log($"Received {evnt.Records.Count} messages");
Expand Down
4 changes: 2 additions & 2 deletions Libraries/test/TestServerlessApp/serverless.template
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@
"Metadata": {
"Tool": "Amazon.Lambda.Annotations",
"SyncedEvents": [
"TestQueue"
"TestQueueEvent"
]
},
"Properties": {
Expand All @@ -793,7 +793,7 @@
]
},
"Events": {
"TestQueue": {
"TestQueueEvent": {
"Type": "SQS",
"Properties": {
"Queue": {
Expand Down

0 comments on commit 0dce29f

Please sign in to comment.