Skip to content

Commit

Permalink
feat: Add server mode framework to be used for IDE integration
Browse files Browse the repository at this point in the history
  • Loading branch information
normj authored and philasmar committed May 14, 2021
1 parent 651b977 commit ad0f0f6
Show file tree
Hide file tree
Showing 40 changed files with 2,649 additions and 36 deletions.
16 changes: 15 additions & 1 deletion AWS.Deploy.sln
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ContosoUniversity", "testap
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ContosoUniversityBackendService", "testapps\ContosoUniversityBackendService\ContosoUniversityBackendService.csproj", "{AC7B0880-00CB-41E0-996C-B4C99103CEF9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Deploy.CLI.IntegrationTests", "test\AWS.Deploy.CLI.IntegrationTests\AWS.Deploy.CLI.IntegrationTests.csproj", "{001A0CE1-CCCE-4006-8CAC-AD18E37060A3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AWS.Deploy.CLI.IntegrationTests", "test\AWS.Deploy.CLI.IntegrationTests\AWS.Deploy.CLI.IntegrationTests.csproj", "{001A0CE1-CCCE-4006-8CAC-AD18E37060A3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AWS.Deploy.ServerMode.Client", "src\AWS.Deploy.ServerMode.Client\AWS.Deploy.ServerMode.Client.csproj", "{1B171F48-D200-407D-A9F2-179E2A602821}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AWS.Deploy.ServerMode.ClientGenerator", "src\AWS.Deploy.ServerMode.ClientGenerator\AWS.Deploy.ServerMode.ClientGenerator.csproj", "{C533AA26-797A-4A41-BA28-2364B66DAA5F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -140,6 +144,14 @@ Global
{001A0CE1-CCCE-4006-8CAC-AD18E37060A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{001A0CE1-CCCE-4006-8CAC-AD18E37060A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{001A0CE1-CCCE-4006-8CAC-AD18E37060A3}.Release|Any CPU.Build.0 = Release|Any CPU
{1B171F48-D200-407D-A9F2-179E2A602821}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B171F48-D200-407D-A9F2-179E2A602821}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B171F48-D200-407D-A9F2-179E2A602821}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B171F48-D200-407D-A9F2-179E2A602821}.Release|Any CPU.Build.0 = Release|Any CPU
{C533AA26-797A-4A41-BA28-2364B66DAA5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C533AA26-797A-4A41-BA28-2364B66DAA5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C533AA26-797A-4A41-BA28-2364B66DAA5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C533AA26-797A-4A41-BA28-2364B66DAA5F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -165,6 +177,8 @@ Global
{6EDCA334-3317-44A4-A017-2A8D810EE7A7} = {C3A0C716-BDEA-4393-B223-AF8F8531522A}
{AC7B0880-00CB-41E0-996C-B4C99103CEF9} = {C3A0C716-BDEA-4393-B223-AF8F8531522A}
{001A0CE1-CCCE-4006-8CAC-AD18E37060A3} = {BD466B5C-D8B0-4069-98A9-6DC8F01FA757}
{1B171F48-D200-407D-A9F2-179E2A602821} = {11C7056E-93C1-408B-BD87-5270595BBE0E}
{C533AA26-797A-4A41-BA28-2364B66DAA5F} = {11C7056E-93C1-408B-BD87-5270595BBE0E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5A4B2863-1763-4496-B122-651A38A4F5D7}
Expand Down
6 changes: 5 additions & 1 deletion src/AWS.Deploy.CLI/AWS.Deploy.CLI.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand All @@ -15,12 +15,16 @@
<LangVersion>Latest</LangVersion>
<PackageIcon>icon.png</PackageIcon>
<PackageProjectUrl>https://github.com/aws/aws-dotnet-deploy</PackageProjectUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1570;1591</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.IdentityManagement" Version="3.5.0.65" />
<PackageReference Include="AWSSDK.CloudFormation" Version="3.5.2.27" />
<PackageReference Include="AWSSDK.SecurityToken" Version="3.5.1.45" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.1.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.2" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" />
</ItemGroup>

Expand Down
42 changes: 42 additions & 0 deletions src/AWS.Deploy.CLI/Commands/CommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public Command BuildRootCommand()
rootCommand.Add(BuildDeployCommand());
rootCommand.Add(BuildListCommand());
rootCommand.Add(BuildDeleteCommand());
rootCommand.Add(BuildServerModeCommand());

return rootCommand;
}
Expand Down Expand Up @@ -294,5 +295,46 @@ private Command BuildListCommand()
});
return listCommand;
}

