Skip to content

Commit 9769bd1

Browse files
authored
Automatically install Amazon.Lambda.TestTool (#28)
1 parent 0c110f9 commit 9769bd1

21 files changed

+601
-73
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"Projects": [
3+
{
4+
"Name": "Aspire.Hosting.AWS",
5+
"Type": "Patch",
6+
"ChangelogMessages": [
7+
"Automatically install .NET Tool Amazon.Lambda.TestTool when running Lambda functions"
8+
]
9+
}
10+
]
11+
}

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,6 @@
4141
<PackageVersion Include="xunit" Version="2.9.2" />
4242
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
4343
<PackageVersion Include="JsonSchema.Net" Version="7.2.3" />
44+
<PackageVersion Include="Moq" Version="4.20.72" />
4445
</ItemGroup>
4546
</Project>

src/Aspire.Hosting.AWS/Lambda/APIGatewayApiResource.cs

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
3+
using Aspire.Hosting.ApplicationModel;
4+
using k8s.KubeConfigModels;
5+
6+
namespace Aspire.Hosting.AWS.Lambda;
7+
8+
9+
/// <summary>
10+
/// Resource representing the Amazon API Gateway emulator.
11+
/// </summary>
12+
/// <param name="name">Aspire resource name</param>
13+
public class APIGatewayEmulatorResource(string name, APIGatewayType apiGatewayType) : ExecutableResource(name,
14+
"dotnet",
15+
Environment.CurrentDirectory
16+
)
17+
{
18+
internal void AddCommandLineArguments(IList<object> arguments)
19+
{
20+
arguments.Add("lambda-test-tool");
21+
arguments.Add("start");
22+
arguments.Add("--no-launch-window");
23+
24+
arguments.Add("--api-gateway-emulator-mode");
25+
arguments.Add(apiGatewayType.ToString());
26+
}
27+
}

