diff --git a/THIRD_PARTY_LICENSES b/THIRD_PARTY_LICENSES index a13951f40..5f703362c 100644 --- a/THIRD_PARTY_LICENSES +++ b/THIRD_PARTY_LICENSES @@ -21,7 +21,7 @@ ** AWSSDK.ElasticLoadBalancingV2; version 3.7.302.33 -- https://www.nuget.org/packages/AWSSDK.ElasticLoadBalancingV2/ ** AWSSDK.Core; version 3.7.303.20 -- https://www.nuget.org/packages/AWSSDK.Core ** AWSSDK.CloudWatchLogs; version 3.7.305.18 -- https://www.nuget.org/packages/AWSSDK.CloudWatchLogs -** Amazon.CDK.Lib; version 2.131.0 -- https://www.nuget.org/packages/Amazon.CDK.Lib/ +** Amazon.CDK.Lib; version 2.146.0 -- https://www.nuget.org/packages/Amazon.CDK.Lib/ Apache License Version 2.0, January 2004 diff --git a/site/content/docs/cicd/recipes/ASP.NET Core App to AWS Elastic Beanstalk on Linux.md b/site/content/docs/cicd/recipes/ASP.NET Core App to AWS Elastic Beanstalk on Linux.md index 2b5756de4..2edfb8021 100644 --- a/site/content/docs/cicd/recipes/ASP.NET Core App to AWS Elastic Beanstalk on Linux.md +++ b/site/content/docs/cicd/recipes/ASP.NET Core App to AWS Elastic Beanstalk on Linux.md @@ -42,6 +42,10 @@ * ID: LoadBalancerType * Description: The type of load balancer for your environment. * Type: String +* **Load Balancer Scheme** + * ID: LoadBalancerScheme + * Description: Specify "Internal" if your application serves requests only from connected VPCs. "Public" load balancers serve requests from the Internet. + * Type: String * **Application IAM Role** * ID: ApplicationIAMRole * Description: The Identity and Access Management (IAM) role that provides AWS credentials to the application to access AWS services. diff --git a/site/content/docs/cicd/recipes/ASP.NET Core App to AWS Elastic Beanstalk on Windows.md b/site/content/docs/cicd/recipes/ASP.NET Core App to AWS Elastic Beanstalk on Windows.md index 5014bbec3..4c60763f5 100644 --- a/site/content/docs/cicd/recipes/ASP.NET Core App to AWS Elastic Beanstalk on Windows.md +++ b/site/content/docs/cicd/recipes/ASP.NET Core App to AWS Elastic Beanstalk on Windows.md @@ -37,6 +37,10 @@ * ID: LoadBalancerType * Description: The type of load balancer for your environment. * Type: String +* **Load Balancer Scheme** + * ID: LoadBalancerScheme + * Description: Specify "Internal" if your application serves requests only from connected VPCs. "Public" load balancers serve requests from the Internet. + * Type: String * **Application IAM Role** * ID: ApplicationIAMRole * Description: The Identity and Access Management (IAM) role that provides AWS credentials to the application to access AWS services. diff --git a/src/AWS.Deploy.CLI/App.cs b/src/AWS.Deploy.CLI/App.cs index ba618d0c6..1a06b8ca1 100644 --- a/src/AWS.Deploy.CLI/App.cs +++ b/src/AWS.Deploy.CLI/App.cs @@ -73,7 +73,7 @@ private static void SetExecutionEnvironment(string[] args) Environment.SetEnvironmentVariable(envName, envValue.ToString()); } - private static string GetToolVersion() + internal static string GetToolVersion() { var assembly = typeof(App).GetTypeInfo().Assembly; var version = assembly.GetCustomAttribute()?.Version; diff --git a/src/AWS.Deploy.CLI/Commands/CommandFactory.cs b/src/AWS.Deploy.CLI/Commands/CommandFactory.cs index a92a56553..2240d8a1b 100644 --- a/src/AWS.Deploy.CLI/Commands/CommandFactory.cs +++ b/src/AWS.Deploy.CLI/Commands/CommandFactory.cs @@ -2,13 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 using System; -using System.Collections; using System.CommandLine; using System.CommandLine.Invocation; using System.IO; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.InteropServices; using Amazon; using AWS.Deploy.CLI.Commands.TypeHints; using AWS.Deploy.CLI.Utilities; @@ -16,7 +12,6 @@ using AWS.Deploy.Common.Extensions; using AWS.Deploy.Orchestration; using AWS.Deploy.Orchestration.CDK; -using AWS.Deploy.Orchestration.Data; using AWS.Deploy.Orchestration.Utilities; using AWS.Deploy.CLI.Commands.CommandHandlerInput; using AWS.Deploy.Common.IO; @@ -37,6 +32,7 @@ public interface ICommandFactory public class CommandFactory : ICommandFactory { + private static readonly Option _optionVersion = new(new[] { "-v", "--version" }, "Show version information"); private static readonly Option _optionProfile = new("--profile", "AWS credential profile used to make calls to AWS."); private static readonly Option _optionRegion = new("--region", "AWS region to deploy the application to. For example, us-west-2."); private static readonly Option _optionProjectPath = new("--project-path", () => Directory.GetCurrentDirectory(), "Path to the project to deploy."); @@ -156,6 +152,7 @@ public Command BuildRootCommand() lock(s_root_command_lock) { + rootCommand.AddOption(_optionVersion); rootCommand.Add(BuildDeployCommand()); rootCommand.Add(BuildListCommand()); rootCommand.Add(BuildDeleteCommand()); @@ -163,6 +160,15 @@ public Command BuildRootCommand() rootCommand.Add(BuildServerModeCommand()); } + rootCommand.Handler = CommandHandler.Create((bool version) => + { + if (version) + { + var toolVersion = App.GetToolVersion(); + _toolInteractiveService.WriteLine($"Version: {toolVersion}"); + } + }); + return rootCommand; } diff --git a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs index 5446369f3..612818a20 100644 --- a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs @@ -128,6 +128,10 @@ public async Task ExecuteAsync(string applicationName, string deploymentProjectP return; } + // Because we're starting a deployment, clear the cached system capabilities checks + // in case the deployment fails and the user reruns it after modifying Docker or Node + _systemCapabilityEvaluator.ClearCachedCapabilityChecks(); + await CreateDeploymentBundle(orchestrator, selectedRecommendation, cloudApplication); if (saveSettingsConfig.SettingsType != SaveSettingsType.None) @@ -282,14 +286,14 @@ private void DisplayOutputResources(List displayedResourc /// The selected recommendation settings used for deployment. public async Task EvaluateSystemCapabilities(Recommendation selectedRecommendation) { - var systemCapabilities = await _systemCapabilityEvaluator.EvaluateSystemCapabilities(selectedRecommendation); + var missingSystemCapabilities = await _systemCapabilityEvaluator.EvaluateSystemCapabilities(selectedRecommendation); var missingCapabilitiesMessage = ""; - foreach (var capability in systemCapabilities) + foreach (var capability in missingSystemCapabilities) { missingCapabilitiesMessage = $"{missingCapabilitiesMessage}{Environment.NewLine}{capability.GetMessage()}{Environment.NewLine}"; } - if (systemCapabilities.Any()) + if (missingSystemCapabilities.Any()) throw new MissingSystemCapabilityException(DeployToolErrorCode.MissingSystemCapabilities, missingCapabilitiesMessage); } diff --git a/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs b/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs index 6764d7329..303524e91 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs @@ -74,17 +74,18 @@ public async Task StartDeploymentSession(StartDeploymentSessionIn Guid.NewGuid().ToString() ); - var serviceProvider = CreateSessionServiceProvider(output.SessionId, input.AWSRegion); - var awsResourceQueryer = serviceProvider.GetRequiredService(); - var state = new SessionState( output.SessionId, input.ProjectPath, input.AWSRegion, - (await awsResourceQueryer.GetCallerIdentity(input.AWSRegion)).Account, await _projectParserUtility.Parse(input.ProjectPath) ); + var serviceProvider = CreateSessionServiceProvider(state); + var awsResourceQueryer = serviceProvider.GetRequiredService(); + + state.AWSAccountId = (await awsResourceQueryer.GetCallerIdentity(input.AWSRegion)).Account; + _stateServer.Save(output.SessionId, state); var deployedApplicationQueryer = serviceProvider.GetRequiredService(); @@ -581,6 +582,10 @@ public async Task StartDeployment(string sessionId) if (capabilities.Any()) return Problem($"Unable to start deployment due to missing system capabilities.{Environment.NewLine}{missingCapabilitiesMessage}", statusCode: Microsoft.AspNetCore.Http.StatusCodes.Status424FailedDependency); + // Because we're starting a deployment, clear the cached system capabilities checks + // in case the deployment fails and the user reruns it after modifying Docker or Node + systemCapabilityEvaluator.ClearCachedCapabilityChecks(); + var task = new DeployRecommendationTask(orchestratorSession, orchestrator, state.ApplicationDetails, state.SelectedRecommendation); state.DeploymentTask = task.Execute(); @@ -668,11 +673,6 @@ public async Task GetDeploymentDetails(string sessionId) } private IServiceProvider CreateSessionServiceProvider(SessionState state) - { - return CreateSessionServiceProvider(state.SessionId, state.AWSRegion); - } - - private IServiceProvider CreateSessionServiceProvider(string sessionId, string awsRegion) { var awsCredentials = HttpContext.User.ToAWSCredentials(); if(awsCredentials == null) @@ -680,16 +680,34 @@ private IServiceProvider CreateSessionServiceProvider(string sessionId, string a throw new FailedToRetrieveAWSCredentialsException("AWS credentials are missing for the current session."); } - var interactiveServices = new SessionOrchestratorInteractiveService(sessionId, _hubContext); + var interactiveServices = new SessionOrchestratorInteractiveService(state.SessionId, _hubContext); var services = new ServiceCollection(); services.AddSingleton(interactiveServices); services.AddSingleton(services => { var wrapper = new CommandLineWrapper(interactiveServices, true); - wrapper.RegisterAWSContext(awsCredentials, awsRegion); + wrapper.RegisterAWSContext(awsCredentials, state.AWSRegion); return wrapper; }); + if (state.AWSResourceQueryService == null) + { + services.AddSingleton(); + } + else + { + services.AddSingleton(state.AWSResourceQueryService); + } + + if (state.SystemCapabilityEvaluator == null) + { + services.AddSingleton(); + } + else + { + services.AddSingleton(state.SystemCapabilityEvaluator); + } + services.AddCustomServices(); var serviceProvider = services.BuildServiceProvider(); @@ -698,9 +716,15 @@ private IServiceProvider CreateSessionServiceProvider(string sessionId, string a awsClientFactory.ConfigureAWSOptions(awsOptions => { awsOptions.Credentials = awsCredentials; - awsOptions.Region = RegionEndpoint.GetBySystemName(awsRegion); + awsOptions.Region = RegionEndpoint.GetBySystemName(state.AWSRegion); }); + // Cache the SessionAWSResourceQuery and SystemCapabilityEvaluator with the session state + // so they can be reused in future ServerMode API calls with the same session id. This avoids reloading + // existing resources from AWS and running the Docker/Node checks when they're not expected to change. + state.AWSResourceQueryService = serviceProvider.GetRequiredService() as SessionAWSResourceQuery; + state.SystemCapabilityEvaluator = serviceProvider.GetRequiredService() as SystemCapabilityEvaluator; + return serviceProvider; } diff --git a/src/AWS.Deploy.CLI/ServerMode/Services/SessionAWSResourceQuery.cs b/src/AWS.Deploy.CLI/ServerMode/Services/SessionAWSResourceQuery.cs new file mode 100644 index 000000000..c2f27853d --- /dev/null +++ b/src/AWS.Deploy.CLI/ServerMode/Services/SessionAWSResourceQuery.cs @@ -0,0 +1,366 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Threading.Tasks; +using Amazon.AppRunner.Model; +using Amazon.CloudControlApi.Model; +using Amazon.CloudFormation.Model; +using Amazon.CloudFront.Model; +using Amazon.CloudWatchEvents.Model; +using Amazon.EC2.Model; +using Amazon.ECR.Model; +using Amazon.ECS.Model; +using Amazon.ElasticBeanstalk.Model; +using Amazon.ElasticLoadBalancingV2; +using Amazon.IdentityManagement.Model; +using Amazon.S3.Model; +using Amazon.SecurityToken.Model; +using AWS.Deploy.Common.Data; +using AWS.Deploy.Orchestration.Data; +using Microsoft.Extensions.DependencyInjection; + +namespace AWS.Deploy.CLI.ServerMode.Services +{ + /// + /// This implementation of IAWSResourceQueryer wraps the normal AWSResourceQueryer implementation + /// but caches the responses for each of the AWS service calls. + /// + /// The lifetime of this class is a session in server mode. Everytime users restart deployment + /// a new instance of this class will be created with a new cache. + /// + public class SessionAWSResourceQuery : IAWSResourceQueryer + { + private readonly static JsonSerializerOptions _cacheKeyJsonOptions = new JsonSerializerOptions { WriteIndented = false }; + + private readonly IAWSResourceQueryer _awsResourceQueryer; + private readonly ConcurrentDictionary _cachedResponse = new(); + + /// + /// Construct an instance of SessionAWSResourceQuery. This will create an instance of AWSResourceQueryer + /// using the services in the IServiceProvider. + /// + /// + + public SessionAWSResourceQuery(IServiceProvider services) + { + _awsResourceQueryer = ActivatorUtilities.CreateInstance(services); + } + + /// + /// Construct an instance of SessionAWSResourceQuery wrapping the passed in IAWSResourceQueryer. + /// This constructor is primary included for unit testing and injecting a Mock version of IAWSResourceQueryer. + /// + /// The factory Create method with a private constructor is used to avoid this constructor being attempted + /// to be used in DI resolution. + /// + /// + private SessionAWSResourceQuery(IAWSResourceQueryer awsResourceQueryer) + { + _awsResourceQueryer = awsResourceQueryer; + } + + /// + /// Construct an instance of SessionAWSResourceQuery wrapping the passed in IAWSResourceQueryer. + /// This method is primary included for unit testing and injecting a Mock version of IAWSResourceQueryer. + /// + /// + public static IAWSResourceQueryer Create(IAWSResourceQueryer awsResourceQueryer) + { + return new SessionAWSResourceQuery(awsResourceQueryer); + } + + public static string CreateCacheKey(IEnumerable? args = null, [CallerMemberName] string caller = "") + { + var values = new List + { + caller + }; + if (args != null) + { + var argString = JsonSerializer.Serialize(args, typeof(IEnumerable), _cacheKeyJsonOptions); + values.Add(argString); + } + + var cacheKey = string.Join(",", values); + return cacheKey; + } + + private async Task GetAndCache(Func> func, IEnumerable? args = null, [CallerMemberName] string caller = "") + { + var cacheKey = CreateCacheKey(args, caller); + + if (_cachedResponse.TryGetValue(cacheKey, out var cacheItem)) + { + return (T?)cacheItem; + } + + cacheItem = await func(); + _cachedResponse[cacheKey] = cacheItem; + + return (T?)cacheItem; + } + + /// + public Task CreateEC2KeyPair(string keyName, string saveLocation) + { + var cacheKey = CreateCacheKey(null, nameof(ListOfEC2KeyPairs)); + _cachedResponse.TryRemove(cacheKey, out _); + + return _awsResourceQueryer.CreateEC2KeyPair(keyName, saveLocation); + } + + /// + public Task CreateECRRepository(string repositoryName, string recipeId) + { + // Since GetECRRepositories takes in an optional list repository names to get we need to clear + // all cached responses for GetECRRepositories. + var cacheKeys = _cachedResponse.Keys.Where(x => x.StartsWith(nameof(GetECRRepositories))); + foreach (var cacheKey in cacheKeys) + { + _cachedResponse.TryRemove(cacheKey, out _); + } + + return _awsResourceQueryer.CreateECRRepository(repositoryName, recipeId); + } + + /// + public async Task DescribeAppRunnerService(string serviceArn) + { + return (await GetAndCache(async () => await _awsResourceQueryer.DescribeAppRunnerService(serviceArn), new object[] { serviceArn }))!; + } + + /// + public async Task> DescribeAppRunnerVpcConnectors() + { + return (await GetAndCache(async () => await _awsResourceQueryer.DescribeAppRunnerVpcConnectors()))!; + } + + /// + public async Task> DescribeCloudFormationResources(string stackName) + { + return (await GetAndCache(async () => await _awsResourceQueryer.DescribeCloudFormationResources(stackName), new object[] { stackName }))!; + } + + /// + public async Task DescribeCloudWatchRule(string ruleName) + { + return (await GetAndCache(async () => await _awsResourceQueryer.DescribeCloudWatchRule(ruleName), new object[] { ruleName }))!; + } + + /// + public async Task DescribeECRRepository(string respositoryName) + { + return (await GetAndCache(async () => await _awsResourceQueryer.DescribeECRRepository(respositoryName), new object[] { respositoryName }))!; + } + + /// + public async Task DescribeElasticBeanstalkEnvironment(string environmentName) + { + return (await GetAndCache(async () => await _awsResourceQueryer.DescribeElasticBeanstalkEnvironment(environmentName), new object[] { environmentName }))!; + } + + /// + public async Task DescribeElasticLoadBalancer(string loadBalancerArn) + { + return (await GetAndCache(async () => await _awsResourceQueryer.DescribeElasticLoadBalancer(loadBalancerArn), new object[] { loadBalancerArn }))!; + } + + /// + public async Task> DescribeElasticLoadBalancerListeners(string loadBalancerArn) + { + return (await GetAndCache(async () => await _awsResourceQueryer.DescribeElasticLoadBalancerListeners(loadBalancerArn), new object[] { loadBalancerArn }))!; + } + + /// + public async Task DescribeInstanceType(string instanceType) + { + return (await GetAndCache(async () => await _awsResourceQueryer.DescribeInstanceType(instanceType), new object[] { instanceType }))!; + } + + /// + public async Task> DescribeSecurityGroups(string? vpcID = null) + { + return (await GetAndCache(async () => await _awsResourceQueryer.DescribeSecurityGroups(vpcID), new object?[] { vpcID }))!; + } + + /// + public async Task> DescribeSubnets(string? vpcID = null) + { + return (await GetAndCache(async () => await _awsResourceQueryer.DescribeSubnets(vpcID), new object?[] { vpcID }))!; + } + + /// + public async Task> GetBeanstalkEnvironmentConfigurationSettings(string environmentName) + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetBeanstalkEnvironmentConfigurationSettings(environmentName), new object[] { environmentName }))!; + } + + /// + public async Task GetCallerIdentity(string awsRegion) + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetCallerIdentity(awsRegion), new object[] { awsRegion }))!; + } + + /// + public async Task GetCloudControlApiResource(string type, string identifier) + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetCloudControlApiResource(type, identifier), new object[] { type, identifier }))!; + } + + /// + public async Task GetCloudFormationStack(string stackName) + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetCloudFormationStack(stackName), new object[] { stackName }))!; + } + + /// + public async Task> GetCloudFormationStackEvents(string stackName) + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetCloudFormationStackEvents(stackName), new object[] { stackName }))!; + } + + /// + public async Task> GetCloudFormationStacks() + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetCloudFormationStacks()))!; + } + + /// + public async Task GetCloudFrontDistribution(string distributionId) + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetCloudFrontDistribution(distributionId), new object[] { distributionId }))!; + } + + /// + public async Task GetDefaultVpc() + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetDefaultVpc())); + } + + /// + public async Task> GetECRAuthorizationToken() + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetECRAuthorizationToken()))!; + } + + /// + public async Task> GetECRRepositories(List? repositoryNames = null) + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetECRRepositories(repositoryNames), new object?[] { repositoryNames }))!; + } + + /// + public async Task> GetElasticBeanstalkPlatformArns(params BeanstalkPlatformType[]? platformTypes) + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetElasticBeanstalkPlatformArns(platformTypes), new object?[] { platformTypes }))!; + } + + /// + public async Task GetLatestElasticBeanstalkPlatformArn(BeanstalkPlatformType platformType) + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetLatestElasticBeanstalkPlatformArn(platformType), new object[] { platformType }))!; + } + + /// + public async Task> GetListOfVpcs() + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetListOfVpcs()))!; + } + + /// + public async Task GetParameterStoreTextValue(string parameterName) + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetParameterStoreTextValue(parameterName), new object[] { parameterName }))!; + } + + /// + public async Task GetS3BucketLocation(string bucketName) + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetS3BucketLocation(bucketName), new object[] { bucketName }))!; + } + + /// + public async Task GetS3BucketWebSiteConfiguration(string bucketName) + { + return (await GetAndCache(async () => await _awsResourceQueryer.GetS3BucketWebSiteConfiguration(bucketName), new object[] { bucketName }))!; + } + + /// + public async Task> ListElasticBeanstalkResourceTags(string resourceArn) + { + return (await GetAndCache(async () => await _awsResourceQueryer.ListElasticBeanstalkResourceTags(resourceArn), new object[] { resourceArn }))!; + } + + /// + public async Task> ListOfAvailableInstanceTypes() + { + return (await GetAndCache(async () => await _awsResourceQueryer.ListOfAvailableInstanceTypes()))!; + } + + /// + public async Task> ListOfDyanmoDBTables() + { + return (await GetAndCache(async () => await _awsResourceQueryer.ListOfDyanmoDBTables()))!; + } + + /// + public async Task> ListOfEC2KeyPairs() + { + return (await GetAndCache(async () => await _awsResourceQueryer.ListOfEC2KeyPairs()))!; + } + + /// + public async Task> ListOfECSClusters(string? ecsClusterName = null) + { + return (await GetAndCache(async () => await _awsResourceQueryer.ListOfECSClusters(ecsClusterName), new object?[] { ecsClusterName }))!; + } + + /// + public async Task> ListOfElasticBeanstalkApplications(string? applicationName = null) + { + return (await GetAndCache(async () => await _awsResourceQueryer.ListOfElasticBeanstalkApplications(applicationName), new object?[] { applicationName }))!; + } + + /// + public async Task> ListOfElasticBeanstalkEnvironments(string? applicationName = null, string? environmentName = null) + { + return (await GetAndCache(async () => await _awsResourceQueryer.ListOfElasticBeanstalkEnvironments(applicationName, environmentName), new object?[] { applicationName, environmentName }))!; + } + + /// + public async Task> ListOfIAMRoles(string? servicePrincipal) + { + return (await GetAndCache(async () => await _awsResourceQueryer.ListOfIAMRoles(servicePrincipal), new object?[] { servicePrincipal }))!; + } + + /// + public async Task> ListOfLoadBalancers(LoadBalancerTypeEnum loadBalancerType) + { + return (await GetAndCache(async () => await _awsResourceQueryer.ListOfLoadBalancers(loadBalancerType), new object[] { loadBalancerType }))!; + } + + /// + public async Task> ListOfS3Buckets() + { + return (await GetAndCache(async () => await _awsResourceQueryer.ListOfS3Buckets()))!; + } + + /// + public async Task> ListOfSNSTopicArns() + { + return (await GetAndCache(async () => await _awsResourceQueryer.ListOfSNSTopicArns()))!; + } + + /// + public async Task> ListOfSQSQueuesUrls() + { + return (await GetAndCache(async () => await _awsResourceQueryer.ListOfSQSQueuesUrls()))!; + } + } +} diff --git a/src/AWS.Deploy.CLI/ServerMode/SessionState.cs b/src/AWS.Deploy.CLI/ServerMode/SessionState.cs index fb3dcb2b1..19d9a3d36 100644 --- a/src/AWS.Deploy.CLI/ServerMode/SessionState.cs +++ b/src/AWS.Deploy.CLI/ServerMode/SessionState.cs @@ -1,10 +1,9 @@ // 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.Tasks; +using AWS.Deploy.CLI.ServerMode.Services; using AWS.Deploy.Common; using AWS.Deploy.Orchestration; @@ -30,21 +29,24 @@ public class SessionState public CloudApplication ApplicationDetails { get; } = new CloudApplication(string.Empty, string.Empty, CloudApplicationResourceType.None, string.Empty); + public SessionAWSResourceQuery? AWSResourceQueryService { get; set; } + + public SystemCapabilityEvaluator? SystemCapabilityEvaluator { get; set; } + public Task? DeploymentTask { get; set; } public SessionState( string sessionId, string projectPath, string awsRegion, - string awsAccountId, ProjectDefinition projectDefinition ) { SessionId = sessionId; ProjectPath = projectPath; AWSRegion = awsRegion; - AWSAccountId = awsAccountId; ProjectDefinition = projectDefinition; + AWSAccountId = string.Empty; } } } diff --git a/src/AWS.Deploy.CLI/Utilities/CommandLineWrapper.cs b/src/AWS.Deploy.CLI/Utilities/CommandLineWrapper.cs index e0e970a28..c845a0d06 100644 --- a/src/AWS.Deploy.CLI/Utilities/CommandLineWrapper.cs +++ b/src/AWS.Deploy.CLI/Utilities/CommandLineWrapper.cs @@ -45,8 +45,8 @@ public async Task Run( bool redirectIO = true, string? stdin = null, IDictionary? environmentVariables = null, - CancellationToken cancelToken = default, - bool needAwsCredentials = false) + bool needAwsCredentials = false, + CancellationToken cancellationToken = default) { StringBuilder strOutput = new StringBuilder(); StringBuilder strError = new StringBuilder(); @@ -116,7 +116,7 @@ public async Task Run( process.StandardInput.Close(); } - await process.WaitForExitAsync(); + await process.WaitForExitAsync(cancellationToken); if (onComplete != null) { diff --git a/src/AWS.Deploy.Common/Data/IAWSResourceQueryer.cs b/src/AWS.Deploy.Common/Data/IAWSResourceQueryer.cs index 998a15ad9..a51d009b5 100644 --- a/src/AWS.Deploy.Common/Data/IAWSResourceQueryer.cs +++ b/src/AWS.Deploy.Common/Data/IAWSResourceQueryer.cs @@ -73,7 +73,15 @@ public interface IAWSResourceQueryer Task GetLatestElasticBeanstalkPlatformArn(BeanstalkPlatformType platformType); Task> GetECRAuthorizationToken(); Task> GetECRRepositories(List? repositoryNames = null); - Task CreateECRRepository(string repositoryName); + + /// + /// Creates a new ECR repository + /// + /// The name to use for the repository + /// The id of the recipe that's being deployd, set as the value of the "aws-dotnet-deploy" tag + /// The repository that was created + Task CreateECRRepository(string repositoryName, string recipeId); + Task> GetCloudFormationStacks(); Task GetCloudFormationStack(string stackName); Task GetCallerIdentity(string awsRegion); diff --git a/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/SecurityGroupsInVpcValidator.cs b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/SecurityGroupsInVpcValidator.cs index 24493635b..ea0af31a5 100644 --- a/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/SecurityGroupsInVpcValidator.cs +++ b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/SecurityGroupsInVpcValidator.cs @@ -55,7 +55,11 @@ public async Task Validate(object input, Recommendation recomm if (shouldUseDefaultVpc) { - vpcId = (await _awsResourceQueryer.GetDefaultVpc())?.VpcId; + var vpc = await _awsResourceQueryer.GetDefaultVpc(); + if (vpc != null) + { + vpcId = vpc.VpcId; + } } } diff --git a/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs b/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs index 634c22f66..01c6628ec 100644 --- a/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs +++ b/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs @@ -714,7 +714,7 @@ public async Task> GetECRRepositories(List? repositoryN "Error attempting to list available ECR repositories"); } - public async Task CreateECRRepository(string repositoryName) + public async Task CreateECRRepository(string repositoryName, string recipeId) { var ecrClient = _awsClientFactory.GetAWSClient(); @@ -723,6 +723,22 @@ public async Task CreateECRRepository(string repositoryName) RepositoryName = repositoryName }; + // If a recipe ID was provided, set it as the tag value like we do for the CloudFormation stack + if (!string.IsNullOrEmpty(recipeId)) + { + // Ensure it fits in the maximum length for ECR + var tagValue = recipeId.Length > 256 ? recipeId.Substring(0,256) : recipeId; + + request.Tags = new List + { + new Amazon.ECR.Model.Tag + { + Key = Constants.CloudFormationIdentifier.STACK_TAG, + Value = tagValue + } + }; + } + var response = await HandleException(async () => await ecrClient.CreateRepositoryAsync(request), "Error attempting to create an ECR repository"); diff --git a/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs b/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs index 2497c668e..56c6fdaf8 100644 --- a/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs +++ b/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs @@ -97,7 +97,7 @@ public async Task PushDockerImageToECR(Recommendation recommendation, string rep await InitiateDockerLogin(); var tagSuffix = sourceTag.Split(":")[1]; - var repository = await SetupECRRepository(repositoryName); + var repository = await SetupECRRepository(repositoryName, recommendation.Recipe.Id); var targetTag = $"{repository.RepositoryUri}:{tagSuffix}"; await TagDockerImage(sourceTag, targetTag); @@ -234,7 +234,7 @@ private async Task InitiateDockerLogin() } } - private async Task SetupECRRepository(string ecrRepositoryName) + private async Task SetupECRRepository(string ecrRepositoryName, string recipeId) { var existingRepositories = await _awsResourceQueryer.GetECRRepositories(new List { ecrRepositoryName }); @@ -244,7 +244,7 @@ private async Task SetupECRRepository(string ecrRepositoryName) } else { - return await _awsResourceQueryer.CreateECRRepository(ecrRepositoryName); + return await _awsResourceQueryer.CreateECRRepository(ecrRepositoryName, recipeId); } } diff --git a/src/AWS.Deploy.Orchestration/SystemCapabilities.cs b/src/AWS.Deploy.Orchestration/SystemCapabilities.cs index b64055725..5d0af1980 100644 --- a/src/AWS.Deploy.Orchestration/SystemCapabilities.cs +++ b/src/AWS.Deploy.Orchestration/SystemCapabilities.cs @@ -2,23 +2,19 @@ namespace AWS.Deploy.Orchestration { - public class SystemCapabilities - { - public Version? NodeJsVersion { get; set; } - public DockerInfo DockerInfo { get; set; } - - public SystemCapabilities( - Version? nodeJsVersion, - DockerInfo dockerInfo) - { - NodeJsVersion = nodeJsVersion; - DockerInfo = dockerInfo; - } - } - + /// + /// Information about the user's Docker installation + /// public class DockerInfo { + /// + /// Whether or not Docker is installed + /// public bool DockerInstalled { get; set; } + + /// + /// Docker's current OSType, expected to be "windows" or "linux" + /// public string DockerContainerType { get; set; } public DockerInfo( @@ -30,6 +26,19 @@ public DockerInfo( } } + /// + /// Information about the user's NodeJS installation + /// + public class NodeInfo + { + /// + /// Version of Node if it's installed, else null if not detected + /// + public Version? NodeJsVersion { get; set; } + + public NodeInfo(Version? version) => NodeJsVersion = version; + } + public class SystemCapability { public readonly string Name; diff --git a/src/AWS.Deploy.Orchestration/SystemCapabilityEvaluator.cs b/src/AWS.Deploy.Orchestration/SystemCapabilityEvaluator.cs index 2782f4452..5108e1a97 100644 --- a/src/AWS.Deploy.Orchestration/SystemCapabilityEvaluator.cs +++ b/src/AWS.Deploy.Orchestration/SystemCapabilityEvaluator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using AWS.Deploy.Common; using AWS.Deploy.Common.Recipes; @@ -13,6 +14,13 @@ namespace AWS.Deploy.Orchestration { public interface ISystemCapabilityEvaluator { + /// + /// Clears the cache of successful capability checks, to ensure + /// that next time + /// is called they will be evaluated again. + /// + void ClearCachedCapabilityChecks(); + Task> EvaluateSystemCapabilities(Recommendation selectedRecommendation); } @@ -27,62 +35,101 @@ public class SystemCapabilityEvaluator : ISystemCapabilityEvaluator private readonly ICommandLineWrapper _commandLineWrapper; private static readonly Version MinimumNodeJSVersion = new Version(14,17,0); - public SystemCapabilityEvaluator(ICommandLineWrapper commandLineWrapper) - { - _commandLineWrapper = commandLineWrapper; - } + /// + /// How long to wait for the commands we run to determine if Node/Docker/etc. are installed to finish + /// + private const int CAPABILITY_EVALUATION_TIMEOUT_MS = 60000; // one minute - public async Task Evaluate() - { - var dockerTask = HasDockerInstalledAndRunning(); - var nodeTask = GetNodeJsVersion(); + /// + /// How long to cache the results of a VALID Node/Docker/etc. check + /// + private static readonly TimeSpan DEPENDENCY_CACHE_INTERVAL = TimeSpan.FromHours(1); - var capabilities = new SystemCapabilities(await nodeTask, await dockerTask); + /// + /// If we ran a successful Node evaluation, this is the timestamp until which that result + /// is valid and we will skip subsequent evaluations + /// + private DateTime _nodeDependencyValidUntilUtc = DateTime.MinValue; + + /// + /// If we ran a successful Docker evaluation, this is the timestamp until which that result + /// is valid and we will skip subsequent evaluations + /// + private DateTime _dockerDependencyValidUntilUtc = DateTime.MinValue; - return capabilities; + public SystemCapabilityEvaluator(ICommandLineWrapper commandLineWrapper) + { + _commandLineWrapper = commandLineWrapper; } - private async Task HasDockerInstalledAndRunning() + /// + /// Attempt to determine whether Docker is running and its current OS type + /// + private async Task HasDockerInstalledAndRunningAsync() { var processExitCode = -1; var containerType = ""; var command = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "docker info -f \"{{.OSType}}\"" : "docker info"; - await _commandLineWrapper.Run( - command, - streamOutputToInteractiveService: false, - onComplete: proc => - { - processExitCode = proc.ExitCode; - containerType = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? - proc.StandardOut?.TrimEnd('\n') ?? - throw new DockerInfoException(DeployToolErrorCode.FailedToCheckDockerInfo, "Failed to check if Docker is running in Windows or Linux container mode.") : - "linux"; - }); - - var dockerInfo = new DockerInfo(processExitCode == 0, containerType); + var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.CancelAfter(CAPABILITY_EVALUATION_TIMEOUT_MS); - return dockerInfo; + try + { + await _commandLineWrapper.Run( + command, + streamOutputToInteractiveService: false, + onComplete: proc => + { + processExitCode = proc.ExitCode; + containerType = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? + proc.StandardOut?.TrimEnd('\n') ?? + throw new DockerInfoException(DeployToolErrorCode.FailedToCheckDockerInfo, "Failed to check if Docker is running in Windows or Linux container mode.") : + "linux"; + }, + cancellationToken: cancellationTokenSource.Token); + + + var dockerInfo = new DockerInfo(processExitCode == 0, containerType); + + return dockerInfo; + } + catch (TaskCanceledException) + { + // If the check timed out, treat Docker as not installed + return new DockerInfo(false, ""); + } } /// - /// From https://docs.aws.amazon.com/cdk/latest/guide/work-with.html#work-with-prerequisites, - /// min version is 10.3 + /// Attempt to determine the installed Node.js version /// - private async Task GetNodeJsVersion() + private async Task GetNodeJsVersionAsync() { - // run node --version to get the version - var result = await _commandLineWrapper.TryRunWithResult("node --version"); + var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.CancelAfter(CAPABILITY_EVALUATION_TIMEOUT_MS); + + try + { + // run node --version to get the version + var result = await _commandLineWrapper.TryRunWithResult("node --version", cancellationToken: cancellationTokenSource.Token); + + var versionString = result.StandardOut ?? ""; - var versionString = result.StandardOut ?? ""; + if (versionString.StartsWith("v", StringComparison.OrdinalIgnoreCase)) + versionString = versionString.Substring(1, versionString.Length - 1); - if (versionString.StartsWith("v", StringComparison.OrdinalIgnoreCase)) - versionString = versionString.Substring(1, versionString.Length - 1); + if (!result.Success || !Version.TryParse(versionString, out var version)) + return new NodeInfo(null); - if (!result.Success || !Version.TryParse(versionString, out var version)) - return null; + return new NodeInfo(version); - return version; + } + catch (TaskCanceledException) + { + // If the check timed out, treat Node as not installed + return new NodeInfo(null); + } } /// @@ -90,42 +137,67 @@ await _commandLineWrapper.Run( /// public async Task> EvaluateSystemCapabilities(Recommendation selectedRecommendation) { - var capabilities = new List(); - var systemCapabilities = await Evaluate(); + var missingCapabilitiesForRecipe = new List(); string? message; + + // We only need to check that Node is installed if the user is deploying a recipe that uses CDK if (selectedRecommendation.Recipe.DeploymentType == DeploymentTypes.CdkProject) { - if (systemCapabilities.NodeJsVersion == null) + // If we haven't cached that NodeJS installation is valid, or the cache is expired + if (DateTime.UtcNow >= _nodeDependencyValidUntilUtc) { - - message = $"Install Node.js {MinimumNodeJSVersion} or later and restart your IDE/Shell. The latest Node.js LTS version is recommended. This deployment option uses the AWS CDK, which requires Node.js."; - - capabilities.Add(new SystemCapability(NODEJS_DEPENDENCY_NAME, message, NODEJS_INSTALLATION_URL)); - } - else if (systemCapabilities.NodeJsVersion < MinimumNodeJSVersion) - { - message = $"Install Node.js {MinimumNodeJSVersion} or later and restart your IDE/Shell. The latest Node.js LTS version is recommended. This deployment option uses the AWS CDK, which requires Node.js version higher than your current installation ({systemCapabilities.NodeJsVersion}). "; - - - capabilities.Add(new SystemCapability(NODEJS_DEPENDENCY_NAME, message, NODEJS_INSTALLATION_URL)); + var nodeInfo = await GetNodeJsVersionAsync(); + + if (nodeInfo.NodeJsVersion == null) + { + message = $"Install Node.js {MinimumNodeJSVersion} or later and restart your IDE/Shell. The latest Node.js LTS version is recommended. This deployment option uses the AWS CDK, which requires Node.js."; + + missingCapabilitiesForRecipe.Add(new SystemCapability(NODEJS_DEPENDENCY_NAME, message, NODEJS_INSTALLATION_URL)); + } + else if (nodeInfo.NodeJsVersion < MinimumNodeJSVersion) + { + message = $"Install Node.js {MinimumNodeJSVersion} or later and restart your IDE/Shell. The latest Node.js LTS version is recommended. This deployment option uses the AWS CDK, which requires Node.js version higher than your current installation ({nodeInfo.NodeJsVersion}). "; + + missingCapabilitiesForRecipe.Add(new SystemCapability(NODEJS_DEPENDENCY_NAME, message, NODEJS_INSTALLATION_URL)); + } + else // It is valid, so update the cache interval + { + _nodeDependencyValidUntilUtc = DateTime.UtcNow.Add(DEPENDENCY_CACHE_INTERVAL); + } } } + // We only need to check that Docker is installed if the user is deploying a recipe that uses Docker if (selectedRecommendation.Recipe.DeploymentBundle == DeploymentBundleTypes.Container) { - if (!systemCapabilities.DockerInfo.DockerInstalled) + if (DateTime.UtcNow >= _dockerDependencyValidUntilUtc) { - message = "Install and start Docker version appropriate for your OS. This deployment option requires Docker, which was not detected."; - capabilities.Add(new SystemCapability(DOCKER_DEPENDENCY_NAME, message, DOCKER_INSTALLATION_URL)); - } - else if (!systemCapabilities.DockerInfo.DockerContainerType.Equals("linux", StringComparison.OrdinalIgnoreCase)) - { - message = "This is Linux-based deployment. Switch your Docker from Windows to Linux container mode."; - capabilities.Add(new SystemCapability(DOCKER_DEPENDENCY_NAME, message)); + var dockerInfo = await HasDockerInstalledAndRunningAsync(); + + if (!dockerInfo.DockerInstalled) + { + message = "Install and start Docker version appropriate for your OS. This deployment option requires Docker, which was not detected."; + missingCapabilitiesForRecipe.Add(new SystemCapability(DOCKER_DEPENDENCY_NAME, message, DOCKER_INSTALLATION_URL)); + } + else if (!dockerInfo.DockerContainerType.Equals("linux", StringComparison.OrdinalIgnoreCase)) + { + message = "This is Linux-based deployment. Switch your Docker from Windows to Linux container mode."; + missingCapabilitiesForRecipe.Add(new SystemCapability(DOCKER_DEPENDENCY_NAME, message)); + } + else // It is valid, so update the cache interval + { + _dockerDependencyValidUntilUtc = DateTime.UtcNow.Add(DEPENDENCY_CACHE_INTERVAL); + } } } - return capabilities; + return missingCapabilitiesForRecipe; + } + + public void ClearCachedCapabilityChecks() + { + _nodeDependencyValidUntilUtc = DateTime.MinValue; + _dockerDependencyValidUntilUtc = DateTime.MinValue; } } } diff --git a/src/AWS.Deploy.Orchestration/Utilities/ICommandLineWrapper.cs b/src/AWS.Deploy.Orchestration/Utilities/ICommandLineWrapper.cs index cd21f50db..b66874ddc 100644 --- a/src/AWS.Deploy.Orchestration/Utilities/ICommandLineWrapper.cs +++ b/src/AWS.Deploy.Orchestration/Utilities/ICommandLineWrapper.cs @@ -34,17 +34,19 @@ public interface ICommandLineWrapper /// By default, , and will be redirected. /// Set this to false to avoid redirection. /// + /// + /// Text to pass into the process through standard input. + /// /// - /// is executed as a child process of running process which inherits the parent process's environment variables. - /// allows to add (replace if exists) extra environment variables to the child process. + /// is executed as a child process of running process which inherits the parent process's environment variables. + /// allows to add (replace if exists) extra environment variables to the child process. /// /// AWS Execution Environment string to append in AWS_EXECUTION_ENV env var. - /// AWS SDK calls made while executing will have User-Agent string containing + /// AWS SDK calls made while executing will have User-Agent string containing /// /// - /// - /// - /// + /// Whether the command requires AWS credentials, which will be set as environment variables + /// Token which can be used to cancel the task public Task Run( string command, string workingDirectory = "", @@ -53,8 +55,9 @@ public Task Run( bool redirectIO = true, string? stdin = null, IDictionary? environmentVariables = null, - CancellationToken cancelToken = default, - bool needAwsCredentials = false); + bool needAwsCredentials = false, + CancellationToken cancellationToken = default + ); /// /// Configure the child process that executes the command passed as parameter in method. @@ -92,16 +95,15 @@ public static class CommandLineWrapperExtensions /// Text to pass into the process through standard input. /// /// - /// is executed as a child process of running process which inherits the parent process's environment variables. - /// allows to add (replace if exists) extra environment variables to the child process. + /// is executed as a child process of running process which inherits the parent process's environment variables. + /// allows to add (replace if exists) extra environment variables to the child process. /// /// AWS Execution Environment string to append in AWS_EXECUTION_ENV env var. - /// AWS SDK calls made while executing will have User-Agent string containing + /// AWS SDK calls made while executing will have User-Agent string containing /// /// - /// - /// - /// + /// Whether the command requires AWS credentials, which will be set as environment variables + /// Token which can be used to cancel the task public static async Task TryRunWithResult( this ICommandLineWrapper commandLineWrapper, string command, @@ -110,8 +112,8 @@ public static async Task TryRunWithResult( bool redirectIO = true, string? stdin = null, IDictionary? environmentVariables = null, - CancellationToken cancelToken = default, - bool needAwsCredentials = false) + bool needAwsCredentials = false, + CancellationToken cancellationToken = default) { var result = new TryRunResult(); @@ -123,8 +125,8 @@ await commandLineWrapper.Run( redirectIO: redirectIO, stdin: stdin, environmentVariables: environmentVariables, - cancelToken: cancelToken, - needAwsCredentials: needAwsCredentials); + needAwsCredentials: needAwsCredentials, + cancellationToken: cancellationToken); return result; } diff --git a/src/AWS.Deploy.Recipes.CDK.Common/AWS.Deploy.Recipes.CDK.Common.csproj b/src/AWS.Deploy.Recipes.CDK.Common/AWS.Deploy.Recipes.CDK.Common.csproj index cca2e6df5..50f742e54 100644 --- a/src/AWS.Deploy.Recipes.CDK.Common/AWS.Deploy.Recipes.CDK.Common.csproj +++ b/src/AWS.Deploy.Recipes.CDK.Common/AWS.Deploy.Recipes.CDK.Common.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppAppRunner/AspNetAppAppRunner.csproj b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppAppRunner/AspNetAppAppRunner.csproj index f9eadfa62..efcba2f6d 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppAppRunner/AspNetAppAppRunner.csproj +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppAppRunner/AspNetAppAppRunner.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/AspNetAppEcsFargate.csproj b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/AspNetAppEcsFargate.csproj index 591ce57e9..36688bbef 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/AspNetAppEcsFargate.csproj +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/AspNetAppEcsFargate.csproj @@ -25,7 +25,7 @@ - + - + diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs index 5630527c2..377dbc6fc 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs @@ -53,6 +53,11 @@ public partial class Configuration /// public string LoadBalancerType { get; set; } = Recipe.LOADBALANCERTYPE_APPLICATION; + /// + /// Whether the load balancer is visibile to the public internet ("public") or only to the connected VPC ("internal"). + /// + public string LoadBalancerScheme { get; set; } = Recipe.LOADBALANCERSCHEME_PUBLIC; + /// /// The EC2 Key Pair used for the Beanstalk Application. /// @@ -131,7 +136,8 @@ public Configuration( string loadBalancerType = Recipe.LOADBALANCERTYPE_APPLICATION, string reverseProxy = Recipe.REVERSEPROXY_NGINX, bool xrayTracingSupportEnabled = false, - string enhancedHealthReporting = Recipe.ENHANCED_HEALTH_REPORTING) + string enhancedHealthReporting = Recipe.ENHANCED_HEALTH_REPORTING, + string loadBalancerScheme = Recipe.LOADBALANCERSCHEME_PUBLIC) { ApplicationIAMRole = applicationIAMRole; ServiceIAMRole = serviceIAMRole; @@ -146,6 +152,7 @@ public Configuration( VPC = vpc; EnvironmentType = environmentType; LoadBalancerType = loadBalancerType; + LoadBalancerScheme = loadBalancerScheme; XRayTracingSupportEnabled = xrayTracingSupportEnabled; ReverseProxy = reverseProxy; EnhancedHealthReporting = enhancedHealthReporting; diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs index 7c096d5d3..426670095 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs @@ -31,6 +31,7 @@ public class Recipe : Construct public const string ENVIRONMENTTYPE_LOADBALANCED = "LoadBalanced"; public const string LOADBALANCERTYPE_APPLICATION = "application"; + public const string LOADBALANCERSCHEME_PUBLIC = "public"; public const string REVERSEPROXY_NGINX = "nginx"; @@ -249,14 +250,19 @@ private void ConfigureBeanstalkEnvironment(Configuration settings, string beanst if (settings.EnvironmentType.Equals(ENVIRONMENTTYPE_LOADBALANCED)) { - optionSettingProperties.Add( - new CfnEnvironment.OptionSettingProperty - { - Namespace = "aws:elasticbeanstalk:environment", - OptionName = "LoadBalancerType", - Value = settings.LoadBalancerType - } - ); + optionSettingProperties.Add(new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:elasticbeanstalk:environment", + OptionName = "LoadBalancerType", + Value = settings.LoadBalancerType + }); + + optionSettingProperties.Add(new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:ec2:vpc", + OptionName = "ELBScheme", + Value = settings.LoadBalancerScheme + }); if (!string.IsNullOrEmpty(settings.HealthCheckURL)) { diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkWindows/AspNetAppElasticBeanstalkWindows.csproj b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkWindows/AspNetAppElasticBeanstalkWindows.csproj index 7095f9c56..369fb8680 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkWindows/AspNetAppElasticBeanstalkWindows.csproj +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkWindows/AspNetAppElasticBeanstalkWindows.csproj @@ -25,7 +25,7 @@ - + diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkWindows/Generated/Configurations/Configuration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkWindows/Generated/Configurations/Configuration.cs index 9ecd07c93..7ab2a0ecc 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkWindows/Generated/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkWindows/Generated/Configurations/Configuration.cs @@ -53,6 +53,11 @@ public partial class Configuration /// public string LoadBalancerType { get; set; } = Recipe.LOADBALANCERTYPE_APPLICATION; + /// + /// Whether the load balancer is visibile to the public internet ("public") or only to the connected VPC ("internal"). + /// + public string LoadBalancerScheme { get; set; } = Recipe.LOADBALANCERSCHEME_PUBLIC; + /// /// The EC2 Key Pair used for the Beanstalk Application. /// @@ -135,7 +140,8 @@ public Configuration( string environmentType = Recipe.ENVIRONMENTTYPE_SINGLEINSTANCE, string loadBalancerType = Recipe.LOADBALANCERTYPE_APPLICATION, bool xrayTracingSupportEnabled = false, - string enhancedHealthReporting = Recipe.ENHANCED_HEALTH_REPORTING) + string enhancedHealthReporting = Recipe.ENHANCED_HEALTH_REPORTING, + string loadBalancerScheme = Recipe.LOADBALANCERSCHEME_PUBLIC) { ApplicationIAMRole = applicationIAMRole; ServiceIAMRole = serviceIAMRole; @@ -150,6 +156,7 @@ public Configuration( VPC = vpc; EnvironmentType = environmentType; LoadBalancerType = loadBalancerType; + LoadBalancerScheme = loadBalancerScheme; XRayTracingSupportEnabled = xrayTracingSupportEnabled; EnhancedHealthReporting = enhancedHealthReporting; HealthCheckURL = healthCheckURL; diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkWindows/Generated/Recipe.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkWindows/Generated/Recipe.cs index e5c6eec3a..363d89e9a 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkWindows/Generated/Recipe.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkWindows/Generated/Recipe.cs @@ -31,6 +31,7 @@ public class Recipe : Construct public const string ENVIRONMENTTYPE_LOADBALANCED = "LoadBalanced"; public const string LOADBALANCERTYPE_APPLICATION = "application"; + public const string LOADBALANCERSCHEME_PUBLIC = "public"; public const string REVERSEPROXY_NGINX = "nginx"; @@ -258,6 +259,13 @@ private void ConfigureBeanstalkEnvironment(Configuration settings, string beanst } ); + optionSettingProperties.Add(new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:ec2:vpc", + OptionName = "ELBScheme", + Value = settings.LoadBalancerScheme + }); + if (!string.IsNullOrEmpty(settings.HealthCheckURL)) { optionSettingProperties.Add( diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/BlazorWasm.csproj b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/BlazorWasm.csproj index c49ae4190..0cc264809 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/BlazorWasm.csproj +++ b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/BlazorWasm.csproj @@ -25,7 +25,7 @@ - + - + - +