diff --git a/.autover/changes/d530dfa0-e51a-4bcb-8061-835237914aee.json b/.autover/changes/d530dfa0-e51a-4bcb-8061-835237914aee.json
new file mode 100644
index 0000000..9005e28
--- /dev/null
+++ b/.autover/changes/d530dfa0-e51a-4bcb-8061-835237914aee.json
@@ -0,0 +1,12 @@
+{
+ "Projects": [
+ {
+ "Name": "Aspire.Hosting.AWS",
+ "Type": "Patch",
+ "ChangelogMessages": [
+ "Add support for configuring SQS event source for a Lambda function",
+ "Update version of Amazon.Lambda.TestTool to install to version 0.10.0"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index a879622..3226850 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,8 @@
*.userosscache
*.sln.docstates
+playground/**/launchSettings.json
+
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
@@ -402,4 +404,7 @@ FodyWeavers.xsd
# JetBrains Rider
*.sln.iml
-.idea/
\ No newline at end of file
+.idea/
+
+# CDK temp files
+**/cdk.out/**
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 51f5d72..1cb3572 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -28,6 +28,7 @@
+
diff --git a/integrations-on-dotnet-aspire-for-aws.sln b/integrations-on-dotnet-aspire-for-aws.sln
index 6e1bd32..aa77e3a 100644
--- a/integrations-on-dotnet-aspire-for-aws.sln
+++ b/integrations-on-dotnet-aspire-for-aws.sln
@@ -44,6 +44,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAWSCallsLambdaFunction",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebCalculatorFunctions", "playground\Lambda\WebCalculatorFunctions\WebCalculatorFunctions.csproj", "{4CE69424-50B4-1D2F-EA87-E9FC81C4BEA6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQSProcessorFunction", "playground\Lambda\SQSProcessorFunction\SQSProcessorFunction.csproj", "{FC2267CD-9769-68B1-A271-774F98326A43}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -102,6 +104,10 @@ Global
{4CE69424-50B4-1D2F-EA87-E9FC81C4BEA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CE69424-50B4-1D2F-EA87-E9FC81C4BEA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CE69424-50B4-1D2F-EA87-E9FC81C4BEA6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FC2267CD-9769-68B1-A271-774F98326A43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FC2267CD-9769-68B1-A271-774F98326A43}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FC2267CD-9769-68B1-A271-774F98326A43}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FC2267CD-9769-68B1-A271-774F98326A43}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -122,6 +128,7 @@ Global
{A2750C2D-1F82-47CB-9EAB-B819E3EBDD74} = {C3C0B096-DB7C-4D1B-B8A0-91631B32B4DE}
{96396A08-6FB9-49C2-A923-1AF1C97087EF} = {C3C0B096-DB7C-4D1B-B8A0-91631B32B4DE}
{4CE69424-50B4-1D2F-EA87-E9FC81C4BEA6} = {C3C0B096-DB7C-4D1B-B8A0-91631B32B4DE}
+ {FC2267CD-9769-68B1-A271-774F98326A43} = {C3C0B096-DB7C-4D1B-B8A0-91631B32B4DE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FBA55172-92F1-4495-A082-E0ABE4F4AF09}
diff --git a/playground/Lambda/Lambda.AppHost/Lambda.AppHost.csproj b/playground/Lambda/Lambda.AppHost/Lambda.AppHost.csproj
index ac2cb8e..a341d47 100644
--- a/playground/Lambda/Lambda.AppHost/Lambda.AppHost.csproj
+++ b/playground/Lambda/Lambda.AppHost/Lambda.AppHost.csproj
@@ -6,11 +6,14 @@
Exe$(DefaultTargetFramework)true
+ $(NoWarn);CS8002
+
+
diff --git a/playground/Lambda/Lambda.AppHost/Program.cs b/playground/Lambda/Lambda.AppHost/Program.cs
index d7f7c5c..a8c1f99 100644
--- a/playground/Lambda/Lambda.AppHost/Program.cs
+++ b/playground/Lambda/Lambda.AppHost/Program.cs
@@ -1,5 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
using Amazon.Lambda;
+using Aspire.Hosting;
using Aspire.Hosting.AWS.Lambda;
#pragma warning disable CA2252 // This API requires opting into preview features
@@ -8,6 +9,9 @@
var awsSdkConfig = builder.AddAWSSDKConfig().WithRegion(Amazon.RegionEndpoint.USWest2);
+var cdkStackResource = builder.AddAWSCDKStack("AWSLambdaPlaygroundResources");
+var sqsDemoQueue = cdkStackResource.AddSQSQueue("DemoQueue");
+
builder.AddAWSLambdaFunction("ToUpperFunction", lambdaHandler: "ToUpperLambdaFunctionExecutable", new LambdaFunctionOptions { ApplicationLogLevel = ApplicationLogLevel.DEBUG, LogFormat = LogFormat.JSON});
var defaultRouteLambda = builder.AddAWSLambdaFunction("LambdaDefaultRoute", lambdaHandler: "WebDefaultLambdaFunction");
@@ -30,5 +34,11 @@
.WithReference(multiplyFunction, Method.Get, "/multiply/{x}/{y}")
.WithReference(divideFunction, Method.Get, "/divide/{x}/{y}");
+
+builder.AddAWSLambdaFunction("SQSProcessorFunction", "SQSProcessorFunction::SQSProcessorFunction.Function::FunctionHandler")
+ .WithReference(awsSdkConfig)
+ .WithSQSEventSource(sqsDemoQueue);
+
+
builder.Build().Run();
\ No newline at end of file
diff --git a/playground/Lambda/Lambda.AppHost/Properties/launchSettings.json b/playground/Lambda/Lambda.AppHost/Properties/launchSettings.json
index c42f360..769afcf 100644
--- a/playground/Lambda/Lambda.AppHost/Properties/launchSettings.json
+++ b/playground/Lambda/Lambda.AppHost/Properties/launchSettings.json
@@ -5,7 +5,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
- "applicationUrl": "https://localhost:17295;http://localhost:15136",
+ "applicationUrl": "https://localhost:17296;http://localhost:15137",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
diff --git a/playground/Lambda/SQSProcessorFunction/Function.cs b/playground/Lambda/SQSProcessorFunction/Function.cs
new file mode 100644
index 0000000..a6494b3
--- /dev/null
+++ b/playground/Lambda/SQSProcessorFunction/Function.cs
@@ -0,0 +1,44 @@
+using Amazon.Lambda.Core;
+using Amazon.Lambda.SQSEvents;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using OpenTelemetry.Instrumentation.AWSLambda;
+using OpenTelemetry.Trace;
+
+// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
+[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
+
+namespace SQSProcessorFunction;
+
+public class Function
+{
+ IHost _host;
+ TracerProvider _traceProvider;
+
+ public Function()
+ {
+ var builder = new HostApplicationBuilder();
+
+ builder.AddServiceDefaults();
+ _host = builder.Build();
+
+ _traceProvider = _host.Services.GetRequiredService();
+ }
+
+ public Task FunctionHandler(SQSEvent evnt, ILambdaContext context)
+ => AWSLambdaWrapper.TraceAsync(_traceProvider, async (evnt, context) =>
+ {
+ foreach (var message in evnt.Records)
+ {
+ await ProcessMessageAsync(message, context);
+ }
+ }, evnt, context);
+
+ private async Task ProcessMessageAsync(SQSEvent.SQSMessage message, ILambdaContext context)
+ {
+ context.Logger.LogInformation($"Processed message {message.Body}");
+
+ // TODO: Do interesting work based on the new message
+ await Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/playground/Lambda/SQSProcessorFunction/SQSProcessorFunction.csproj b/playground/Lambda/SQSProcessorFunction/SQSProcessorFunction.csproj
new file mode 100644
index 0000000..74c6791
--- /dev/null
+++ b/playground/Lambda/SQSProcessorFunction/SQSProcessorFunction.csproj
@@ -0,0 +1,24 @@
+
+
+ net8.0
+ enable
+ enable
+ true
+ Lambda
+
+ true
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/playground/Lambda/SQSProcessorFunction/aws-lambda-tools-defaults.json b/playground/Lambda/SQSProcessorFunction/aws-lambda-tools-defaults.json
new file mode 100644
index 0000000..87d664e
--- /dev/null
+++ b/playground/Lambda/SQSProcessorFunction/aws-lambda-tools-defaults.json
@@ -0,0 +1,15 @@
+{
+ "Information": [
+ "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
+ "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
+ "dotnet lambda help",
+ "All the command line options for the Lambda command can be specified in this file."
+ ],
+ "profile": "default",
+ "region": "us-west-2",
+ "configuration": "Release",
+ "function-runtime": "dotnet8",
+ "function-memory-size": 512,
+ "function-timeout": 30,
+ "function-handler": "SQSProcessorFunction::SQSProcessorFunction.Function::FunctionHandler"
+}
\ No newline at end of file
diff --git a/playground/Lambda/WebCalculatorFunctions/Properties/launchSettings.json b/playground/Lambda/WebCalculatorFunctions/Properties/launchSettings.json
deleted file mode 100644
index daae879..0000000
--- a/playground/Lambda/WebCalculatorFunctions/Properties/launchSettings.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "profiles": {
- "Aspire_AddFunction": {
- "commandName": "Executable",
- "commandLineArgs": "exec --depsfile ./WebCalculatorFunctions.deps.json --runtimeconfig ./WebCalculatorFunctions.runtimeconfig.json %USERPROFILE%\\.dotnet\\tools\\.store\\amazon.lambda.testtool\\0.0.2-preview\\amazon.lambda.testtool\\0.0.2-preview\\content\\Amazon.Lambda.RuntimeSupport\\net8.0\\Amazon.Lambda.RuntimeSupport.dll WebCalculatorFunctions::WebCalculatorFunctions.Functions::AddFunctionHandler",
- "workingDirectory": ".\\bin\\$(Configuration)\\net8.0",
- "executablePath": "dotnet"
- },
- "Aspire_MinusFunction": {
- "commandName": "Executable",
- "commandLineArgs": "exec --depsfile ./WebCalculatorFunctions.deps.json --runtimeconfig ./WebCalculatorFunctions.runtimeconfig.json %USERPROFILE%\\.dotnet\\tools\\.store\\amazon.lambda.testtool\\0.0.2-preview\\amazon.lambda.testtool\\0.0.2-preview\\content\\Amazon.Lambda.RuntimeSupport\\net8.0\\Amazon.Lambda.RuntimeSupport.dll WebCalculatorFunctions::WebCalculatorFunctions.Functions::MinusFunctionHandler",
- "workingDirectory": ".\\bin\\$(Configuration)\\net8.0",
- "executablePath": "dotnet"
- },
- "Aspire_MultiplyFunction": {
- "commandName": "Executable",
- "commandLineArgs": "exec --depsfile ./WebCalculatorFunctions.deps.json --runtimeconfig ./WebCalculatorFunctions.runtimeconfig.json %USERPROFILE%\\.dotnet\\tools\\.store\\amazon.lambda.testtool\\0.0.2-preview\\amazon.lambda.testtool\\0.0.2-preview\\content\\Amazon.Lambda.RuntimeSupport\\net8.0\\Amazon.Lambda.RuntimeSupport.dll WebCalculatorFunctions::WebCalculatorFunctions.Functions::MultiplyFunctionHandler",
- "workingDirectory": ".\\bin\\$(Configuration)\\net8.0",
- "executablePath": "dotnet"
- },
- "Aspire_DivideFunction": {
- "commandName": "Executable",
- "commandLineArgs": "exec --depsfile ./WebCalculatorFunctions.deps.json --runtimeconfig ./WebCalculatorFunctions.runtimeconfig.json %USERPROFILE%\\.dotnet\\tools\\.store\\amazon.lambda.testtool\\0.0.2-preview\\amazon.lambda.testtool\\0.0.2-preview\\content\\Amazon.Lambda.RuntimeSupport\\net8.0\\Amazon.Lambda.RuntimeSupport.dll WebCalculatorFunctions::WebCalculatorFunctions.Functions::DivideFunctionHandler",
- "workingDirectory": ".\\bin\\$(Configuration)\\net8.0",
- "executablePath": "dotnet"
- }
- }
-}
\ No newline at end of file
diff --git a/playground/Lambda/WebCalculatorFunctions/WebCalculatorFunctions.csproj b/playground/Lambda/WebCalculatorFunctions/WebCalculatorFunctions.csproj
index 545d09b..70f0119 100644
--- a/playground/Lambda/WebCalculatorFunctions/WebCalculatorFunctions.csproj
+++ b/playground/Lambda/WebCalculatorFunctions/WebCalculatorFunctions.csproj
@@ -18,4 +18,7 @@
+
+
+
\ No newline at end of file
diff --git a/src/Aspire.Hosting.AWS/Constants.cs b/src/Aspire.Hosting.AWS/Constants.cs
index 76f92c0..28dbb5f 100644
--- a/src/Aspire.Hosting.AWS/Constants.cs
+++ b/src/Aspire.Hosting.AWS/Constants.cs
@@ -46,5 +46,5 @@ internal static class Constants
///
/// The default version of Amazon.Lambda.TestTool that will be automatically installed
///
- internal const string DefaultLambdaTestToolVersion = "0.9.1";
+ internal const string DefaultLambdaTestToolVersion = "0.10.0";
}
diff --git a/src/Aspire.Hosting.AWS/Lambda/SQSEventSourceExtensions.cs b/src/Aspire.Hosting.AWS/Lambda/SQSEventSourceExtensions.cs
new file mode 100644
index 0000000..0486d81
--- /dev/null
+++ b/src/Aspire.Hosting.AWS/Lambda/SQSEventSourceExtensions.cs
@@ -0,0 +1,116 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+using Amazon.CDK.AWS.Events.Targets;
+using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.AWS;
+using Aspire.Hosting.AWS.CDK;
+using Aspire.Hosting.AWS.CloudFormation;
+using Aspire.Hosting.AWS.Lambda;
+using Constructs;
+using System.Runtime.Versioning;
+using System.Text;
+
+#pragma warning disable IDE0130
+namespace Aspire.Hosting;
+
+///
+/// Extension methods adding SQS event source for Lambda functions.
+///
+[RequiresPreviewFeatures(Constants.LambdaPreviewMessage)]
+public static class SQSEventSourceExtensions
+{
+ ///
+ /// Add an SQS event source to a Lambda function. This feature emulates adding an SQS event source to a Lambda function when deployed to AWS.
+ /// A separate sub resource will be added to the .NET Aspire application that polls the SQS queue. As messages
+ /// are received from the queue the Lambda function will be invoked with the messages.
+ ///
+ /// The Lambda function to add the event source to.
+ /// The queue url for an SQS queue that will be polled for messages.
+ /// Optional configuration for the event source.
+ ///
+ ///
+ public static IResourceBuilder WithSQSEventSource(this IResourceBuilder lambdaFunction, string queueUrl, SQSEventSourceOptions? options = null)
+ {
+ return WithSQSEventSource(lambdaFunction, () => ValueTask.FromResult(queueUrl), options);
+ }
+
+ ///
+ /// Add an SQS event source to a Lambda function. This feature emulates adding an SQS event source to a Lambda function when deployed to AWS.
+ /// A separate sub resource will be added to the .NET Aspire application that polls the SQS queue. As messages
+ /// are received from the queue the Lambda function will be invoked with the messages.
+ ///
+ /// The Lambda function to add the event source to.
+ /// CDK SQS queue construct that will be polled for messages.
+ /// Optional configuration for the event source.
+ ///
+ ///
+ public static IResourceBuilder WithSQSEventSource(this IResourceBuilder lambdaFunction, IResourceBuilder> queue, SQSEventSourceOptions? options = null)
+ {
+ var queueOutputReference = queue.GetOutput("QueueUrl", queue => queue.QueueUrl);
+ return WithSQSEventSource(lambdaFunction, queueOutputReference, options);
+ }
+
+ ///
+ /// Add an SQS event source to a Lambda function. This feature emulates adding an SQS event source to a Lambda function when deployed to AWS.
+ /// A separate sub resource will be added to the .NET Aspire application that polls the SQS queue. As messages
+ /// are received from the queue the Lambda function will be invoked with the messages.
+ ///
+ /// The Lambda function to add the event source to.
+ /// CloudFormation StackOutputReference that should point to a SQS queue url output parameter in the CloudFormation stack.
+ /// Optional configuration for the event source.
+ ///
+ ///
+ public static IResourceBuilder WithSQSEventSource(this IResourceBuilder lambdaFunction, StackOutputReference queueCfnOutputReference, SQSEventSourceOptions? options = null)
+ {
+ Func> resolver = async () =>
+ {
+ var queueUrl = await queueCfnOutputReference.GetValueAsync();
+ if (string.IsNullOrEmpty(queueUrl))
+ {
+ throw new InvalidOperationException("Output parameter for queue url failed to resolve");
+ }
+
+ if (!Uri.TryCreate(queueUrl, UriKind.Absolute, out _))
+ {
+ throw new InvalidOperationException($"Output parameter value {queueUrl} is not a SQS queue url.");
+ }
+
+ return queueUrl;
+ };
+
+ return WithSQSEventSource(lambdaFunction, resolver, options);
+ }
+
+ private static IResourceBuilder WithSQSEventSource(IResourceBuilder lambdaFunction, Func> queueUrlResolver, SQSEventSourceOptions? options = null)
+ {
+ var sqsEventSourceResource = lambdaFunction.ApplicationBuilder.AddResource(new SQSEventSourceResource($"SQSEventSource-{lambdaFunction.Resource.Name}"))
+ .WithParentRelationship(lambdaFunction)
+ .ExcludeFromManifest();
+
+ sqsEventSourceResource.WithArgs(context =>
+ {
+ sqsEventSourceResource.Resource.AddCommandLineArguments(context.Args);
+ });
+
+ sqsEventSourceResource.WithEnvironment(async (context) =>
+ {
+ LambdaEmulatorAnnotation? lambdaEmulatorAnnotation = null;
+ if (lambdaFunction.ApplicationBuilder.Resources.FirstOrDefault(x => x.TryGetLastAnnotation(out lambdaEmulatorAnnotation)) == null ||
+ lambdaEmulatorAnnotation == null)
+ {
+ throw new InvalidOperationException("Lambda function is missing required annotations for Lambda emulator");
+ }
+
+ var queueUrl = await queueUrlResolver();
+
+ // Look to see if the Lambda function has been configured with an AWS SDK config. If so then
+ // configure the SQS event source with the same config to access the SQS queue.
+ var awsSdkConfig = lambdaFunction.Resource.Annotations.OfType().FirstOrDefault()?.SdkConfig;
+
+ var sqsEventConfig = SQSEventSourceResource.CreateSQSEventConfig(queueUrl, lambdaFunction.Resource.Name, lambdaEmulatorAnnotation.Endpoint.Url, options, awsSdkConfig);
+ context.EnvironmentVariables[SQSEventSourceResource.SQS_EVENT_CONFIG_ENV_VAR] = sqsEventConfig;
+ });
+
+ return lambdaFunction;
+ }
+}
diff --git a/src/Aspire.Hosting.AWS/Lambda/SQSEventSourceOptions.cs b/src/Aspire.Hosting.AWS/Lambda/SQSEventSourceOptions.cs
new file mode 100644
index 0000000..bffc4aa
--- /dev/null
+++ b/src/Aspire.Hosting.AWS/Lambda/SQSEventSourceOptions.cs
@@ -0,0 +1,26 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+namespace Aspire.Hosting.AWS.Lambda;
+
+///
+/// Optional settings for configuring an SQS event source for a Lambda function.
+///
+public class SQSEventSourceOptions
+{
+ ///
+ /// The batch size to read and send to Lambda function.
+ /// SQS will return with less then batch size if there are not enough messages in the queue.
+ ///
+ public int? BatchSize { get; set; }
+
+ ///
+ /// If true the messages read from the queue will not be deleted after being processed.
+ ///
+ public bool? DisableMessageDelete { get; set; }
+
+ ///
+ /// The visibility timeout used for messages read from the queue. This is the length the message will not be visible to be read
+ /// again once it is returned in the receive call.
+ ///
+ public int? VisibilityTimeout { get; set; }
+}
diff --git a/src/Aspire.Hosting.AWS/Lambda/SQSEventSourceResource.cs b/src/Aspire.Hosting.AWS/Lambda/SQSEventSourceResource.cs
new file mode 100644
index 0000000..00d26b9
--- /dev/null
+++ b/src/Aspire.Hosting.AWS/Lambda/SQSEventSourceResource.cs
@@ -0,0 +1,63 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+using Aspire.Hosting.ApplicationModel;
+using System.Text;
+
+namespace Aspire.Hosting.AWS.Lambda;
+
+internal class SQSEventSourceResource(string name) : ExecutableResource(name,
+ "dotnet",
+ Environment.CurrentDirectory
+ )
+{
+ internal const string SQS_EVENT_CONFIG_ENV_VAR = "SQS_EVENTSOURCE_CONFIG";
+ internal void AddCommandLineArguments(IList