src/Aspire.Hosting.AWS/Lambda/APIGatewayExtensions.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,12 @@ public static class APIGatewayExtensions
2424
/// <param name="name">Aspire resource name</param>
2525
/// <param name="apiGatewayType">The type of API Gateway API. For example Rest, HttpV1 or HttpV2</param>
2626
/// <returns></returns>
27-
public static IResourceBuilder<APIGatewayApiResource> AddAWSAPIGatewayEmulator(this IDistributedApplicationBuilder builder, string name, APIGatewayType apiGatewayType)
27+
public static IResourceBuilder<APIGatewayEmulatorResource> AddAWSAPIGatewayEmulator(this IDistributedApplicationBuilder builder, string name, APIGatewayType apiGatewayType)
2828
{
29-
var apiGatewayEmulator = builder.AddResource(new APIGatewayApiResource(name)).ExcludeFromManifest();
30-
29+
var apiGatewayEmulator = builder.AddResource(new APIGatewayEmulatorResource(name, apiGatewayType)).ExcludeFromManifest();
3130
apiGatewayEmulator.WithArgs(context =>
3231
{
33-
context.Args.Add("--api-gateway-emulator-mode");
34-
context.Args.Add(apiGatewayType.ToString());
35-
context.Args.Add("--no-launch-window");
32+
apiGatewayEmulator.Resource.AddCommandLineArguments(context.Args);
3633
});
3734

3835
var annotation = new EndpointAnnotation(
@@ -62,7 +59,7 @@ public static IResourceBuilder<APIGatewayApiResource> AddAWSAPIGatewayEmulator(t
6259
/// <param name="httpMethod">The HTTP method the Lambda function should be called for.</param>
6360
/// <param name="path">The resource path the Lambda function should be called for.</param>
6461
/// <returns></returns>
65-
public static IResourceBuilder<APIGatewayApiResource> WithReference(this IResourceBuilder<APIGatewayApiResource> builder, IResourceBuilder<LambdaProjectResource> lambda, Method httpMethod, string path)
62+
public static IResourceBuilder<APIGatewayEmulatorResource> WithReference(this IResourceBuilder<APIGatewayEmulatorResource> builder, IResourceBuilder<LambdaProjectResource> lambda, Method httpMethod, string path)
6663
{
6764
LambdaEmulatorAnnotation? lambdaEmulatorAnnotation = null;
6865
if (builder.ApplicationBuilder.Resources.FirstOrDefault(x => x.TryGetLastAnnotation<LambdaEmulatorAnnotation>(out lambdaEmulatorAnnotation)) == null ||

src/Aspire.Hosting.AWS/Lambda/LambdaEmulatorAnnotation.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,24 @@ internal class LambdaEmulatorAnnotation(EndpointReference endpoint) : IResourceA
1414
/// The HTTP endpoint for the Lambda runtime emulator.
1515
/// </summary>
1616
public EndpointReference Endpoint { get; init; } = endpoint;
17+
18+
/// <summary>
19+
/// By default Amazon.Lambda.TestTool will be updated/installed during AppHost startup. Amazon.Lambda.TestTool is
20+
/// a .NET Tool that will be installed globally.
21+
///
22+
/// When DisableAutoInstall is set to true the auto installation is disabled.
23+
/// </summary>
24+
public bool DisableAutoInstall { get; set; }
25+
26+
/// <summary>
27+
/// Override the minimum version of Amazon.Lambda.TestTool that will be installed. If a newer vesion is already installed
28+
/// it will be used unless AllowDowngrade is set to true.
29+
/// </summary>
30+
public string? OverrideMinimumInstallVersion { get; set; }
31+
32+
/// <summary>
33+
/// If set to true and a newer version of the Amazon.Lambda.TestTool is installed then expected the installed version will be downgraded
34+
/// to match the expected version.
35+
/// </summary>
36+
public bool AllowDowngrade { get; set; }
1737
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
3+
namespace Aspire.Hosting.AWS.Lambda;
4+
5+
/// <summary>
6+
/// Options that can be added to the Lambda emulator resource.
7+
/// </summary>
8+
public class LambdaEmulatorOptions
9+
{
10+
/// <summary>
11+
/// By default Amazon.Lambda.TestTool will be updated/installed during AppHost startup. Amazon.Lambda.TestTool is
12+
/// a .NET Tool that will be installed globally.
13+
///
14+
/// When DisableAutoInstall is set to true the auto installation is disabled.
15+
/// </summary>
16+
public bool DisableAutoInstall { get; set; }
17+
18+
/// <summary>
19+
/// Override the minimum version of Amazon.Lambda.TestTool that will be installed. If a newer version is already installed
20+
/// it will be used unless AllowDowngrade is set to true.
21+
/// </summary>
22+
public string? OverrideMinimumInstallVersion { get; set; }
23+
24+
/// <summary>
25+
/// If set to true, and a newer version of Amazon.Lambda.TestTool is already installed then the requested version, the installed version
26+
/// will be downgraded to the request version.
27+
/// </summary>
28+
public bool AllowDowngrade { get; set; }
29+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
3+
using Aspire.Hosting.ApplicationModel;
4+
5+
namespace Aspire.Hosting.AWS.Lambda;
6+
7+
8+
/// <summary>
9+
/// Resource representing the Lambda Runtime API service emulator.
10+
/// </summary>
11+
/// <param name="name">Aspire resource name</param>
12+
public class LambdaEmulatorResource(string name) : ExecutableResource(name,
13+
"dotnet",
14+
Environment.CurrentDirectory
15+
)
16+
{
17+
internal void AddCommandLineArguments(IList<object> arguments)
18+
{
19+
arguments.Add("lambda-test-tool");
20+
arguments.Add("start");
21+
arguments.Add("--no-launch-window");
22+
}
23+
}

src/Aspire.Hosting.AWS/Lambda/LambdaExtensions.cs

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
using Aspire.Hosting.AWS;
55
using Aspire.Hosting.AWS.Lambda;
66
using Microsoft.Extensions.Hosting;
7+
using Aspire.Hosting.AWS.Utils.Internal;
8+
using Aspire.Hosting.Lifecycle;
9+
using Microsoft.AspNetCore.Http;
10+
using Microsoft.Extensions.DependencyInjection.Extensions;
711
using System.Diagnostics;
812
using System.Net.Sockets;
913
using System.Runtime.Versioning;
@@ -54,6 +58,7 @@ public static class LambdaExtensions
5458
// Add the Lambda function resource on the path so the emulator can distingish request
5559
// for each Lambda function.
5660
var apiPath = $"{serviceEmulatorEndpoint.Host}:{serviceEmulatorEndpoint.Port}/{name}";
61+
context.EnvironmentVariables["AWS_EXECUTION_ENV"] = $"aspire.hosting.aws#{SdkUtilities.GetAssemblyVersion()}";
5762
context.EnvironmentVariables["AWS_LAMBDA_RUNTIME_API"] = apiPath;
5863
context.EnvironmentVariables["AWS_LAMBDA_FUNCTION_NAME"] = name;
5964
context.EnvironmentVariables["_HANDLER"] = lambdaHandler;
@@ -96,6 +101,67 @@ public static class LambdaExtensions
96101
return resource;
97102
}
98103

104+
/// <summary>
105+
/// Add the Lambda service emulator resource. The <see cref="AddAWSLambdaFunction"/> method will automatically add the Lambda service emulator if it hasn't
106+
/// already been added. This method only needs to be called if the emulator needs to be customized with the <see cref="LambdaEmulatorOptions"/>. If
107+
/// this method is called it must be called only once and before any <see cref="AddAWSLambdaFunction"/> calls.
108+
/// </summary>
109+
/// <param name="builder"></param>
110+
/// <param name="options">The options to configure the emulator with.</param>
111+
/// <returns></returns>
112+
/// <exception cref="InvalidOperationException">Thrown if the Lambda service emulator has already been added.</exception>
113+
public static IResourceBuilder<LambdaEmulatorResource> AddAWSLambdaServiceEmulator(this IDistributedApplicationBuilder builder, LambdaEmulatorOptions? options = null)
114+
{
115+
if (builder.Resources.FirstOrDefault(x => x.TryGetAnnotationsOfType<LambdaEmulatorAnnotation>(out _)) is ExecutableResource serviceEmulator)
116+
{
117+
throw new InvalidOperationException("A Lambda service emulator has already been added. The AddAWSLambdaFunction will add the emulator " +
118+
"if it hasn't already been added. This method must be called before AddAWSLambdaFunction if the Lambda service emulator needs to be customized.");
119+
}
120+
121+
builder.Services.TryAddSingleton<IProcessCommandService, ProcessCommandService>();
122+
123+
var lambdaEmulator = builder.AddResource(new LambdaEmulatorResource("LambdaServiceEmulator")).ExcludeFromManifest();
124+
lambdaEmulator.WithArgs(context =>
125+
{
126+
lambdaEmulator.Resource.AddCommandLineArguments(context.Args);
127+
});
128+
129+
var annotation = new EndpointAnnotation(
130+
protocol: ProtocolType.Tcp,
131+
uriScheme: "http");
132+
133+
lambdaEmulator.WithAnnotation(annotation);
134+
var endpointReference = new EndpointReference(lambdaEmulator.Resource, annotation);
135+
136+
lambdaEmulator.WithAnnotation(new LambdaEmulatorAnnotation(endpointReference)
137+
{
138+
DisableAutoInstall = options?.DisableAutoInstall ?? false,
139+
OverrideMinimumInstallVersion = options?.OverrideMinimumInstallVersion,
140+
AllowDowngrade = options?.AllowDowngrade ?? false,
141+
});
142+
143+
lambdaEmulator.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
144+
{
145+
context.EnvironmentVariables[Constants.IsAspireHostedEnvVariable] = "true";
146+
context.EnvironmentVariables["LAMBDA_RUNTIME_API_PORT"] = endpointReference.Property(EndpointProperty.TargetPort);
147+
}));
148+
149+
serviceEmulator = lambdaEmulator.Resource;
150+
builder.Services.TryAddLifecycleHook<LambdaLifecycleHook>();
151+
152+
return lambdaEmulator;
153+
}
154+
155+
private static ExecutableResource AddOrGetLambdaServiceEmulatorResource(IDistributedApplicationBuilder builder)
156+
{
157+
if (builder.Resources.FirstOrDefault(x => x.TryGetAnnotationsOfType<LambdaEmulatorAnnotation>(out _)) is not ExecutableResource serviceEmulator)
158+
{
159+
serviceEmulator = builder.AddAWSLambdaServiceEmulator().Resource;
160+
}
161+
162+
return serviceEmulator;
163+
}
164+
99165
/// <summary>
100166
/// This method is adapted from the Aspire WithProjectDefaults method.
101167
/// https://github.com/dotnet/aspire/blob/157f312e39300912b37a14f59beda217c8195e14/src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs#L287
@@ -122,35 +188,4 @@ private static IResourceBuilder<LambdaProjectResource> WithOpenTelemetry(this IR
122188

123189
return builder;
124190
}
125-
126-
private static ExecutableResource AddOrGetLambdaServiceEmulatorResource(IDistributedApplicationBuilder builder)
127-
{
128-
if (builder.Resources.FirstOrDefault(x => x.TryGetAnnotationsOfType<LambdaEmulatorAnnotation>(out _)) is not ExecutableResource serviceEmulator)
129-
{
130-
var serviceEmulatorBuilder = builder.AddExecutable($"Lambda-ServiceEmulator",
131-
"dotnet-lambda-test-tool",
132-
Environment.CurrentDirectory,
133-
"--no-launch-window")
134-
.ExcludeFromManifest();
135-
136-
var annotation = new EndpointAnnotation(
137-
protocol: ProtocolType.Tcp,
138-
uriScheme: "http");
139-
140-
serviceEmulatorBuilder.WithAnnotation(annotation);
141-
var endpointReference = new EndpointReference(serviceEmulatorBuilder.Resource, annotation);
142-
143-
serviceEmulatorBuilder.WithAnnotation(new LambdaEmulatorAnnotation(endpointReference));
144-
145-
serviceEmulatorBuilder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
146-
{
147-
context.EnvironmentVariables[Constants.IsAspireHostedEnvVariable] = "true";
148-
context.EnvironmentVariables["LAMBDA_RUNTIME_API_PORT"] = endpointReference.Property(EndpointProperty.TargetPort);
149-
}));
150-
151-
serviceEmulator = serviceEmulatorBuilder.Resource;
152-
}
153-
154-
return serviceEmulator;
155-
}
156191
}

0 commit comments

Comments
 (0)