Skip to content
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

feat: add api gateway emulator skeleton #1902

Open
wants to merge 6 commits into
base: feature/lambdatesttool-v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Runtime.InteropServices;

namespace Spectre.Console.Cli;

philasmar marked this conversation as resolved.
Show resolved Hide resolved
/// <summary>
/// Provides an abstract base class for asynchronous commands that support cancellation.
/// </summary>
/// <typeparam name="TSettings">The type of the settings used for the command.</typeparam>
public abstract class CancellableAsyncCommand<TSettings> : AsyncCommand<TSettings> where TSettings : CommandSettings
{
/// <summary>
/// Executes the command asynchronously, with support for cancellation.
/// </summary>
public abstract Task<int> ExecuteAsync(CommandContext context, TSettings settings, CancellationTokenSource cancellationTokenSource);

/// <summary>
/// Executes the command asynchronously with built-in cancellation handling.
/// </summary>
public sealed override async Task<int> ExecuteAsync(CommandContext context, TSettings settings)
{
using var cancellationSource = new CancellationTokenSource();

using var sigInt = PosixSignalRegistration.Create(PosixSignal.SIGINT, onSignal);
using var sigQuit = PosixSignalRegistration.Create(PosixSignal.SIGQUIT, onSignal);
using var sigTerm = PosixSignalRegistration.Create(PosixSignal.SIGTERM, onSignal);

var cancellable = ExecuteAsync(context, settings, cancellationSource);
return await cancellable;

void onSignal(PosixSignalContext context)
{
context.Cancel = true;
cancellationSource.Cancel();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.ComponentModel;
using System.Diagnostics;
using Amazon.Lambda.TestTool.Commands.Settings;
using Amazon.Lambda.TestTool.Extensions;
using Amazon.Lambda.TestTool.Models;
using Amazon.Lambda.TestTool.Processes;
Expand All @@ -8,49 +8,32 @@

namespace Amazon.Lambda.TestTool.Commands;

philasmar marked this conversation as resolved.
Show resolved Hide resolved
/// <summary>
/// The default command of the application which is responsible for launching the Lambda Runtime API and the API Gateway Emulator.
/// </summary>
public sealed class RunCommand(
IToolInteractiveService toolInteractiveService) : AsyncCommand<RunCommand.Settings>
IToolInteractiveService toolInteractiveService) : CancellableAsyncCommand<RunCommandSettings>
{
public sealed class Settings : CommandSettings
{
[CommandOption("--host <HOST>")]
[Description(
"The hostname or IP address used for the test tool's web interface. Any host other than an explicit IP address or localhost (e.g. '*', '+' or 'example.com') binds to all public IPv4 and IPv6 addresses.")]
[DefaultValue(Constants.DefaultHost)]
public string Host { get; set; } = Constants.DefaultHost;

[CommandOption("-p|--port <PORT>")]
[Description("The port number used for the test tool's web interface.")]
[DefaultValue(Constants.DefaultPort)]
public int Port { get; set; } = Constants.DefaultPort;

[CommandOption("--no-launch-window")]
[Description("Disable auto launching the test tool's web interface in a browser.")]
public bool NoLaunchWindow { get; set; }

[CommandOption("--pause-exit")]
[Description("If set to true the test tool will pause waiting for a key input before exiting. The is useful when executing from an IDE so you can avoid having the output window immediately disappear after executing the Lambda code. The default value is true.")]
public bool PauseExit { get; set; }

[CommandOption("--disable-logs")]
[Description("Disables logging in the application")]
public bool DisableLogs { get; set; }
}

public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
/// <summary>
/// The method responsible for executing the <see cref="RunCommand"/>.
/// </summary>
public override async Task<int> ExecuteAsync(CommandContext context, RunCommandSettings settings, CancellationTokenSource cancellationTokenSource)
{
try
{
var process = TestToolProcess.Startup(settings);

var tasks = new List<Task>();

var testToolProcess = TestToolProcess.Startup(settings, cancellationTokenSource.Token);
tasks.Add(testToolProcess.RunningTask);

if (!settings.NoLaunchWindow)
{
try
{
var info = new ProcessStartInfo
{
UseShellExecute = true,
FileName = process.ServiceUrl
FileName = testToolProcess.ServiceUrl
};
Process.Start(info);
}
Expand All @@ -59,16 +42,23 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
toolInteractiveService.WriteErrorLine($"Error launching browser: {e.Message}");
}
}

await process.RunningTask;


if (settings.ApiGatewayEmulatorMode is not null)
{
var apiGatewayEmulatorProcess =
ApiGatewayEmulatorProcess.Startup(settings, cancellationTokenSource.Token);
tasks.Add(apiGatewayEmulatorProcess.RunningTask);
}

await Task.WhenAny(tasks);

return CommandReturnCodes.Success;
}
catch (Exception e) when (e.IsExpectedException())
{
toolInteractiveService.WriteErrorLine(string.Empty);
toolInteractiveService.WriteErrorLine(e.Message);

return CommandReturnCodes.UserError;
}
catch (Exception e)
Expand All @@ -79,8 +69,12 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
$"This is a bug.{Environment.NewLine}" +
$"Please copy the stack trace below and file a bug at {Constants.LinkGithubRepo}. " +
e.PrettyPrint());

return CommandReturnCodes.UnhandledException;
}
finally
{
await cancellationTokenSource.CancelAsync();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Amazon.Lambda.TestTool.Models;
using Spectre.Console.Cli;
using System.ComponentModel;

namespace Amazon.Lambda.TestTool.Commands.Settings;

/// <summary>
/// Represents the settings for configuring the <see cref="RunCommand"/>, which is the default command.
/// </summary>
public sealed class RunCommandSettings : CommandSettings
{
/// <summary>
/// The hostname or IP address used for the test tool's web interface.
/// Any host other than an explicit IP address or localhost (e.g. '*', '+' or 'example.com') binds to all public IPv4 and IPv6 addresses.
/// </summary>
[CommandOption("--host <HOST>")]
[Description(
"The hostname or IP address used for the test tool's web interface. Any host other than an explicit IP address or localhost (e.g. '*', '+' or 'example.com') binds to all public IPv4 and IPv6 addresses.")]
[DefaultValue(Constants.DefaultHost)]
public string Host { get; set; } = Constants.DefaultHost;

/// <summary>
/// The port number used for the test tool's web interface.
/// </summary>
[CommandOption("-p|--port <PORT>")]
[Description("The port number used for the test tool's web interface.")]
[DefaultValue(Constants.DefaultLambdaRuntimeEmulatorPort)]
public int Port { get; set; } = Constants.DefaultLambdaRuntimeEmulatorPort;

/// <summary>
/// Disable auto launching the test tool's web interface in a browser.
/// </summary>
[CommandOption("--no-launch-window")]
[Description("Disable auto launching the test tool's web interface in a browser.")]
public bool NoLaunchWindow { get; set; }

/// <summary>
/// If set to true the test tool will pause waiting for a key input before exiting.
/// The is useful when executing from an IDE so you can avoid having the output window immediately disappear after executing the Lambda code.
/// The default value is true.
/// </summary>
[CommandOption("--pause-exit")]
[Description("If set to true the test tool will pause waiting for a key input before exiting. The is useful when executing from an IDE so you can avoid having the output window immediately disappear after executing the Lambda code. The default value is true.")]
public bool PauseExit { get; set; }

/// <summary>
/// Disables logging in the application
/// </summary>
[CommandOption("--disable-logs")]
[Description("Disables logging in the application")]
public bool DisableLogs { get; set; }

/// <summary>
/// The API Gateway Emulator Mode specifies the format of the event that API Gateway sends to a Lambda integration,
/// and how API Gateway interprets the response from Lambda.
/// The available modes are: Rest, HttpV1, HttpV2.
/// </summary>
[CommandOption("--api-gateway-emulator <MODE>")]
[Description(
"The API Gateway Emulator Mode specifies the format of the event that API Gateway sends to a Lambda integration, and how API Gateway interprets the response from Lambda. " +
"The available modes are: Rest, HttpV1, HttpV2.")]
public ApiGatewayEmulatorMode? ApiGatewayEmulatorMode { get; set; }

/// <summary>
/// The port number used for the test tool's API Gateway emulator.
/// </summary>
[CommandOption("--api-gateway-emulator-port <PORT>")]
[Description("The port number used for the test tool's API Gateway emulator.")]
[DefaultValue(Constants.DefaultApiGatewayEmulatorPort)]
public int? ApiGatewayEmulatorPort { get; set; } = Constants.DefaultApiGatewayEmulatorPort;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@using Amazon.Lambda.TestTool.Services
@using Amazon.Lambda.TestTool.Models
@using Amazon.Lambda.TestTool.SampleRequests;
@using Amazon.Lambda.TestTool.Services.IO
@using Amazon.Lambda.TestTool.Utilities
@using Microsoft.AspNetCore.Http;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@using Amazon.Lambda.TestTool.Commands
@using Amazon.Lambda.TestTool.SampleRequests;
@using Amazon.Lambda.TestTool.Services
@using Amazon.Lambda.TestTool.Services.IO
@inject IModalService ModalService
@inject IDirectoryManager DirectoryManager

Expand Down
77 changes: 74 additions & 3 deletions Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Constants.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,94 @@
using Amazon.Lambda.TestTool.Models;

namespace Amazon.Lambda.TestTool;

/// <summary>
/// Provides constant values used across the application.
/// </summary>
public abstract class Constants
{
/// <summary>
/// The name of the dotnet CLI tool
/// </summary>
public const string ToolName = "lambda-test-tool";
public const int DefaultPort = 5050;

/// <summary>
/// The default port used by the Lambda Test Tool for the Lambda Runtime API and the Web Interface.
/// </summary>
public const int DefaultLambdaRuntimeEmulatorPort = 5050;

/// <summary>
/// The default port used by the API Gateway Emulator.
/// </summary>
public const int DefaultApiGatewayEmulatorPort = 5051;

/// <summary>
/// The default hostname used for the Lambda Test Tool.
/// </summary>
public const string DefaultHost = "localhost";

/// <summary>
/// The default mode for the API Gateway Emulator.
/// </summary>
public const ApiGatewayEmulatorMode DefaultApiGatewayEmulatorMode = ApiGatewayEmulatorMode.HttpV2;

/// <summary>
/// The prefix for environment variables used to configure the Lambda functions.
/// </summary>
public const string LambdaConfigEnvironmentVariablePrefix = "APIGATEWAY_EMULATOR_ROUTE_CONFIG";

/// <summary>
/// The product name displayed for the Lambda Test Tool.
/// </summary>
public const string ProductName = "AWS .NET Mock Lambda Test Tool";

/// <summary>
/// The CSS style used for successful responses in the tool's UI.
/// </summary>
public const string ResponseSuccessStyle = "white-space: pre-wrap; height: min-content; font-size: 75%; color: black";

/// <summary>
/// The CSS style used for error responses in the tool's UI.
/// </summary>
public const string ResponseErrorStyle = "white-space: pre-wrap; height: min-content; font-size: 75%; color: red";

/// <summary>
/// The CSS style used for successful responses in the tool's UI when a size constraint is applied.
/// </summary>
public const string ResponseSuccessStyleSizeConstraint = "white-space: pre-wrap; height: 300px; font-size: 75%; color: black";

/// <summary>
/// The CSS style used for error responses in the tool's UI when a size constraint is applied.
/// </summary>
public const string ResponseErrorStyleSizeConstraint = "white-space: pre-wrap; height: 300px; font-size: 75%; color: red";

/// <summary>
/// The GitHub repository link for the AWS Lambda .NET repository.
/// </summary>
public const string LinkGithubRepo = "https://github.com/aws/aws-lambda-dotnet";
public const string LinkGithubTestTool = "https://github.com/aws/aws-lambda-dotnet/tree/master/Tools/LambdaTestTool";

/// <summary>
/// The GitHub link for the Lambda Test Tool.
/// </summary>
public const string LinkGithubTestTool = "https://github.com/aws/aws-lambda-dotnet/tree/master/Tools/LambdaTestTool-v2";

/// <summary>
/// The GitHub link for the Lambda Test Tool's installation and running instructions.
/// </summary>
public const string LinkGithubTestToolInstallAndRun = "https://github.com/aws/aws-lambda-dotnet/tree/master/Tools/LambdaTestTool#installing-and-running";

/// <summary>
/// The AWS Developer Guide link for Dead Letter Queues in AWS Lambda.
/// </summary>
public const string LinkDlqDeveloperGuide = "https://docs.aws.amazon.com/lambda/latest/dg/dlq.html";

/// <summary>
/// The Microsoft documentation link for the <see cref="System.Runtime.Loader.AssemblyLoadContext"/> class.
/// </summary>
public const string LinkMsdnAssemblyLoadContext = "https://docs.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext";
public const string LinkVsToolkitMarketplace = "https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.AWSToolkitforVisualStudio2017";

/// <summary>
/// The Visual Studio Marketplace link for the AWS Toolkit for Visual Studio.
/// </summary>
public const string LinkVsToolkitMarketplace = "https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.AWSToolkitforVisualStudio2022";
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Amazon.Lambda.TestTool.Extensions;

/// <summary>
/// A class that contains extension methods for the <see cref="Exception"/> class.
/// </summary>
public static class ExceptionExtensions
{
/// <summary>
Expand All @@ -11,6 +14,9 @@ public static class ExceptionExtensions
public static bool IsExpectedException(this Exception e) =>
e is TestToolException;

/// <summary>
/// Prints an exception in a user-friendly way.
/// </summary>
public static string PrettyPrint(this Exception? e)
{
if (null == e)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
using Amazon.Lambda.TestTool.Services;
using Amazon.Lambda.TestTool.Services.IO;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Amazon.Lambda.TestTool.Extensions;

/// <summary>
/// A class that contains extension methods for the <see cref="IServiceCollection"/> interface.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds a set of services for the .NET CLI portion of this application.
/// </summary>
public static void AddCustomServices(this IServiceCollection serviceCollection,
ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IToolInteractiveService), typeof(ConsoleInteractiveService), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDirectoryManager), typeof(DirectoryManager), lifetime));
}

/// <summary>
/// Adds a set of services for the API Gateway emulator portion of this application.
/// </summary>
public static void AddApiGatewayEmulatorServices(this IServiceCollection serviceCollection,
ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IApiGatewayRouteConfigService), typeof(ApiGatewayRouteConfigService), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IEnvironmentManager), typeof(EnvironmentManager), lifetime));
}
}
Loading
Loading