Skip to content

Automatically install Amazon.Lambda.TestTool #28

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

Merged
merged 4 commits into from
Feb 7, 2025
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: 11 additions & 0 deletions .autover/changes/b2019317-5414-4315-b539-7735b04f8e63.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Projects": [
{
"Name": "Aspire.Hosting.AWS",
"Type": "Patch",
"ChangelogMessages": [
"Automatically install .NET Tool Amazon.Lambda.TestTool when running Lambda functions"
]
}
]
}
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="JsonSchema.Net" Version="7.2.3" />
<PackageVersion Include="Moq" Version="4.20.72" />
</ItemGroup>
</Project>
17 changes: 0 additions & 17 deletions src/Aspire.Hosting.AWS/Lambda/APIGatewayApiResource.cs

This file was deleted.

27 changes: 27 additions & 0 deletions src/Aspire.Hosting.AWS/Lambda/APIGatewayEmulatorResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

using Aspire.Hosting.ApplicationModel;
using k8s.KubeConfigModels;

namespace Aspire.Hosting.AWS.Lambda;


/// <summary>
/// Resource representing the Amazon API Gateway emulator.
/// </summary>
/// <param name="name">Aspire resource name</param>
public class APIGatewayEmulatorResource(string name, APIGatewayType apiGatewayType) : ExecutableResource(name,
"dotnet",
Environment.CurrentDirectory
)
{
internal void AddCommandLineArguments(IList<object> arguments)
{
arguments.Add("lambda-test-tool");
Copy link
Contributor

Choose a reason for hiding this comment

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

isn't the name of our tool dotnet-lambda-test-tool?

Copy link
Contributor

Choose a reason for hiding this comment

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

apparently dotnet lambda-test-tool works and so does dotnet-lambda-test-tool.

Copy link
Member Author

Choose a reason for hiding this comment

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

Discussed in sync meeting and we are on the same page now.

arguments.Add("start");
arguments.Add("--no-launch-window");

arguments.Add("--api-gateway-emulator-mode");
arguments.Add(apiGatewayType.ToString());
}
}
11 changes: 4 additions & 7 deletions src/Aspire.Hosting.AWS/Lambda/APIGatewayExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,12 @@ public static class APIGatewayExtensions
/// <param name="name">Aspire resource name</param>
/// <param name="apiGatewayType">The type of API Gateway API. For example Rest, HttpV1 or HttpV2</param>
/// <returns></returns>
public static IResourceBuilder<APIGatewayApiResource> AddAWSAPIGatewayEmulator(this IDistributedApplicationBuilder builder, string name, APIGatewayType apiGatewayType)
public static IResourceBuilder<APIGatewayEmulatorResource> AddAWSAPIGatewayEmulator(this IDistributedApplicationBuilder builder, string name, APIGatewayType apiGatewayType)
{
var apiGatewayEmulator = builder.AddResource(new APIGatewayApiResource(name)).ExcludeFromManifest();

var apiGatewayEmulator = builder.AddResource(new APIGatewayEmulatorResource(name, apiGatewayType)).ExcludeFromManifest();
apiGatewayEmulator.WithArgs(context =>
{
context.Args.Add("--api-gateway-emulator-mode");
context.Args.Add(apiGatewayType.ToString());
context.Args.Add("--no-launch-window");
apiGatewayEmulator.Resource.AddCommandLineArguments(context.Args);
});

var annotation = new EndpointAnnotation(
Expand Down Expand Up @@ -62,7 +59,7 @@ public static IResourceBuilder<APIGatewayApiResource> AddAWSAPIGatewayEmulator(t
/// <param name="httpMethod">The HTTP method the Lambda function should be called for.</param>
/// <param name="path">The resource path the Lambda function should be called for.</param>
/// <returns></returns>
public static IResourceBuilder<APIGatewayApiResource> WithReference(this IResourceBuilder<APIGatewayApiResource> builder, IResourceBuilder<LambdaProjectResource> lambda, Method httpMethod, string path)
public static IResourceBuilder<APIGatewayEmulatorResource> WithReference(this IResourceBuilder<APIGatewayEmulatorResource> builder, IResourceBuilder<LambdaProjectResource> lambda, Method httpMethod, string path)
{
LambdaEmulatorAnnotation? lambdaEmulatorAnnotation = null;
if (builder.ApplicationBuilder.Resources.FirstOrDefault(x => x.TryGetLastAnnotation<LambdaEmulatorAnnotation>(out lambdaEmulatorAnnotation)) == null ||
Expand Down
20 changes: 20 additions & 0 deletions src/Aspire.Hosting.AWS/Lambda/LambdaEmulatorAnnotation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,24 @@ internal class LambdaEmulatorAnnotation(EndpointReference endpoint) : IResourceA
/// The HTTP endpoint for the Lambda runtime emulator.
/// </summary>
public EndpointReference Endpoint { get; init; } = endpoint;

/// <summary>
/// By default Amazon.Lambda.TestTool will be updated/installed during AppHost startup. Amazon.Lambda.TestTool is
/// a .NET Tool that will be installed globally.
///
/// When DisableAutoInstall is set to true the auto installation is disabled.
/// </summary>
public bool DisableAutoInstall { get; set; }

/// <summary>
/// Override the minimum version of Amazon.Lambda.TestTool that will be installed. If a newer vesion is already installed
/// it will be used unless AllowDowngrade is set to true.
/// </summary>
public string? OverrideMinimumInstallVersion { get; set; }

/// <summary>
/// If set to true and a newer version of the Amazon.Lambda.TestTool is installed then expected the installed version will be downgraded
/// to match the expected version.
/// </summary>
public bool AllowDowngrade { get; set; }
}
29 changes: 29 additions & 0 deletions src/Aspire.Hosting.AWS/Lambda/LambdaEmulatorOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

namespace Aspire.Hosting.AWS.Lambda;

/// <summary>
/// Options that can be added to the Lambda emulator resource.
/// </summary>
public class LambdaEmulatorOptions
{
/// <summary>
/// By default Amazon.Lambda.TestTool will be updated/installed during AppHost startup. Amazon.Lambda.TestTool is
/// a .NET Tool that will be installed globally.
///
/// When DisableAutoInstall is set to true the auto installation is disabled.
/// </summary>
public bool DisableAutoInstall { get; set; }

/// <summary>
/// Override the minimum version of Amazon.Lambda.TestTool that will be installed. If a newer version is already installed
/// it will be used unless AllowDowngrade is set to true.
/// </summary>
public string? OverrideMinimumInstallVersion { get; set; }

/// <summary>
/// If set to true, and a newer version of Amazon.Lambda.TestTool is already installed then the requested version, the installed version
/// will be downgraded to the request version.
/// </summary>
public bool AllowDowngrade { get; set; }
}
23 changes: 23 additions & 0 deletions src/Aspire.Hosting.AWS/Lambda/LambdaEmulatorResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.AWS.Lambda;


/// <summary>
/// Resource representing the Lambda Runtime API service emulator.
/// </summary>
/// <param name="name">Aspire resource name</param>
public class LambdaEmulatorResource(string name) : ExecutableResource(name,
"dotnet",
Environment.CurrentDirectory
)
{
internal void AddCommandLineArguments(IList<object> arguments)
{
arguments.Add("lambda-test-tool");
Copy link
Contributor

Choose a reason for hiding this comment

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

same question on tool name

Copy link
Contributor

Choose a reason for hiding this comment

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

also didn't we change the behavior so that you need to specify the port for the emulator to run? won't this error out as is?

Copy link
Member Author

Choose a reason for hiding this comment

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

Discussed in sync meeting.

arguments.Add("start");
arguments.Add("--no-launch-window");
}
}
97 changes: 66 additions & 31 deletions src/Aspire.Hosting.AWS/Lambda/LambdaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
using Aspire.Hosting.AWS;
using Aspire.Hosting.AWS.Lambda;
using Microsoft.Extensions.Hosting;
using Aspire.Hosting.AWS.Utils.Internal;
using Aspire.Hosting.Lifecycle;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Diagnostics;
using System.Net.Sockets;
using System.Runtime.Versioning;
Expand Down Expand Up @@ -54,6 +58,7 @@ public static class LambdaExtensions
// Add the Lambda function resource on the path so the emulator can distingish request
// for each Lambda function.
var apiPath = $"{serviceEmulatorEndpoint.Host}:{serviceEmulatorEndpoint.Port}/{name}";
context.EnvironmentVariables["AWS_EXECUTION_ENV"] = $"aspire.hosting.aws#{SdkUtilities.GetAssemblyVersion()}";
context.EnvironmentVariables["AWS_LAMBDA_RUNTIME_API"] = apiPath;
context.EnvironmentVariables["AWS_LAMBDA_FUNCTION_NAME"] = name;
context.EnvironmentVariables["_HANDLER"] = lambdaHandler;
Expand Down Expand Up @@ -96,6 +101,67 @@ public static class LambdaExtensions
return resource;
}

/// <summary>
/// Add the Lambda service emulator resource. The <see cref="AddAWSLambdaFunction"/> method will automatically add the Lambda service emulator if it hasn't
/// already been added. This method only needs to be called if the emulator needs to be customized with the <see cref="LambdaEmulatorOptions"/>. If
/// this method is called it must be called only once and before any <see cref="AddAWSLambdaFunction"/> calls.
/// </summary>
/// <param name="builder"></param>
/// <param name="options">The options to configure the emulator with.</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">Thrown if the Lambda service emulator has already been added.</exception>
public static IResourceBuilder<LambdaEmulatorResource> AddAWSLambdaServiceEmulator(this IDistributedApplicationBuilder builder, LambdaEmulatorOptions? options = null)
{
if (builder.Resources.FirstOrDefault(x => x.TryGetAnnotationsOfType<LambdaEmulatorAnnotation>(out _)) is ExecutableResource serviceEmulator)
{
throw new InvalidOperationException("A Lambda service emulator has already been added. The AddAWSLambdaFunction will add the emulator " +
"if it hasn't already been added. This method must be called before AddAWSLambdaFunction if the Lambda service emulator needs to be customized.");
}

builder.Services.TryAddSingleton<IProcessCommandService, ProcessCommandService>();

var lambdaEmulator = builder.AddResource(new LambdaEmulatorResource("LambdaServiceEmulator")).ExcludeFromManifest();
lambdaEmulator.WithArgs(context =>
{
lambdaEmulator.Resource.AddCommandLineArguments(context.Args);
});

var annotation = new EndpointAnnotation(
protocol: ProtocolType.Tcp,
uriScheme: "http");

lambdaEmulator.WithAnnotation(annotation);
var endpointReference = new EndpointReference(lambdaEmulator.Resource, annotation);

lambdaEmulator.WithAnnotation(new LambdaEmulatorAnnotation(endpointReference)
{
DisableAutoInstall = options?.DisableAutoInstall ?? false,
OverrideMinimumInstallVersion = options?.OverrideMinimumInstallVersion,
AllowDowngrade = options?.AllowDowngrade ?? false,
});

lambdaEmulator.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
{
context.EnvironmentVariables[Constants.IsAspireHostedEnvVariable] = "true";
context.EnvironmentVariables["LAMBDA_RUNTIME_API_PORT"] = endpointReference.Property(EndpointProperty.TargetPort);
}));

serviceEmulator = lambdaEmulator.Resource;
builder.Services.TryAddLifecycleHook<LambdaLifecycleHook>();

return lambdaEmulator;
}

private static ExecutableResource AddOrGetLambdaServiceEmulatorResource(IDistributedApplicationBuilder builder)
{
if (builder.Resources.FirstOrDefault(x => x.TryGetAnnotationsOfType<LambdaEmulatorAnnotation>(out _)) is not ExecutableResource serviceEmulator)
{
serviceEmulator = builder.AddAWSLambdaServiceEmulator().Resource;
}

return serviceEmulator;
}

/// <summary>
/// This method is adapted from the Aspire WithProjectDefaults method.
/// https://github.com/dotnet/aspire/blob/157f312e39300912b37a14f59beda217c8195e14/src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs#L287
Expand All @@ -122,35 +188,4 @@ private static IResourceBuilder<LambdaProjectResource> WithOpenTelemetry(this IR

return builder;
}

private static ExecutableResource AddOrGetLambdaServiceEmulatorResource(IDistributedApplicationBuilder builder)
{
if (builder.Resources.FirstOrDefault(x => x.TryGetAnnotationsOfType<LambdaEmulatorAnnotation>(out _)) is not ExecutableResource serviceEmulator)
{
var serviceEmulatorBuilder = builder.AddExecutable($"Lambda-ServiceEmulator",
"dotnet-lambda-test-tool",
Environment.CurrentDirectory,
"--no-launch-window")
.ExcludeFromManifest();

var annotation = new EndpointAnnotation(
protocol: ProtocolType.Tcp,
uriScheme: "http");

serviceEmulatorBuilder.WithAnnotation(annotation);
var endpointReference = new EndpointReference(serviceEmulatorBuilder.Resource, annotation);

serviceEmulatorBuilder.WithAnnotation(new LambdaEmulatorAnnotation(endpointReference));

serviceEmulatorBuilder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
{
context.EnvironmentVariables[Constants.IsAspireHostedEnvVariable] = "true";
context.EnvironmentVariables["LAMBDA_RUNTIME_API_PORT"] = endpointReference.Property(EndpointProperty.TargetPort);
}));

serviceEmulator = serviceEmulatorBuilder.Resource;
}

return serviceEmulator;
}
}
Loading
Loading