private Command BuildServerModeCommand()
{
var serverModeCommand = new Command(
"server-mode",
"Launches the tool in a server mode for IDEs like Visual Studio to integrate with.")
{
new Option<int>(new []{"--port"}, description: "Port the server mode will listen to."),
new Option<int>(new []{"--parent-pid"}, description: "The ID of the process that is launching server mode. Server mode will exit when the parent pid terminates."),
_optionDiagnosticLogging
};
serverModeCommand.Handler = CommandHandler.Create<int, int?, bool>(async (port, parentPid, diagnostics) =>
{
try
{
var toolInteractiveService = new ConsoleInteractiveServiceImpl(diagnostics);
var serverMode = new ServerModeCommand(toolInteractiveService, port, parentPid);

await serverMode.ExecuteAsync();
}
catch (Exception e) when (e.IsAWSDeploymentExpectedException())
{
if (diagnostics)
_toolInteractiveService.WriteErrorLine(e.PrettyPrint());
else
{
_toolInteractiveService.WriteErrorLine(string.Empty);
_toolInteractiveService.WriteErrorLine(e.Message);
}
}
catch (Exception e)
{
// This is a bug
_toolInteractiveService.WriteErrorLine(
"Unhandled exception. This is a bug. Please copy the stack trace below and file a bug at https://github.com/aws/aws-dotnet-deploy. " +
e.PrettyPrint());
}
});

return serverModeCommand;
}
}
}
41 changes: 41 additions & 0 deletions src/AWS.Deploy.CLI/Commands/ServerModeCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AWS.Deploy.CLI.ServerMode;
using Microsoft.AspNetCore.Hosting;

namespace AWS.Deploy.CLI.Commands
{
public class ServerModeCommand
{
private readonly IToolInteractiveService _interactiveService;
private readonly int _port;
private readonly int? _parentPid;

public ServerModeCommand(IToolInteractiveService interactiveService, int port, int? parentPid)
{
_interactiveService = interactiveService;
_port = port;
_parentPid = parentPid;
}

public async Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var url = $"http://localhost:{_port}";

var builder = new WebHostBuilder()
.UseKestrel()
.UseUrls(url)
.UseStartup<Startup>();

var host = builder.Build();

await host.RunAsync(cancellationToken);
}
}
}
54 changes: 27 additions & 27 deletions src/AWS.Deploy.CLI/Extensions/CustomServiceCollectionExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using AWS.Deploy.Orchestration.Data;
using AWS.Deploy.Orchestration.Utilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace AWS.Deploy.CLI.Extensions
{
Expand All @@ -22,36 +23,35 @@ public static class CustomServiceCollectionExtension
/// It is safer to use singleton instances for dependencies because every command (ex. deploy, list-deployments) run as a separate instance.
/// </summary>
/// <param name="serviceCollection"><see cref="IServiceCollection"/> instance that holds the app dependencies.</param>
public static void AddCustomServices(this IServiceCollection serviceCollection)
/// <param name="lifetime"></param>
public static void AddCustomServices(this IServiceCollection serviceCollection, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
// dependencies that are required in various part of the app
serviceCollection.AddSingleton<IAWSClientFactory, DefaultAWSClientFactory>();
serviceCollection.AddSingleton<IAWSResourceQueryer, AWSResourceQueryer>();
serviceCollection.AddSingleton<IAWSUtilities, AWSUtilities>();
serviceCollection.AddSingleton<ICDKInstaller, CDKInstaller>();
serviceCollection.AddSingleton<ICDKManager, CDKManager>();
serviceCollection.AddSingleton<ICdkProjectHandler, CdkProjectHandler>();
serviceCollection.AddSingleton<ICloudApplicationNameGenerator, CloudApplicationNameGenerator>();
serviceCollection.AddSingleton<ICommandLineWrapper, CommandLineWrapper>();
serviceCollection.AddSingleton<IConsoleUtilities, ConsoleUtilities>();
serviceCollection.AddSingleton<IDeployedApplicationQueryer, DeployedApplicationQueryer>();
serviceCollection.AddSingleton<IDeploymentBundleHandler, DeploymentBundleHandler>();
serviceCollection.AddSingleton<IDirectoryManager, DirectoryManager>();
serviceCollection.AddSingleton<IFileManager, FileManager>();
serviceCollection.AddSingleton<INPMPackageInitializer, NPMPackageInitializer>();
serviceCollection.AddSingleton<IOrchestratorInteractiveService, ConsoleOrchestratorLogger>();
serviceCollection.AddSingleton<IPackageJsonGenerator, PackageJsonGenerator>();
serviceCollection.AddSingleton<IProjectDefinitionParser, ProjectDefinitionParser>();
serviceCollection.AddSingleton<IProjectParserUtility, ProjectParserUtility>();
serviceCollection.AddSingleton<ISystemCapabilityEvaluator, SystemCapabilityEvaluator>();
serviceCollection.AddSingleton<ITemplateMetadataReader, TemplateMetadataReader>();
serviceCollection.AddSingleton<IToolInteractiveService, ConsoleInteractiveServiceImpl>();
serviceCollection.AddSingleton<ITypeHintCommandFactory, TypeHintCommandFactory>();
serviceCollection.AddSingleton<IZipFileManager, ZipFileManager>();
serviceCollection.AddSingleton<ICommandFactory, CommandFactory>();
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IAWSClientFactory), typeof(DefaultAWSClientFactory), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IAWSResourceQueryer), typeof(AWSResourceQueryer), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IAWSUtilities), typeof(AWSUtilities), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ICDKInstaller), typeof(CDKInstaller), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ICDKManager), typeof(CDKManager), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ICdkProjectHandler), typeof(CdkProjectHandler), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ICloudApplicationNameGenerator), typeof(CloudApplicationNameGenerator), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ICommandLineWrapper), typeof(CommandLineWrapper), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IConsoleUtilities), typeof(ConsoleUtilities), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDeployedApplicationQueryer), typeof(DeployedApplicationQueryer), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDeploymentBundleHandler), typeof(DeploymentBundleHandler), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDirectoryManager), typeof(DirectoryManager), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IFileManager), typeof(FileManager), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(INPMPackageInitializer), typeof(NPMPackageInitializer), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IOrchestratorInteractiveService), typeof(ConsoleOrchestratorLogger), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IProjectDefinitionParser), typeof(ProjectDefinitionParser), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IProjectParserUtility), typeof(ProjectParserUtility), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ISystemCapabilityEvaluator), typeof(SystemCapabilityEvaluator), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ITemplateMetadataReader), typeof(TemplateMetadataReader), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IToolInteractiveService), typeof(ConsoleInteractiveServiceImpl), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ITypeHintCommandFactory), typeof(TypeHintCommandFactory), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IZipFileManager), typeof(ZipFileManager), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ICommandFactory), typeof(CommandFactory), lifetime));

