diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Amazon.Lambda.Annotations.SourceGenerator.csproj b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Amazon.Lambda.Annotations.SourceGenerator.csproj index e9392872a..ee16e0794 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Amazon.Lambda.Annotations.SourceGenerator.csproj +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Amazon.Lambda.Annotations.SourceGenerator.csproj @@ -1,7 +1,7 @@  - 0.7.0.0 + 0.8.0.0 netstandard2.0 diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs index 79a32b1bb..29367ed93 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs +++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.IO; using System.Linq; using Amazon.Lambda.Annotations.APIGateway; using Amazon.Lambda.Annotations.SourceGenerator.Diagnostics; @@ -20,6 +21,9 @@ namespace Amazon.Lambda.Annotations.SourceGenerator.Writers public class CloudFormationWriter : IAnnotationReportWriter { private const string CREATION_TOOL = "Amazon.Lambda.Annotations"; + private const string PARAMETERS = "Parameters"; + private const string GET_ATTRIBUTE = "Fn::GetAtt"; + private const string REF = "Ref"; private readonly IFileManager _fileManager; private readonly IDirectoryManager _directoryManager; @@ -113,7 +117,7 @@ private void ProcessLambdaFunctionProperties(ILambdaFunctionSerializable lambdaF if (!string.IsNullOrEmpty(lambdaFunction.Role)) { - _templateWriter.SetToken($"{propertiesPath}.Role", _templateWriter.GetValueOrRef(lambdaFunction.Role)); + ProcessLambdaFunctionRole(lambdaFunction, $"{propertiesPath}.Role"); _templateWriter.RemoveToken($"{propertiesPath}.Policies"); } @@ -292,5 +296,33 @@ private void RemoveOrphanedLambdaFunctions(HashSet processedLambdaFuncti _templateWriter.RemoveToken($"Resources.{resourceName}"); } } + + /// + /// Write the IAM role associated with the Lambda function. + /// The IAM role is specified under 'Resources.FUNCTION_NAME.Properties.Role' path. + /// + private void ProcessLambdaFunctionRole(ILambdaFunctionSerializable lambdaFunction, string rolePath) + { + if (string.IsNullOrEmpty(lambdaFunction.Role)) + { + return; + } + + if (!lambdaFunction.Role.StartsWith("@")) + { + _templateWriter.SetToken(rolePath, lambdaFunction.Role); + return; + } + + var role = lambdaFunction.Role.Substring(1); + if (_templateWriter.Exists($"{PARAMETERS}.{role}")) + { + _templateWriter.SetToken($"{rolePath}.{REF}", role); + } + else + { + _templateWriter.SetToken($"{rolePath}.{GET_ATTRIBUTE}", new List{role, "Arn"}, TokenType.List); + } + } } } \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.Annotations.nuspec b/Libraries/src/Amazon.Lambda.Annotations.nuspec index b6c4368cd..ce1862fee 100644 --- a/Libraries/src/Amazon.Lambda.Annotations.nuspec +++ b/Libraries/src/Amazon.Lambda.Annotations.nuspec @@ -1,7 +1,7 @@ Amazon.Lambda.Annotations - 0.7.0-preview + 0.8.0-preview Amazon Web Services AWS Amazon Lambda Annotations that can be added to Lambda projects to generate C# code and CloudFormation templates. @@ -16,11 +16,13 @@ - - + + + + diff --git a/Libraries/src/Amazon.Lambda.Annotations/Amazon.Lambda.Annotations.csproj b/Libraries/src/Amazon.Lambda.Annotations/Amazon.Lambda.Annotations.csproj index d9cb767e8..5db9f009d 100644 --- a/Libraries/src/Amazon.Lambda.Annotations/Amazon.Lambda.Annotations.csproj +++ b/Libraries/src/Amazon.Lambda.Annotations/Amazon.Lambda.Annotations.csproj @@ -1,8 +1,9 @@ - + - 0.7.0.0 + 0.8.0.0 netstandard2.0 + true \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.Annotations/README.md b/Libraries/src/Amazon.Lambda.Annotations/README.md index d080cfb71..b41c7382a 100644 --- a/Libraries/src/Amazon.Lambda.Annotations/README.md +++ b/Libraries/src/Amazon.Lambda.Annotations/README.md @@ -1,12 +1,26 @@ # Amazon.Lambda.Annotations -The Lambda Annotations is a programming model for writing .NET Lambda function. At a high level the programming model allows -idiomatic .NET coding patterns and uses [C# Source Generators](https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview) to bridge the gap between the Lambda programming model -to the more idiomatic programming model. - -For example here is a simplistic example of a .NET Lambda function that acts like a calculator plus method using the normal -Lambda programming model. It respondes to an API Gateway REST API, pulls the operands from the resource paths, does the +Lambda Annotations is a programming model for writing .NET Lambda functions. At a high level the programming model allows +idiomatic .NET coding patterns. [C# Source Generators](https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview) are used to bridge the +gap between the Lambda programming model to the Lambda Annotations programming model. + +Topics: +* [How does Lambda Annotations work?](#how-does-lambda-annotations-work) +* [Dependency Injection integration](#dependency-injection-integration) +* [Synchronizing CloudFormation template](#synchronizing-cloudFormation-template) +* [Getting build information](#getting-build-information) +* [Amazon API Gateway example](#amazon-api-gateway-example) +* [Amazon S3 example](#amazon-s3-example) +* [Lambda .NET Attributes Reference](#lambda.net-attributes-reference) + +## How does Lambda Annotations work? + +The default experience for writing .NET Lambda functions is to write a .NET method that takes in an event object. From there boiler plate code is written to +parse the data out of the event object and synchronize the CloudFormation template to define the Lambda function and the .NET method to call +for each event. Here is a simplistic example of a .NET Lambda function that acts like a calculator plus method using the default Lambda programming model. It responds to +an API Gateway REST API, pulls the operands from the resource paths, does the addition and returns back an API Gateway response. + ```csharp public class Functions { @@ -40,7 +54,8 @@ public class Functions } ``` -Using Amazon.Lambda.Annotations the same Lambda function can be written like this. +Using Lambda Annotations the same Lambda function can remove a lot of that boiler plate code and write the method like this. + ```csharp public class Functions { @@ -53,9 +68,112 @@ public class Functions } ``` -## Using References To Other Resources and Parameters in the template +Lambda Annotations uses C# source generators to generate that boiler plate code to bridge the gap between the default Lambda programming model to Lambda Annotations programming model at compile time. +In addition the source generator also synchronizes the CloudFormation template to declare all of the .NET methods with the `LambdaFunction` attribute as +Lambda functions in the CloudFormation template. + +## Dependency Injection integration + +Lambda Annotations supports dependency injection. A class can be marked with a `LambdaStartup` attribute. The class will +have a `ConfigureServices` method for configuring services. + +The services can be injected by either constructor injection or using the `FromServices` attribute on a method parameter of +the function decorated with the `LambdaFunction` attribute. + +Services injected via the constructor have a lifecycle for the length of the Lambda compute container. For each Lambda +invocation a scope is created and the services injected using the `FromServices` attribute are created within the scope. + +Example startup class: +```csharp +[LambdaStartup] +public class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + services.AddAWSService(); + services.AddScoped(); + } +} +``` + +Example function using DI: +```csharp +public class Functions +{ + IAmazonS3 _s3Client; + + public Functions(IAmazonS3 s3Client) + { + _s3Client = s3Client; + } + + + [LambdaFunction] + [HttpApi(LambdaHttpMethod.Put, "/process/{name}")] + public async Task Process([FromServices] ITracker tracker, string name, [FromBody] string data) + { + tracker.Record(); + + await _s3Client.PutObjectAsync(new PutObjectRequest + { + BucketName = "storage-bucket", + Key = name, + ContentBody = data + }); + } +} +``` + +## Synchronizing CloudFormation template + +When the .NET project is compiled the Lambda Annotation source generator will synchronize all of the C# methods with the `LambdaFunction` attribute in the +project's CloudFormation template. Support is available for both JSON and YAML based CloudFormation templates. +The source generator identifies the CloudFormation template for the project by looking at the `template` property in the `aws-lambda-tools-defaults.json` +file. If the `template` property is absent, the source generator will default to `serverless.template` and create the file if it does not exist. + +The source generator synchronizes Lambda resources in the CloudFormation template. The template can still be edited to add additional AWS resources or to further customize the Lambda functions, such as adding other event sources that are not currently supported by Lambda Annotations attributes. + +When a .NET Method is synchronized to the CloudFormation template the source generator adds the `Tool` metadata property shown below. This metadata +links the CloudFormation resource to the source generator. If the `LambdaFunction` attribute is removed the C# method then the source generator +will remove the CloudFormation resource. To unlink the CloudFormation resource from the source generator +remove the `Tool` metadata property. + +``` + + ... + +"CloudCalculatorFunctionsAddGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootGet" + ] + }, + "Properties": { + "Runtime": "dotnet6", + "CodeUri": ".", + + ... +} +``` + +The `LambdaFunction` attribute contains properties that map to properties of the CloudFormation resource. For example in this snippet the Lambda function's `MemorySize` and `Timeout` +properties are set in the C# code. The source generator will synchronize these properties into the CloudFormation template. +```csharp +[LambdaFunction(MemorySize = 512, Timeout = 55)] +[HttpApi(LambdaHttpMethod.Get, "/add/{x}/{y}")] +public int Add(int x, int y, ILambdaContext context) +{ + context.Logger.LogInformation($"{x} plus {y} is {x + y}"); + return x + y; +} +``` + +Some CloudFormation properties are not set to a specific value but instead reference another resource or parameter defined in the CloudFormation template. To indicate the value for a +property of the .NET attribute is meant to reference another CloudFormation resource prefix the value with `@`. Here is an example of the `Role` for the Lambda function to reference +an IAM role defined in the CloudFormation template as `LambdaRoleParameter` -To use a reference to a Resource or Parameter in the template, prefix the value with `@`. Example shows using CloudFormation template Parameter named `LambdaRoleParameter` for the role of the Lambda function. ```csharp public class Functions { @@ -68,37 +186,142 @@ public class Functions } ``` -and place in your template: +```json + "CloudCalculatorFunctionsAddGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations", + "SyncedEvents": [ + "RootGet" + ] + }, + "Properties": { + "Runtime": "dotnet6", + +... + "Role": { + "Fn::GetAtt": [ + "LambdaRoleParameter", + "Arn" + ] + } + } + }, +``` + +## Amazon API Gateway example + +This example creates a REST API through Amazon API Gateway that exposes the common arithmetic operations. + +To avoid putting business logic inside the REST API a separate calculator service is created to encapsulate the logic of the arithmetic operations. Here is both the +calculator service's interface and default implementation. + +```csharp +public interface ICalculatorService +{ + int Add(int x, int y); + + int Subtract(int x, int y); + + int Multiply(int x, int y); + + int Divide(int x, int y); +} + +public class DefaultCalculatorService : ICalculatorService +{ + public int Add(int x, int y) => x + y; + + public int Subtract(int x, int y) => x - y; + + public int Multiply(int x, int y) => x * y; + + public int Divide(int x, int y) => x / y; +} +``` + +The startup class contains the `LambdaStartup` attribute identifying it as the class to configure the services registered in the dependency injection framework. +Here the `ICalculatorService` is registered as a singleton service in the collection of services. + +```csharp +[LambdaStartup] +public class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + } +} ``` - "Parameters": { - "LambdaExecutionRole": { - "Type": "String" +Since the `ICalculatorService` is registered as a singleton the service is injected into the Lambda function via the constructor. +If the registered service is registered as scoped or transient and a new instance is needed for each Lambda invocation then the +`FromServices` attribute should be used on a method parameter of the Lambda function. + +```csharp +public class Functions +{ + ICalculatorService _calculatorService; + public Functions(ICalculatorService calculatorService) + { + _calculatorService = calculatorService; } - }, + ... ``` -The above two examples when used together will the use the value of LambdaRoleParameter as the role during deployment. +For each arithmetic operation a separate C# method is added containing the `LambdaFunction` attribute. The `LambdaFunction` attribute +ensures the dependency injection framework is hooked up to the Lambda function and the Lambda function will be declared in the +CloudFormation template. -## Source Generator +Since these Lambda functions are responding to API Gateway events the `HttpApi` attribute is added +to register the event source in CloudFormation along with the HTTP verb and resource path. The `HttpApi` attribute also enables +mapping of the HTTP request components to method parameters. In this case the operands used for the arithmetic operations are +mapped from the resource path. Checkout the list of Lambda attributes in the reference section to see how to map other components +of the HTTP request to method parameters. -To bridge the gap from Lambda Annotations programming model to the normal programming model a .NET source generator is included in this package. -After adding the attributes to your .NET code the source generator will generate the translation code between the 2 programming models. It will also -keep in sync the generated information including a new function handler string into the CloudFormation template. The usage of source -generator is transparent to the user. The source generator supports syncing of both JSON and YAML CloudFormation templates. ```csharp -[LambdaFunction(Name = "Plus")] -[RestApi(LambdaHttpMethod.Get, "/plus/{x}/{y}")] -public int Plus(int x, int y) +[LambdaFunction()] +[HttpApi(LambdaHttpMethod.Get, "/add/{x}/{y}")] +public int Add(int x, int y, ILambdaContext context) { - return x + y; + context.Logger.LogInformation($"{x} plus {y} is {x + y}"); + return _calculatorService.Add(x, y); +} + +[LambdaFunction()] +[HttpApi(LambdaHttpMethod.Get, "/subtract/{x}/{y}")] +public int Subtract(int x, int y, ILambdaContext context) +{ + context.Logger.LogInformation($"{x} subtract {y} is {x - y}"); + return _calculatorService.Subtract(x, y); +} + +[LambdaFunction()] +[HttpApi(LambdaHttpMethod.Get, "/multiply/{x}/{y}")] +public int Multiply(int x, int y, ILambdaContext context) +{ + context.Logger.LogInformation($"{x} multiply {y} is {x * y}"); + return _calculatorService.Multiply(x, y); +} + +[LambdaFunction()] +[HttpApi(LambdaHttpMethod.Get, "/divide/{x}/{y}")] +public int Divide(int x, int y, ILambdaContext context) +{ + context.Logger.LogInformation($"{x} divide {y} is {x / y}"); + return _calculatorService.Divide(x, y); } ``` -The source generator adds the following entry in the CloudFormation template corresponding to the above Lambda function: + +For each `LambdaFunction` declared the source generator will update the CloudFormation template with the corresponding resource. +The Lambda CloudFormation resource has the `Handler` property set to the generated method by Lambda Annotations. This generated +method is where Lambda Annotations bridges the gap between the Lambda Annotation programming model and the Lambda programming model. +The `HttpApi` attribute also adds the API Gateway event source. + ```json -"Plus": { + "CloudCalculatorFunctionsAddGenerated": { "Type": "AWS::Serverless::Function", "Metadata": { "Tool": "Amazon.Lambda.Annotations", @@ -111,84 +334,283 @@ The source generator adds the following entry in the CloudFormation template cor "CodeUri": ".", "MemorySize": 256, "Timeout": 30, - "Policies": [ - "AWSLambdaBasicExecutionRole" - ], "PackageType": "Zip", - "Handler": "TestServerlessApp::TestServerlessApp.Functions_Plus_Generated::Plus", + "Handler": "CloudCalculator::CloudCalculator.Functions_Add_Generated::Add", "Events": { "RootGet": { - "Type": "Api", + "Type": "HttpApi", "Properties": { - "Path": "/plus/{x}/{y}", - "Method": "GET" + "Path": "/add/{x}/{y}", + "Method": "GET", + "PayloadFormatVersion": "2.0" + } } } } - } - } + }, ``` -To see the code that is generated by the source generator turn the verbosity to detailed when executing a build. From the command this -is done by using the `--verbosity` switch. -``` -dotnet build --verbosity detailed -``` -To change the verbosity in Visual Studio go to Tools -> Options -> Projects and Solutions and adjust the MSBuild verbosity drop down boxes. +Here is an example of the generated code from the source generator for the `Add` Lambda function. The generated code wraps around the +C# method that has the `LambdaFunction` attribute. It takes care of +configuring the dependency injection, gets the parameters from the API Gateway event and invokes the wrapped `LambdaFunction`. This code snippet is here for +informational purposes, as a user of the Lambda Annotations framework this code should not be needed to be seen. -## Dependency Injection +```csharp +public class Functions_Add_Generated +{ + private readonly ServiceProvider serviceProvider; -Lambda Annotations supports using dependency injection. A class can be marked with a `LambdaStartup` attribute. The class will -have a `ConfigureServices` method for configuring services. + public Functions_Add_Generated() + { + var services = new ServiceCollection(); -The services can be injected by either constructor injection or using the `FromServices` attribute on a method parameter of -the function decorated with the `LambdaFunction` attribute. + // By default, Lambda function class is added to the service container using the singleton lifetime + // To use a different lifetime, specify the lifetime in Startup.ConfigureServices(IServiceCollection) method. + services.AddSingleton(); -Services injected via the constructor have a lifecycle for the length of the Lambda compute container. For each Lambda -invocation a scope is created and the services injected using the `FromServices` attribute are created within the scope. + var startup = new CloudCalculator.Startup(); + startup.ConfigureServices(services); + serviceProvider = services.BuildServiceProvider(); + } + + public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse Add(Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest request, Amazon.Lambda.Core.ILambdaContext context) + { + // Create a scope for every request, + // this allows creating scoped dependencies without creating a scope manually. + using var scope = serviceProvider.CreateScope(); + var functions = scope.ServiceProvider.GetRequiredService(); + + var validationErrors = new List(); + + var x = default(int); + if (request.PathParameters?.ContainsKey("x") == true) + { + try + { + x = (int)Convert.ChangeType(request.PathParameters["x"], typeof(int)); + } + catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) + { + validationErrors.Add($"Value {request.PathParameters["x"]} at 'x' failed to satisfy constraint: {e.Message}"); + } + } + + var y = default(int); + if (request.PathParameters?.ContainsKey("y") == true) + { + try + { + y = (int)Convert.ChangeType(request.PathParameters["y"], typeof(int)); + } + catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) + { + validationErrors.Add($"Value {request.PathParameters["y"]} at 'y' failed to satisfy constraint: {e.Message}"); + } + } + + // return 400 Bad Request if there exists a validation error + if (validationErrors.Any()) + { + return new Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse + { + Body = @$"{{""message"": ""{validationErrors.Count} validation error(s) detected: {string.Join(",", validationErrors)}""}}", + Headers = new Dictionary + { + {"Content-Type", "application/json"}, + {"x-amzn-ErrorType", "ValidationException"} + }, + StatusCode = 400 + }; + } + + var response = functions.Add(x, y, context); + + var body = response.ToString(); + + return new Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse + { + Body = body, + Headers = new Dictionary + { + {"Content-Type", "application/json"} + }, + StatusCode = 200 + }; + } +} +``` + +## Amazon S3 example + +Lambda functions that are not using API Gateway can take advantage of Lambda Annotation's dependency injection integration and CloudFormation +synchronization features. This example is a Lambda function that responds to S3 events and resizes images that are uploaded to S3. + +The `Startup` class is used to register the services needed for the function. Two services are registered in this example. First is the +AWS SDK's S3 client. The second is the `IImageServices` to handle image manipulation. In this example the `IImageService` +is registered as a transient service so we can have a new instance created for every invocation. This is commonly needed if a +service has state that should not be preserved per invocation. -Example startup class: ```csharp [LambdaStartup] public class Startup { public void ConfigureServices(IServiceCollection services) { + // Using the AWSSDK.Extensions.NETCore.Setup package add the AWS SDK's S3 client services.AddAWSService(); - services.AddScoped(); + + // Add service for handling image manipulation. + // IImageServices is added as transient service so a new instance + // is created for each Lambda invocation. This can be important if services + // have state that should not be persisted per invocation. + services.AddTransient(); } } ``` -Example function using DI: +In the Lambda function the AWS SDK's S3 client is injected by the dependency injection framework via the constructor. The constructor is only ever called +once per Lambda invocation so for the `IImageServices` which was registered as transient it would not make sense to inject that service via the constructor. +Instead the `IImageServices` is injected as a method parameter using the `FromServices` attribute. That ensures each time the method is called a new instance +of `IImageServices` is created. + +On the `Resize` method the `LambdaFunction` attribute sets the `MemorySize` and `Timeout` properties for the Lambda function. The source generator will sync these +values to the corresponding properties in the CloudFormation template. The `Role` property is also set but in this case the value is prefixed with a `@`. +The `@` tells the source generator to treat the value for a role as a reference to another element in the CloudFormation template. In this case the +CloudFormation template defines an IAM role called `LambdaResizeImageRole` and the Lambda function should use that IAM role. + ```csharp public class Functions { - IAmazonS3 _s3Client; + private IAmazonS3 _s3Client; public Functions(IAmazonS3 s3Client) { _s3Client = s3Client; } - - [LambdaFunction] - [HttpApi(LambdaHttpMethod.Put, "/process/{name}", Version = HttpApiVersion.V2)] - public async Task Process([FromServices] ITracker tracker, string name, [FromBody] string data) + [LambdaFunction(MemorySize = 1024, Timeout = 120, Role = "@LambdaResizeImageRole")] + public async Task Resize([FromServices] IImageServices imageServices, S3Event evnt, ILambdaContext context) { - tracker.Record(); + var transferUtility = new TransferUtility(this._s3Client); - await _s3Client.PutObjectAsync(new PutObjectRequest + foreach(var record in evnt.Records) { - BucketName = "storage-bucket", - Key = name, - ContentBody = data - }); + var tempFile = Path.GetTempFileName(); + + // Download image from S3 + await transferUtility.DownloadAsync(tempFile, record.S3.Bucket.Name, record.S3.Object.Key); + + // Resize the image + var resizeImagePath = await imageServices.ResizeImageAsync(imagePath: tempFile, width: 50, height: 50); + + // Upload resized image to S3 with a "/thumbnails" prefix in the object key. + await transferUtility.UploadAsync(resizeImagePath, record.S3.Bucket.Name, "/thumbnails" + record.S3.Object.Key); + } + } +} +``` + +The source generator will create the Lambda function resource in the CloudFormation template. The source generator will sync the properties that were +defined in the `LambdaFunction` attribute. The Lambda function resources synchronized in the template can also be modified directly in the template as well. +In this example the function is modified to define the event source in this case to S3. + +```json + "ImageResizerFunctionFunctionsResizeGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "Runtime": "dotnet6", + "CodeUri": ".", + "MemorySize": 1024, + "Timeout": 120, + "PackageType": "Zip", + "Handler": "ImageResizerFunction::ImageResizerFunction.Functions_Resize_Generated::Resize", + "Role": { + "Fn::GetAtt": [ + "LambdaResizeImageRole", + "Arn" + ] + }, + "Events": { + "S3Objects": { + "Type": "S3", + "Properties": { + "Bucket": { + "Ref": "ImageBucket" + }, + "Filter": { + "S3Key": { + "Rules": [ + { + "Name": "prefix", + "Value": "/images" + } + ] + } + }, + "Events": [ + "s3:ObjectCreated:*" + ] + } + } + } + } + }, +``` + +This is the code the source generator will produce for this function. The constructor is handling setting up the dependency injection. During the generated `Resize` +method a dependency injection scope is created and then the `IImageServices` is retrieved from the dependency injection and passed into the function written +by the developer. By creating the scope in the generated `Resize` method all services registered as scoped or transient will trigger a new instance to be created +when retrieved from the dependency injection framework. This code snippet is here for +informational purposes, as a user of the Lambda Annotations framework this code should not be needed to be seen. + +```csharp +public class Functions_Resize_Generated +{ + private readonly ServiceProvider serviceProvider; + + public Functions_Resize_Generated() + { + var services = new ServiceCollection(); + + // By default, Lambda function class is added to the service container using the singleton lifetime + // To use a different lifetime, specify the lifetime in Startup.ConfigureServices(IServiceCollection) method. + services.AddSingleton(); + + var startup = new ImageResizerFunction.Startup(); + startup.ConfigureServices(services); + serviceProvider = services.BuildServiceProvider(); + } + + public async System.Threading.Tasks.Task Resize(Amazon.Lambda.S3Events.S3Event evnt, Amazon.Lambda.Core.ILambdaContext __context__) + { + // Create a scope for every request, + // this allows creating scoped dependencies without creating a scope manually. + using var scope = serviceProvider.CreateScope(); + var functions = scope.ServiceProvider.GetRequiredService(); + + var imageServices = scope.ServiceProvider.GetRequiredService(); + await functions.Resize(imageServices, evnt, __context__); } } ``` -## Lambda .NET Attributes +## Getting build information + +The source generator integrates with MSBuild's compiler error and warning reporting when there are problems generating the boiler plate code. + +To see the code that is generated by the source generator turn the verbosity to detailed when executing a build. From the command this +is done by using the `--verbosity` switch. +``` +dotnet build --verbosity detailed +``` +To change the verbosity in Visual Studio go to Tools -> Options -> Projects and Solutions and adjust the MSBuild verbosity drop down boxes. + + + +## Lambda .NET Attributes Reference List of .NET attributes currently supported. @@ -224,8 +646,4 @@ parameter to the `LambdaFunction` must be the event object and the event source ## Project References -If you are using API Gateway event attributes, such as `RestAPI` or `HttpAPI`, you need to manually add a package reference to `Amazon.Lambda.APIGatewayEvents` in your project, otherwise the project will not compile. We do not include it by default in order to keep the `Amazon.Lambda.Annotations` library lightweight. - -This release only supports API Gateway Events. As we add support for other types of events, such as S3 or DynamoDB, the list of required package references will depend on the Lambda .NET attributes you are using. - -**Note**: If you are using [dependency injection](#dependency-injection) to write functions for other service events , such as S3 or DynamoDB, you will need to reference these packages in your project as well \ No newline at end of file +If API Gateway event attributes, such as `RestAPI` or `HttpAPI`, are being used then a package reference to `Amazon.Lambda.APIGatewayEvents` must be added to the project, otherwise the project will not compile. We do not include it by default in order to keep the `Amazon.Lambda.Annotations` library lightweight. \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.Annotations/THIRD_PARTY_LICENSES b/Libraries/src/Amazon.Lambda.Annotations/THIRD_PARTY_LICENSES new file mode 100644 index 000000000..d41f36986 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.Annotations/THIRD_PARTY_LICENSES @@ -0,0 +1,23 @@ +** YamlDotNet; version 12.0.0 -- https://github.com/aaubry/YamlDotNet +Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Antoine Aubry and +contributors +** Newtonsoft.Json; version 13.0.1 -- https://www.nuget.org/packages/Newtonsoft.Json/ +Copyright (c) 2007 James Newton-King + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ComplexCalculator_Add_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ComplexCalculator_Add_Generated.g.cs index e302163cc..312c95e7c 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ComplexCalculator_Add_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ComplexCalculator_Add_Generated.g.cs @@ -47,7 +47,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ComplexCalculator_Subtract_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ComplexCalculator_Subtract_Generated.g.cs index 2983c62a1..f5beaa957 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ComplexCalculator_Subtract_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ComplexCalculator_Subtract_Generated.g.cs @@ -72,7 +72,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Functions_ToUpper_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Functions_ToUpper_Generated.g.cs index 45f7e592d..662c26890 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Functions_ToUpper_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Functions_ToUpper_Generated.g.cs @@ -33,7 +33,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Greeter_SayHelloAsync_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Greeter_SayHelloAsync_Generated.g.cs index 9d1280802..4a0a39c94 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Greeter_SayHelloAsync_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Greeter_SayHelloAsync_Generated.g.cs @@ -74,7 +74,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Greeter_SayHello_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Greeter_SayHello_Generated.g.cs index ed0b89cc3..5bfc77391 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Greeter_SayHello_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/Greeter_SayHello_Generated.g.cs @@ -74,7 +74,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Add_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Add_Generated.g.cs index 81dc165fb..6f85aec1e 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Add_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Add_Generated.g.cs @@ -102,7 +102,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_DivideAsync_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_DivideAsync_Generated.g.cs index d0bdd371f..2890ebab3 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_DivideAsync_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_DivideAsync_Generated.g.cs @@ -102,7 +102,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Multiply_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Multiply_Generated.g.cs index a66c58271..a74b3a3a3 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Multiply_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Multiply_Generated.g.cs @@ -100,7 +100,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Pi_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Pi_Generated.g.cs index e110c34f4..5beab706e 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Pi_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Pi_Generated.g.cs @@ -48,7 +48,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Random_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Random_Generated.g.cs index 0761f253e..c7210facf 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Random_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Random_Generated.g.cs @@ -47,7 +47,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Randoms_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Randoms_Generated.g.cs index eaf59a5a7..02d5bf776 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Randoms_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Randoms_Generated.g.cs @@ -47,7 +47,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Subtract_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Subtract_Generated.g.cs index 24eaa2548..b019700be 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Subtract_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/SimpleCalculator_Subtract_Generated.g.cs @@ -92,7 +92,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/TaskExample_TaskReturn_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/TaskExample_TaskReturn_Generated.g.cs index 1df3c7f47..a7810b6c4 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/TaskExample_TaskReturn_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/TaskExample_TaskReturn_Generated.g.cs @@ -33,7 +33,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/VoidExample_VoidReturn_Generated.g.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/VoidExample_VoidReturn_Generated.g.cs index ef9592e3a..b6d5c0030 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/VoidExample_VoidReturn_Generated.g.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/VoidExample_VoidReturn_Generated.g.cs @@ -33,7 +33,7 @@ private static void SetExecutionEnvironment() envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_"); } - envValue.Append("amazon-lambda-annotations_0.7.0.0"); + envValue.Append("amazon-lambda-annotations_0.8.0.0"); Environment.SetEnvironmentVariable(envName, envValue.ToString()); } diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/CloudFormationWriterTests.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/CloudFormationWriterTests.cs index 741aa1913..1c42a4530 100644 --- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/CloudFormationWriterTests.cs +++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/CloudFormationWriterTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Amazon.Lambda.Annotations.APIGateway; using Amazon.Lambda.Annotations.SourceGenerator; @@ -134,20 +135,58 @@ public void UseRefForPolicies(CloudFormationTemplateFormat templateFormat) public void UseRefForRole(CloudFormationTemplateFormat templateFormat) { // ARRANGE - var mockFileManager = GetMockFileManager(string.Empty); + const string jsonContent = @"{ + 'Parameters':{ + 'Basic':{ + 'Type':'String', + } + } + }"; + + const string yamlContent = @"Parameters: + Basic: + Type: String"; + + ITemplateWriter templateWriter = templateFormat == CloudFormationTemplateFormat.Json ? new JsonWriter() : new YamlWriter(); + var content = templateFormat == CloudFormationTemplateFormat.Json ? jsonContent : yamlContent; + + var mockFileManager = GetMockFileManager(content); var lambdaFunctionModel = GetLambdaFunctionModel("MyAssembly::MyNamespace.MyType::Handler", "TestMethod", 45, 512, "@Basic", null); var cloudFormationWriter = GetCloudFormationWriter(mockFileManager, _mockDirectoryManager, templateFormat, _diagnosticReporter); var report = GetAnnotationReport(new List {lambdaFunctionModel}); - ITemplateWriter templateWriter = templateFormat == CloudFormationTemplateFormat.Json ? new JsonWriter() : new YamlWriter(); - const string rolePath = "Resources.TestMethod.Properties.Role.Ref"; // ACT cloudFormationWriter.ApplyReport(report); // ASSERT + const string rolePath = "Resources.TestMethod.Properties.Role.Ref"; templateWriter.Parse(mockFileManager.ReadAllText(ServerlessTemplateFilePath)); Assert.Equal("Basic", templateWriter.GetToken(rolePath)); + Assert.False(templateWriter.Exists("Resources.TestMethod.Properties.Role.Fn::GetAtt")); + } + + [Theory] + [InlineData(CloudFormationTemplateFormat.Json)] + [InlineData(CloudFormationTemplateFormat.Yaml)] + public void UseFnGetForRole(CloudFormationTemplateFormat templateFormat) + { + ITemplateWriter templateWriter = templateFormat == CloudFormationTemplateFormat.Json ? new JsonWriter() : new YamlWriter(); + + var mockFileManager = GetMockFileManager(string.Empty); + var lambdaFunctionModel = GetLambdaFunctionModel("MyAssembly::MyNamespace.MyType::Handler", + "TestMethod", 45, 512, "@Basic", null); + var cloudFormationWriter = GetCloudFormationWriter(mockFileManager, _mockDirectoryManager, templateFormat, _diagnosticReporter); + var report = GetAnnotationReport(new List {lambdaFunctionModel}); + + // ACT + cloudFormationWriter.ApplyReport(report); + + // ASSERT + const string rolePath = "Resources.TestMethod.Properties.Role.Fn::GetAtt"; + templateWriter.Parse(mockFileManager.ReadAllText(ServerlessTemplateFilePath)); + Assert.Equal(new List{"Basic", "Arn"}, templateWriter.GetToken>(rolePath)); + Assert.False(templateWriter.Exists("Resources.TestMethod.Properties.Role.Ref")); } [Theory] diff --git a/RELEASE.CHANGELOG.md b/RELEASE.CHANGELOG.md index d0715201f..808a1bbd5 100644 --- a/RELEASE.CHANGELOG.md +++ b/RELEASE.CHANGELOG.md @@ -1,3 +1,16 @@ +### Release 2022-09-13 +* **Amazon.Lambda.Annotations (0.8.0-preview)** + * Add third party licenses + * Correctly populate the role property in CloudFormation template + * Copy XML documentation for Amazon.Lambda.Annotations into nupkg to drive IntelliSense + * Add more detail to the Lambda Annotations README.md +* **Amazon.Lambda.TestTool-3.1 (0.12.4)** + * Pull request [#1308](https://github.com/aws/aws-lambda-dotnet/pull/1308) fixing serialization of messages while monitoring dead letter queue. Thanks [William Keller](https://github.com/william-keller) +* **Amazon.Lambda.TestTool-5.0 (0.12.4)** + * Pull request [#1308](https://github.com/aws/aws-lambda-dotnet/pull/1308) fixing serialization of messages while monitoring dead letter queue. Thanks [William Keller](https://github.com/william-keller) +* **Amazon.Lambda.TestTool-6.0 (0.12.4)** + * Pull request [#1308](https://github.com/aws/aws-lambda-dotnet/pull/1308) fixing serialization of messages while monitoring dead letter queue. Thanks [William Keller](https://github.com/william-keller) + ### Release 2022-08-29 * **Amazon.Lambda.Annotations (0.7.0-preview)** * Add support for syncing YAML based CloudFormation templates diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester.csproj b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester.csproj index 6d82c40bb..f06a6d85d 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester.csproj +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester.csproj @@ -6,7 +6,7 @@ Exe A tool to help debug and test your .NET Core AWS Lambda functions locally. Latest - 0.12.3 + 0.12.4 AWS .NET Lambda Test Tool Apache 2 AWS;Amazon;Lambda diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester31-pack.csproj b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester31-pack.csproj index b3e700b3c..92e5f56b7 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester31-pack.csproj +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester31-pack.csproj @@ -5,7 +5,7 @@ Exe A tool to help debug and test your .NET Core 3.1 AWS Lambda functions locally. - 0.12.3 + 0.12.4 AWS .NET Lambda Test Tool Apache 2 AWS;Amazon;Lambda diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester50-pack.csproj b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester50-pack.csproj index 3b0df6453..e304c3b0b 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester50-pack.csproj +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester50-pack.csproj @@ -5,7 +5,7 @@ Exe A tool to help debug and test your .NET 5.0 AWS Lambda functions locally. - 0.12.3 + 0.12.4 AWS .NET Lambda Test Tool Apache 2 AWS;Amazon;Lambda diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester60-pack.csproj b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester60-pack.csproj index fc36167d7..7f7c9e04a 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester60-pack.csproj +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool.BlazorTester/Amazon.Lambda.TestTool.BlazorTester60-pack.csproj @@ -5,7 +5,7 @@ Exe A tool to help debug and test your .NET 6.0 AWS Lambda functions locally. - 0.12.3 + 0.12.4 AWS .NET Lambda Test Tool Apache 2 AWS;Amazon;Lambda diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/Runtime/DlqMonitor.cs b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/Runtime/DlqMonitor.cs index e023a47f1..11dfd0f2a 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/Runtime/DlqMonitor.cs +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/Runtime/DlqMonitor.cs @@ -1,10 +1,9 @@ -using System; +using Amazon.SQS.Model; +using System; using System.Collections.Generic; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Amazon.Lambda.TestTool.Services; - -using Amazon.SQS.Model; namespace Amazon.Lambda.TestTool.Runtime { @@ -17,7 +16,7 @@ public class DlqMonitor private readonly object LOG_LOCK = new object(); private CancellationTokenSource _cancelSource; private IList _records = new List(); - + private readonly ILocalLambdaRuntime _runtime; private readonly LambdaFunction _function; private readonly string _profile; @@ -70,13 +69,19 @@ private async Task Loop(CancellationToken token) var request = new ExecutionRequest { AWSProfile = this._profile, - AWSRegion = this._region, + AWSRegion = this._region, Function = this._function, - Payload = message.Body + Payload = JsonSerializer.Serialize(new + { + Records = new List + { + message + } + }) }; var response = await this._runtime.ExecuteLambdaFunctionAsync(request); - + // Capture the results to send back to the client application. logRecord = new LogRecord { @@ -93,17 +98,17 @@ private async Task Loop(CancellationToken token) ProcessTime = DateTime.Now, Error = e.Message }; - + Thread.Sleep(1000); } - if(logRecord != null && message != null) + if (logRecord != null && message != null) { logRecord.Event = message.Body; } lock (LOG_LOCK) - { + { this._records.Add(logRecord); } } @@ -119,7 +124,7 @@ public IList FetchNewLogs() return logsToSend; } } - + public class LogRecord { diff --git a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/Services/AWSServiceImpl.cs b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/Services/AWSServiceImpl.cs index df9e3d093..d0aa6048e 100644 --- a/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/Services/AWSServiceImpl.cs +++ b/Tools/LambdaTestTool/src/Amazon.Lambda.TestTool/Services/AWSServiceImpl.cs @@ -1,12 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -using Amazon.Runtime; +using Amazon.Runtime; using Amazon.Runtime.CredentialManagement; - using Amazon.SQS; using Amazon.SQS.Model; +using System.Collections.Generic; +using System.Threading.Tasks; namespace Amazon.Lambda.TestTool.Services { @@ -53,7 +50,9 @@ public async Task ReadMessageAsync(string profile, string region, strin QueueUrl = queueUrl, WaitTimeSeconds = 20, MaxNumberOfMessages = 1, - VisibilityTimeout = 60 + VisibilityTimeout = 60, + AttributeNames = new List() { "All" }, + MessageAttributeNames = new List() { "*" } }; var response = await client.ReceiveMessageAsync(request); if (response.Messages.Count == 0)