-
Notifications
You must be signed in to change notification settings - Fork 13
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
Changes from 1 commit
ffbb42b
1662376
21c6ad8
0de32d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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" | ||
] | ||
} | ||
] | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// 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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isn't the name of our tool There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. apparently There was a problem hiding this comment. Choose a reason for hiding this commentThe 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("--no-launch-window"); | ||
|
||
arguments.Add("--api-gateway-emulator-mode"); | ||
arguments.Add(apiGatewayType.ToString()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,4 +14,22 @@ internal class LambdaEmulatorAnnotation(EndpointReference endpoint) : IResourceA | |
/// The HTTP endpoint for the Lambda runtime emulator. | ||
/// </summary> | ||
public EndpointReference Endpoint { get; init; } = endpoint; | ||
|
||
/// <summary> | ||
/// If set to true Amazon.Lambda.TestTool will updated/installed during AppHost startup. Amazon.Lambda.TestTool is | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the name and the description are saying 2 different things. The name suggests that we won't auto install, but the description is saying that we will? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
/// a .NET Tool that will be installed globally. | ||
/// </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; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
||
namespace Aspire.Hosting.AWS.Lambda; | ||
|
||
/// <summary> | ||
/// Options that can be added the Lambda emulator resource. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: add "to" between "added" and "the" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
/// </summary> | ||
public class LambdaEmulatorOptions | ||
{ | ||
/// <summary> | ||
/// If set to true, Amazon.Lambda.TestTool will updated/installed during AppHost startup. Amazon.Lambda.TestTool is | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same comment as before There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
/// a .NET Tool that will be installed globally. | ||
/// </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; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// 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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same question on tool name There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discussed in sync meeting. |
||
arguments.Add("--no-launch-window"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,12 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
||
using Amazon.Runtime.Internal.Endpoints.StandardLibrary; | ||
using Aspire.Hosting.ApplicationModel; | ||
using Aspire.Hosting.AWS; | ||
using Aspire.Hosting.AWS.Lambda; | ||
using Aspire.Hosting.AWS.Utils.Internal; | ||
using Aspire.Hosting.Lifecycle; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using System.Collections.Immutable; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using System.Diagnostics; | ||
using System.Net.Sockets; | ||
using System.Runtime.Versioning; | ||
|
@@ -97,32 +97,62 @@ public static class LambdaExtensions | |
return resource; | ||
} | ||
|
||
private static ExecutableResource AddOrGetLambdaServiceEmulatorResource(IDistributedApplicationBuilder builder) | ||
{ | ||
if (builder.Resources.FirstOrDefault(x => x.TryGetAnnotationsOfType<LambdaEmulatorAnnotation>(out _)) is not ExecutableResource serviceEmulator) | ||
/// <summary> | ||
/// Add the Lambda service emulator resource. The AddAWSLambdaFunction method will automatically add the Lambda service emulator if it hasn't | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you add doc references to "AddAWSLambdaFunction" and "LambdaEmulatorOptions" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
/// already been added. This method only needs to be called if the emulator needs to be customized with the LambdaEmulatorOptions. If | ||
/// this method is called it must be called only once and before any 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) | ||
{ | ||
var serviceEmulatorBuilder = builder.AddExecutable($"Lambda-ServiceEmulator", | ||
"dotnet-lambda-test-tool", | ||
Environment.CurrentDirectory, | ||
"--no-launch-window") | ||
.ExcludeFromManifest(); | ||
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."); | ||
} | ||
|
||
var annotation = new EndpointAnnotation( | ||
protocol: ProtocolType.Tcp, | ||
uriScheme: "http"); | ||
builder.Services.TryAddSingleton<IProcessCommandService, ProcessCommandService>(); | ||
|
||
serviceEmulatorBuilder.WithAnnotation(annotation); | ||
var endpointReference = new EndpointReference(serviceEmulatorBuilder.Resource, annotation); | ||
var lambdaEmulator = builder.AddResource(new LambdaEmulatorResource("LambdaServiceEmulator")).ExcludeFromManifest(); | ||
lambdaEmulator.WithArgs(context => | ||
{ | ||
lambdaEmulator.Resource.AddCommandLineArguments(context.Args); | ||
}); | ||
|
||
serviceEmulatorBuilder.WithAnnotation(new LambdaEmulatorAnnotation(endpointReference)); | ||
var annotation = new EndpointAnnotation( | ||
protocol: ProtocolType.Tcp, | ||
uriScheme: "http"); | ||
|
||
serviceEmulatorBuilder.WithAnnotation(new EnvironmentCallbackAnnotation(context => | ||
{ | ||
context.EnvironmentVariables[Constants.IsAspireHostedEnvVariable] = "true"; | ||
context.EnvironmentVariables["LAMBDA_RUNTIME_API_PORT"] = endpointReference.Property(EndpointProperty.TargetPort); | ||
})); | ||
lambdaEmulator.WithAnnotation(annotation); | ||
var endpointReference = new EndpointReference(lambdaEmulator.Resource, annotation); | ||
|
||
serviceEmulator = serviceEmulatorBuilder.Resource; | ||
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; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
||
using Aspire.Hosting.ApplicationModel; | ||
using Aspire.Hosting.AWS.Utils.Internal; | ||
using Aspire.Hosting.Lifecycle; | ||
using Microsoft.Extensions.Logging; | ||
using System.Text.Json; | ||
using System.Text.Json.Nodes; | ||
using System.Threading; | ||
using static Google.Protobuf.Reflection.GeneratedCodeInfo.Types; | ||
|
||
namespace Aspire.Hosting.AWS.Lambda; | ||
|
||
/// <summary> | ||
/// Lambda lifecycle hook takes care of getting Amazon.Lambda.TestTool is installed if there was | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
/// a Lambda service emulator added to the resources. | ||
/// </summary> | ||
/// <param name="logger"></param> | ||
internal class LambdaLifecycleHook(ILogger<LambdaEmulatorResource> logger, IProcessCommandService processCommandService) : IDistributedApplicationLifecycleHook | ||
{ | ||
internal const string DefaultLambdaTestToolVersion = "0.0.2-preview"; | ||
|
||
public async Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default) | ||
{ | ||
LambdaEmulatorAnnotation? emulatorAnnotation = null; | ||
if (appModel.Resources.FirstOrDefault(x => x.TryGetLastAnnotation<LambdaEmulatorAnnotation>(out emulatorAnnotation)) != null && emulatorAnnotation != null) | ||
{ | ||
await ApplyLambdaEmulatorAnnotationAsync(emulatorAnnotation, cancellationToken); | ||
} | ||
else | ||
{ | ||
logger.LogDebug("Skipping installing Amazon.Lambda.TestTool since no Lambda emulator resource was found"); | ||
} | ||
} | ||
|
||
internal async Task ApplyLambdaEmulatorAnnotationAsync(LambdaEmulatorAnnotation emulatorAnnotation, CancellationToken cancellationToken = default) | ||
{ | ||
if (emulatorAnnotation.DisableAutoInstall) | ||
{ | ||
return; | ||
} | ||
|
||
var expectedVersion = emulatorAnnotation.OverrideMinimumInstallVersion ?? DefaultLambdaTestToolVersion; | ||
var installedVersion = await GetCurrentInstalledVersionAsync(cancellationToken); | ||
|
||
if (ShouldInstall(installedVersion, expectedVersion, emulatorAnnotation.AllowDowngrade)) | ||
{ | ||
logger.LogDebug("Installing .NET Tool Amazon.Lambda.TestTool ({version})", installedVersion); | ||
|
||
var commandLineArgument = $"tool install -g Amazon.Lambda.TestTool --version {expectedVersion}"; | ||
if (emulatorAnnotation.AllowDowngrade) | ||
{ | ||
commandLineArgument += " --allow-downgrade"; | ||
} | ||
|
||
var result = await processCommandService.RunProcessAndCaptureOuputAsync(logger, "dotnet", commandLineArgument, cancellationToken); | ||
if (result.ExitCode == 0) | ||
{ | ||
if (!string.IsNullOrEmpty(installedVersion)) | ||
{ | ||
logger.LogInformation("Successfully Updated .NET Tool Amazon.Lambda.TestTool from version {installedVersion} to {newVersion}", installedVersion, expectedVersion); | ||
} | ||
else | ||
{ | ||
logger.LogInformation("Successfully installed .NET Tool Amazon.Lambda.TestTool ({version})", expectedVersion); | ||
} | ||
} | ||
else | ||
{ | ||
if (!string.IsNullOrEmpty(installedVersion)) | ||
{ | ||
logger.LogWarning("Failed to update Amazon.Lambda.TestTool from {installedVersion} to {expectedVersion}:\n{output}", installedVersion, expectedVersion, result.Output); | ||
} | ||
else | ||
{ | ||
logger.LogError("Fail to install Amazon.Lambda.TestTool ({version}) required for running Lambda functions locally:\n{output}", expectedVersion, result.Output); | ||
} | ||
} | ||
} | ||
else | ||
{ | ||
logger.LogInformation("Amazon.Lambda.TestTool version {version} already installed", installedVersion); | ||
} | ||
} | ||
|
||
internal static bool ShouldInstall(string currentInstalledVersion, string expectedVersionStr, bool allowDowngrading) | ||
{ | ||
if (string.IsNullOrEmpty(currentInstalledVersion)) | ||
{ | ||
return true; | ||
} | ||
|
||
var installedVersion = Version.Parse(currentInstalledVersion.Replace("-preview", string.Empty)); | ||
var expectedVersion = Version.Parse(expectedVersionStr.Replace("-preview", string.Empty)); | ||
|
||
return (installedVersion < expectedVersion) || (allowDowngrading && installedVersion != expectedVersion); | ||
} | ||
|
||
private async Task<string> GetCurrentInstalledVersionAsync(CancellationToken cancellationToken) | ||
{ | ||
var results = await processCommandService.RunProcessAndCaptureOuputAsync(logger, "dotnet", "lambda-test-tool --tool-info", cancellationToken); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. might need to update based on other PR changes aws/aws-lambda-dotnet#1969 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
if (results.ExitCode != 0) | ||
{ | ||
return string.Empty; | ||
} | ||
|
||
try | ||
{ | ||
var versionDoc = JsonNode.Parse(results.Output); | ||
if (versionDoc == null) | ||
{ | ||
logger.LogWarning("Error parsing version information from Amazon.Lambda.TestTool: {versionInfo}", results.Output); | ||
return string.Empty; | ||
|
||
} | ||
var version = versionDoc["version"]?.ToString(); | ||
logger.LogDebug("Installed version of Amazon.Lambda.TestTool is {version}", version); | ||
return version ?? string.Empty; | ||
} | ||
catch (JsonException ex) | ||
{ | ||
logger.LogWarning(ex, "Error parsing version information from Amazon.Lambda.TestTool: {versionInfo}", results.Output); | ||
return string.Empty; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated to this change, but why does the entire solution has 1 Directory.Packages.props? So anytime we add a new package, we are adding it for the entire solution?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The repo was setup so that it had centralized package versioning. But in the individual project files you still specify the package you want to use. But in the project you don't set a version. For example in the project file of Aspire.Hosting.AWS we add the packages but don't set the version.
This way you only have to update the version of a package in one place for all projects in a repository.