var packageJsonTemplate = typeof(PackageJsonGenerator).Assembly.ReadEmbeddedFile(PackageJsonGenerator.TemplateIdentifier);
serviceCollection.AddSingleton<IPackageJsonGenerator>(new PackageJsonGenerator(packageJsonTemplate));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IPackageJsonGenerator), (serviceProvider) => new PackageJsonGenerator(packageJsonTemplate), lifetime));

// required to run the application
serviceCollection.AddSingleton<App>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;

using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;

namespace AWS.Deploy.CLI.ServerMode
{
public class AwsCredentialsAuthenticationSchemeOptions
: AuthenticationSchemeOptions
{ }

/// <summary>
/// The ASP.NET Core Authentication handler. Verify the Authorization header has been correctly set with AWS Credentials.
/// </summary>
public class AwsCredentialsAuthenticationHandler : AuthenticationHandler<AwsCredentialsAuthenticationSchemeOptions>
{
public const string SchemaName = "aws-deploy-tool-server-mode";

public const string ClaimAwsAccessKeyId = "awsAccessKeyId";
public const string ClaimAwsSecretKey = "awsSecretKey";
public const string ClaimAwsSessionToken = "awsSessionToken";

public AwsCredentialsAuthenticationHandler(
IOptionsMonitor<AwsCredentialsAuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}

protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue("Authorization", out var value))
{
return Task.FromResult(AuthenticateResult.Fail("Missing Authorization header"));
}

return Task.FromResult(ProcessAuthorizationHeader(value));
}

public static AuthenticateResult ProcessAuthorizationHeader(string authorizationHeaderValue)
{
var tokens = authorizationHeaderValue.Split(' ');
if (tokens.Length != 2)
{
return AuthenticateResult.Fail($"Incorrect format Authorization header. Format should be \"{SchemaName} <base-64-auth-parameters>\"");
}
if (!string.Equals(SchemaName, tokens[0]))
{
return AuthenticateResult.Fail($"Unsupported authorization schema. Supported schema: {SchemaName}");
}

try
{
var base64Bytes = Convert.FromBase64String(tokens[1]);
var json = Encoding.UTF8.GetString(base64Bytes);

var authParameters = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);

var claimIdentity = new ClaimsIdentity(nameof(AwsCredentialsAuthenticationHandler));
foreach (var kvp in authParameters)
{
claimIdentity.AddClaim(new Claim(kvp.Key, kvp.Value));
}

var ticket = new AuthenticationTicket(
new ClaimsPrincipal(claimIdentity), SchemaName);

return AuthenticateResult.Success(ticket);
}
catch (Exception)
{
return AuthenticateResult.Fail("Error decoding authorization value");
}
}
}

}
Loading

0 comments on commit ad0f0f6

Please sign in to comment.