From a825ef13d758fcf06096d62a60f1a8423345e299 Mon Sep 17 00:00:00 2001 From: aws-sdk-dotnet-automation Date: Tue, 9 Nov 2021 19:53:47 +0000 Subject: [PATCH 01/10] build: version bump to 0.31 --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 2848fe8f8..cf3dafc99 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.30", + "version": "0.31", "publicReleaseRefSpec": [ ".*" ], From e4ac432b3598bf7d0dad93f23b3a763e2fafd080 Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Fri, 19 Nov 2021 16:38:55 -0500 Subject: [PATCH 02/10] feat: Add AWS X-Ray Tracing support to beanstalk deployments --- .../Generated/Configurations/Configuration.cs | 9 ++++++++- .../AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs | 6 ++++++ .../RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe | 9 +++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) 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 d4c31ac39..1db75803f 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs @@ -56,6 +56,11 @@ public partial class Configuration /// public ElasticBeanstalkManagedPlatformUpdatesConfiguration ElasticBeanstalkManagedPlatformUpdates { get; set; } + /// + /// Specifies whether to enable or disable AWS X-Ray tracing support. + /// + public bool XRayTracingSupportEnabled { get; set; } = false; + /// A parameterless constructor is needed for /// or the classes will fail to initialize. /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. @@ -75,7 +80,8 @@ public Configuration( string ec2KeyPair, ElasticBeanstalkManagedPlatformUpdatesConfiguration elasticBeanstalkManagedPlatformUpdates, string environmentType = Recipe.ENVIRONMENTTYPE_SINGLEINSTANCE, - string loadBalancerType = Recipe.LOADBALANCERTYPE_APPLICATION) + string loadBalancerType = Recipe.LOADBALANCERTYPE_APPLICATION, + bool xrayTracingSupportEnabled = false) { ApplicationIAMRole = applicationIAMRole; InstanceType = instanceType; @@ -86,6 +92,7 @@ public Configuration( ElasticBeanstalkManagedPlatformUpdates = elasticBeanstalkManagedPlatformUpdates; EnvironmentType = environmentType; LoadBalancerType = loadBalancerType; + XRayTracingSupportEnabled = xrayTracingSupportEnabled; } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs index 776f5608f..50a26b909 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs @@ -144,6 +144,12 @@ private void ConfigureBeanstalkEnvironment(Configuration settings) Namespace = "aws:elasticbeanstalk:managedactions", OptionName = "ManagedActionsEnabled", Value = settings.ElasticBeanstalkManagedPlatformUpdates.ManagedActionsEnabled.ToString().ToLower() + }, + new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:elasticbeanstalk:xray", + OptionName = "XRayEnabled", + Value = settings.XRayTracingSupportEnabled.ToString().ToLower() } }; diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe index 06bb1b421..9d006e671 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe @@ -329,6 +329,15 @@ ] } ] + }, + { + "Id": "XRayTracingSupportEnabled", + "Name": "Enable AWS X-Ray Tracing Support", + "Description": "AWS X-Ray is a service that collects data about requests that your application serves, and provides tools you can use to view, filter, and gain insights into that data to identify issues and opportunities for optimization. Do you want to enable AWS X-Ray tracing support?", + "Type": "Bool", + "DefaultValue": false, + "AdvancedSetting": true, + "Updatable": true } ] } From 227a86175c5bb5a594a44b80800385b2fd89cf70 Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Fri, 19 Nov 2021 13:43:57 -0500 Subject: [PATCH 03/10] feat: Add ability to set Reverse Proxy in Beanstalk deployment --- .../Generated/Configurations/Configuration.cs | 7 +++++++ .../Generated/Recipe.cs | 14 ++++++++++++++ .../ASP.NETAppElasticBeanstalk.recipe | 13 +++++++++++++ 3 files changed, 34 insertions(+) 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 1db75803f..3c7a6a28e 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs @@ -61,6 +61,11 @@ public partial class Configuration /// public bool XRayTracingSupportEnabled { get; set; } = false; + /// + /// The reverse proxy to use. + /// + public string ReverseProxy { get; set; } = Recipe.REVERSEPROXY_NGINX; + /// A parameterless constructor is needed for /// or the classes will fail to initialize. /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. @@ -81,6 +86,7 @@ public Configuration( ElasticBeanstalkManagedPlatformUpdatesConfiguration elasticBeanstalkManagedPlatformUpdates, string environmentType = Recipe.ENVIRONMENTTYPE_SINGLEINSTANCE, string loadBalancerType = Recipe.LOADBALANCERTYPE_APPLICATION, + string reverseProxy = Recipe.REVERSEPROXY_NGINX, bool xrayTracingSupportEnabled = false) { ApplicationIAMRole = applicationIAMRole; @@ -93,6 +99,7 @@ public Configuration( EnvironmentType = environmentType; LoadBalancerType = loadBalancerType; XRayTracingSupportEnabled = xrayTracingSupportEnabled; + ReverseProxy = reverseProxy; } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs index 50a26b909..41894ff69 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs @@ -26,6 +26,8 @@ public class Recipe : Construct public const string LOADBALANCERTYPE_APPLICATION = "application"; + public const string REVERSEPROXY_NGINX = "nginx"; + public IRole? AppIAMRole { get; private set; } public IRole? BeanstalkServiceRole { get; private set; } @@ -220,6 +222,18 @@ private void ConfigureBeanstalkEnvironment(Configuration settings) }); } + if (!string.IsNullOrEmpty(settings.ReverseProxy)) + { + optionSettingProperties.Add( + new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:elasticbeanstalk:environment:proxy", + OptionName = "ProxyServer", + Value = settings.ReverseProxy + } + ); + } + BeanstalkEnvironment = new CfnEnvironment(this, nameof(BeanstalkEnvironment), InvokeCustomizeCDKPropsEvent(nameof(BeanstalkEnvironment), this, new CfnEnvironmentProps { EnvironmentName = settings.EnvironmentName, diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe index 9d006e671..d4006f882 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe @@ -338,6 +338,19 @@ "DefaultValue": false, "AdvancedSetting": true, "Updatable": true + }, + { + "Id": "ReverseProxy", + "Name": "Reverse Proxy", + "Description": "By default Nginx is used as a reverse proxy in front of the .NET Core web server Kestrel. To use Kestrel as the front facing web server then select `none` as the reverse proxy.", + "Type": "String", + "DefaultValue": "nginx", + "AllowedValues": [ + "nginx", + "none" + ], + "AdvancedSetting": true, + "Updatable": true } ] } From 64538c32d95b9f59c98bdace21f519f5f6a6cff6 Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Fri, 3 Dec 2021 12:35:53 -0500 Subject: [PATCH 04/10] feat: add enhanced health reporting setting to beanstalk deployments --- .../Generated/Configurations/Configuration.cs | 9 ++++++- .../Generated/Recipe.cs | 8 ++++++ .../ASP.NETAppElasticBeanstalk.recipe | 25 +++++++++++++++++-- 3 files changed, 39 insertions(+), 3 deletions(-) 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 3c7a6a28e..114e56267 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs @@ -65,6 +65,11 @@ public partial class Configuration /// The reverse proxy to use. /// public string ReverseProxy { get; set; } = Recipe.REVERSEPROXY_NGINX; + + /// + /// Specifies whether to enable or disable enhanced health reporting. + /// + public string EnhancedHealthReporting { get; set; } = Recipe.ENHANCED_HEALTH_REPORTING; /// A parameterless constructor is needed for /// or the classes will fail to initialize. @@ -87,7 +92,8 @@ public Configuration( string environmentType = Recipe.ENVIRONMENTTYPE_SINGLEINSTANCE, string loadBalancerType = Recipe.LOADBALANCERTYPE_APPLICATION, string reverseProxy = Recipe.REVERSEPROXY_NGINX, - bool xrayTracingSupportEnabled = false) + bool xrayTracingSupportEnabled = false, + string enhancedHealthReporting = Recipe.ENHANCED_HEALTH_REPORTING) { ApplicationIAMRole = applicationIAMRole; InstanceType = instanceType; @@ -100,6 +106,7 @@ public Configuration( LoadBalancerType = loadBalancerType; 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 41894ff69..6ce916961 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs @@ -27,6 +27,8 @@ public class Recipe : Construct public const string LOADBALANCERTYPE_APPLICATION = "application"; public const string REVERSEPROXY_NGINX = "nginx"; + + public const string ENHANCED_HEALTH_REPORTING = "enhanced"; public IRole? AppIAMRole { get; private set; } @@ -152,6 +154,12 @@ private void ConfigureBeanstalkEnvironment(Configuration settings) Namespace = "aws:elasticbeanstalk:xray", OptionName = "XRayEnabled", Value = settings.XRayTracingSupportEnabled.ToString().ToLower() + }, + new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:elasticbeanstalk:healthreporting:system", + OptionName = "SystemType", + Value = settings.EnhancedHealthReporting } }; diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe index d4006f882..9b3181093 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe @@ -74,8 +74,6 @@ } ], - - "OptionSettings": [ { "Id": "BeanstalkApplication", @@ -351,6 +349,29 @@ ], "AdvancedSetting": true, "Updatable": true + }, + { + "Id": "EnhancedHealthReporting", + "Name": "Enhanced Health Reporting", + "Description": "Enhanced health reporting provides free real-time application and operating system monitoring of the instances and other resources in your environment.", + "Type": "String", + "DefaultValue": "enhanced", + "AllowedValues": [ + "enhanced", + "basic" + ], + "ValueMapping": { + "enhanced": "Enhanced", + "basic": "Basic" + }, + "AdvancedSetting": true, + "Updatable": true, + "DependsOn": [ + { + "Id": "ElasticBeanstalkManagedPlatformUpdates.ManagedActionsEnabled", + "Value": false + } + ] } ] } From d8b1cb7db7c1a1191d94d6427143037db25f9633 Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Tue, 23 Nov 2021 10:08:38 -0500 Subject: [PATCH 05/10] feat: add health check url setting to beanstalk deployments --- .../Generated/Configurations/Configuration.cs | 7 +++++++ .../Generated/Recipe.cs | 21 +++++++++++++++++++ .../ASP.NETAppElasticBeanstalk.recipe | 15 +++++++++++++ 3 files changed, 43 insertions(+) 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 114e56267..a15dee853 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs @@ -71,6 +71,11 @@ public partial class Configuration /// public string EnhancedHealthReporting { get; set; } = Recipe.ENHANCED_HEALTH_REPORTING; + /// + /// The health check URL to use. + /// + public string HealthCheckURL { get; set; } + /// A parameterless constructor is needed for /// or the classes will fail to initialize. /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. @@ -89,6 +94,7 @@ public Configuration( string elasticBeanstalkPlatformArn, string ec2KeyPair, ElasticBeanstalkManagedPlatformUpdatesConfiguration elasticBeanstalkManagedPlatformUpdates, + string healthCheckURL, string environmentType = Recipe.ENVIRONMENTTYPE_SINGLEINSTANCE, string loadBalancerType = Recipe.LOADBALANCERTYPE_APPLICATION, string reverseProxy = Recipe.REVERSEPROXY_NGINX, @@ -107,6 +113,7 @@ public Configuration( XRayTracingSupportEnabled = xrayTracingSupportEnabled; ReverseProxy = reverseProxy; EnhancedHealthReporting = enhancedHealthReporting; + HealthCheckURL = healthCheckURL; } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs index 6ce916961..75edd353b 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs @@ -183,6 +183,27 @@ private void ConfigureBeanstalkEnvironment(Configuration settings) Value = settings.LoadBalancerType } ); + + if (!string.IsNullOrEmpty(settings.HealthCheckURL)) + { + optionSettingProperties.Add( + new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:elasticbeanstalk:application", + OptionName = "Application Healthcheck URL", + Value = settings.HealthCheckURL + } + ); + + optionSettingProperties.Add( + new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:elasticbeanstalk:environment:process:default", + OptionName = "HealthCheckPath", + Value = settings.HealthCheckURL + } + ); + } } if (!string.IsNullOrEmpty(settings.EC2KeyPair)) diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe index 9b3181093..ae9ff00ec 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe @@ -372,6 +372,21 @@ "Value": false } ] + }, + { + "Id": "HealthCheckURL", + "Name": "Health Check URL", + "Description": "Customize the load balancer health check to ensure that your application, and not just the web server, is in a good state.", + "Type": "String", + "DefaultValue": "/", + "DependsOn": [ + { + "Id": "EnvironmentType", + "Value": "LoadBalanced" + } + ], + "AdvancedSetting": true, + "Updatable": true } ] } From a2a4b681ed4e491c6814394ece31363dad5e2797 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Sat, 27 Nov 2021 20:49:05 -0800 Subject: [PATCH 06/10] fix: Fixed issue with ConsoleAppECSFargateService project file name not getting updated when creating deployment project --- ...CSFargateService.csproj => ConsoleAppEcsFargateService.csproj} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/{ConsoleAppECSFargateService.csproj => ConsoleAppEcsFargateService.csproj} (100%) diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/ConsoleAppECSFargateService.csproj b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/ConsoleAppEcsFargateService.csproj similarity index 100% rename from src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/ConsoleAppECSFargateService.csproj rename to src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/ConsoleAppEcsFargateService.csproj From 369f78aafc6f7cd71fe122dc554b8c7ec9553dac Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Tue, 23 Nov 2021 14:24:36 -0500 Subject: [PATCH 07/10] feat: add rolling updates configuration to beanstalk deployments --- .../Recipes/OptionSettingTypeHint.cs | 3 +- .../RangeValidator.cs | 4 + .../Generated/Configurations/Configuration.cs | 7 + ...ticBeanstalkRollingUpdatesConfiguration.cs | 21 +++ .../Generated/Recipe.cs | 54 +++++++ .../ASP.NETAppElasticBeanstalk.recipe | 143 ++++++++++++++++++ ...anStalkOptionSettingItemValidationTests.cs | 14 ++ 7 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/ElasticBeanstalkRollingUpdatesConfiguration.cs diff --git a/src/AWS.Deploy.Common/Recipes/OptionSettingTypeHint.cs b/src/AWS.Deploy.Common/Recipes/OptionSettingTypeHint.cs index a8da7a182..95c60759b 100644 --- a/src/AWS.Deploy.Common/Recipes/OptionSettingTypeHint.cs +++ b/src/AWS.Deploy.Common/Recipes/OptionSettingTypeHint.cs @@ -25,6 +25,7 @@ public enum OptionSettingTypeHint DynamoDBTableName, SQSQueueUrl, SNSTopicArn, - S3BucketName + S3BucketName, + BeanstalkRollingUpdates }; } diff --git a/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/RangeValidator.cs b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/RangeValidator.cs index 2fdbaf49a..75f61ba4c 100644 --- a/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/RangeValidator.cs +++ b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/RangeValidator.cs @@ -20,9 +20,13 @@ public class RangeValidator : IOptionSettingItemValidator /// Supports replacement tokens {{Min}} and {{Max}} /// public string ValidationFailedMessage { get; set; } = defaultValidationFailedMessage; + public bool AllowEmptyString { get; set; } public ValidationResult Validate(object input) { + if (AllowEmptyString && string.IsNullOrEmpty(input?.ToString())) + return ValidationResult.Valid(); + if (int.TryParse(input?.ToString(), out var result) && result >= Min && result <= Max) 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 a15dee853..e93dcd279 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs @@ -75,6 +75,11 @@ public partial class Configuration /// The health check URL to use. /// public string HealthCheckURL { get; set; } + + /// + /// Specifies whether to enable or disable Rolling Updates. + /// + public ElasticBeanstalkRollingUpdatesConfiguration ElasticBeanstalkRollingUpdates { get; set; } /// A parameterless constructor is needed for /// or the classes will fail to initialize. @@ -95,6 +100,7 @@ public Configuration( string ec2KeyPair, ElasticBeanstalkManagedPlatformUpdatesConfiguration elasticBeanstalkManagedPlatformUpdates, string healthCheckURL, + ElasticBeanstalkRollingUpdatesConfiguration elasticBeanstalkRollingUpdates, string environmentType = Recipe.ENVIRONMENTTYPE_SINGLEINSTANCE, string loadBalancerType = Recipe.LOADBALANCERTYPE_APPLICATION, string reverseProxy = Recipe.REVERSEPROXY_NGINX, @@ -108,6 +114,7 @@ public Configuration( ElasticBeanstalkPlatformArn = elasticBeanstalkPlatformArn; EC2KeyPair = ec2KeyPair; ElasticBeanstalkManagedPlatformUpdates = elasticBeanstalkManagedPlatformUpdates; + ElasticBeanstalkRollingUpdates = elasticBeanstalkRollingUpdates; EnvironmentType = environmentType; LoadBalancerType = loadBalancerType; XRayTracingSupportEnabled = xrayTracingSupportEnabled; diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/ElasticBeanstalkRollingUpdatesConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/ElasticBeanstalkRollingUpdatesConfiguration.cs new file mode 100644 index 000000000..0d5261545 --- /dev/null +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/ElasticBeanstalkRollingUpdatesConfiguration.cs @@ -0,0 +1,21 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// This is a generated file from the original deployment recipe. It contains properties for +// all of the settings defined in the recipe file. It is recommended to not modify this file in order +// to allow easy updates to the file when the original recipe that this project was created from has updates. +// This class is marked as a partial class. If you add new settings to the recipe file, those settings should be +// added to partial versions of this class outside of the Generated folder for example in the Configuration folder. + +namespace AspNetAppElasticBeanstalkLinux.Configurations +{ + public partial class ElasticBeanstalkRollingUpdatesConfiguration + { + public bool RollingUpdatesEnabled { get; set; } = false; + public string RollingUpdateType { get; set; } = "Time"; + public int? MaxBatchSize { get; set; } + public int? MinInstancesInService { get; set; } + public string? PauseTime { get; set; } + public string Timeout { get; set; } = "PT30M"; + } +} diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs index 75edd353b..5651d5ae9 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs @@ -262,6 +262,60 @@ private void ConfigureBeanstalkEnvironment(Configuration settings) } ); } + + if (settings.ElasticBeanstalkRollingUpdates.RollingUpdatesEnabled) + { + optionSettingProperties.Add(new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:autoscaling:updatepolicy:rollingupdate", + OptionName = "RollingUpdateEnabled", + Value = settings.ElasticBeanstalkRollingUpdates.RollingUpdatesEnabled.ToString().ToLower() + }); + + optionSettingProperties.Add(new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:autoscaling:updatepolicy:rollingupdate", + OptionName = "RollingUpdateType", + Value = settings.ElasticBeanstalkRollingUpdates.RollingUpdateType + }); + + optionSettingProperties.Add(new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:autoscaling:updatepolicy:rollingupdate", + OptionName = "Timeout", + Value = settings.ElasticBeanstalkRollingUpdates.Timeout + }); + + if (settings.ElasticBeanstalkRollingUpdates.MaxBatchSize != null) + { + optionSettingProperties.Add(new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:autoscaling:updatepolicy:rollingupdate", + OptionName = "MaxBatchSize", + Value = settings.ElasticBeanstalkRollingUpdates.MaxBatchSize.ToString() + }); + } + + if (settings.ElasticBeanstalkRollingUpdates.MinInstancesInService != null) + { + optionSettingProperties.Add(new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:autoscaling:updatepolicy:rollingupdate", + OptionName = "MinInstancesInService", + Value = settings.ElasticBeanstalkRollingUpdates.MinInstancesInService.ToString() + }); + } + + if (settings.ElasticBeanstalkRollingUpdates.PauseTime != null) + { + optionSettingProperties.Add(new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:autoscaling:updatepolicy:rollingupdate", + OptionName = "PauseTime", + Value = settings.ElasticBeanstalkRollingUpdates.PauseTime + }); + } + } BeanstalkEnvironment = new CfnEnvironment(this, nameof(BeanstalkEnvironment), InvokeCustomizeCDKPropsEvent(nameof(BeanstalkEnvironment), this, new CfnEnvironmentProps { diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe index ae9ff00ec..1511dea37 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe @@ -387,6 +387,149 @@ ], "AdvancedSetting": true, "Updatable": true + }, + { + "Id": "ElasticBeanstalkRollingUpdates", + "Name": "Rolling Updates", + "Description": "When a configuration change requires replacing instances, Elastic Beanstalk can perform the update in batches to avoid downtime while the change is propagated. During a rolling update, capacity is only reduced by the size of a single batch, which you can configure. Elastic Beanstalk takes one batch of instances out of service, terminates them, and then launches a batch with the new configuration. After the new batch starts serving requests, Elastic Beanstalk moves on to the next batch.", + "Type": "Object", + "TypeHint": "BeanstalkRollingUpdates", + "AdvancedSetting": true, + "Updatable": true, + "DependsOn": [ + { + "Id": "EnvironmentType", + "Value": "LoadBalanced" + } + ], + "ChildOptionSettings": [ + { + "Id": "RollingUpdatesEnabled", + "Name": "Enable Rolling Updates", + "Description": "Do you want to enable Rolling Updates?", + "Type": "Bool", + "DefaultValue": false, + "AdvancedSetting": true, + "Updatable": true + }, + { + "Id": "RollingUpdateType", + "Name": "Rolling Update Type", + "Description": "This includes three types: time-based rolling updates, health-based rolling updates, and immutable updates. Time-based rolling updates apply a PauseTime between batches. Health-based rolling updates wait for new instances to pass health checks before moving on to the next batch. Immutable updates launch a full set of instances in a new Auto Scaling group.", + "Type": "String", + "DefaultValue": "Time", + "AllowedValues": [ + "Time", + "Health", + "Immutable" + ], + "AdvancedSetting": true, + "Updatable": true, + "DependsOn": [ + { + "Id": "ElasticBeanstalkRollingUpdates.RollingUpdatesEnabled", + "Value": true + } + ] + }, + { + "Id": "MaxBatchSize", + "Name": "Max Batch Size", + "Description": "The number of instances included in each batch of the rolling update.", + "Type": "Int", + "AdvancedSetting": true, + "Updatable": true, + "DependsOn": [ + { + "Id": "ElasticBeanstalkRollingUpdates.RollingUpdatesEnabled", + "Value": true + } + ], + "Validators": [ + { + "ValidatorType": "Range", + "Configuration": { + "Min": 1, + "Max": 10000, + "AllowEmptyString": true + } + } + ] + }, + { + "Id": "MinInstancesInService", + "Name": "Min Instances In Service", + "Description": "The minimum number of instances that must be in service within the Auto Scaling group while other instances are terminated.", + "Type": "Int", + "AdvancedSetting": true, + "Updatable": true, + "DependsOn": [ + { + "Id": "ElasticBeanstalkRollingUpdates.RollingUpdatesEnabled", + "Value": true + } + ], + "Validators": [ + { + "ValidatorType": "Range", + "Configuration": { + "Min": 0, + "Max": 9999, + "AllowEmptyString": true + } + } + ] + }, + { + "Id": "PauseTime", + "Name": "Pause Time", + "Description": "The amount of time (in seconds, minutes, or hours) the Elastic Beanstalk service waits after it completed updates to one batch of instances and before it continues on to the next batch. (ISO8601 duration format: PT#H#M#S where each # is the number of hours, minutes, and/or seconds, respectively.)", + "Type": "String", + "AdvancedSetting": true, + "Updatable": true, + "DependsOn": [ + { + "Id": "ElasticBeanstalkRollingUpdates.RollingUpdatesEnabled", + "Value": true + } + ], + "Validators": [ + { + "ValidatorType": "Regex", + "Configuration": { + "Regex": "^P([0-9]+(?:[,\\.][0-9]+)?Y)?([0-9]+(?:[,\\.][0-9]+)?M)?([0-9]+(?:[,\\.][0-9]+)?D)?(?:T([0-9]+(?:[,\\.][0-9]+)?H)?([0-9]+(?:[,\\.][0-9]+)?M)?([0-9]+(?:[,\\.][0-9]+)?S)?)?$", + "AllowEmptyString": true, + "ValidationFailedMessage": "Invalid PauseTime. The PauseTime follows the ISO8601 duration format: PT#H#M#S where each # is the number of hours, minutes, and/or seconds, respectively." + } + } + ] + }, + { + "Id": "Timeout", + "Name": "Timeout", + "Description": "The maximum amount of time (in minutes or hours) to wait for all instances in a batch of instances to pass health checks before canceling the update. (ISO8601 duration format: PT#H#M#S where each # is the number of hours, minutes, and/or seconds, respectively.)", + "Type": "String", + "DefaultValue": "PT30M", + "AdvancedSetting": true, + "Updatable": true, + "DependsOn": [ + { + "Id": "ElasticBeanstalkRollingUpdates.RollingUpdatesEnabled", + "Value": true + } + ], + "Validators": [ + { + "ValidatorType": "Regex", + "Configuration": { + "Regex": "^P([0-9]+(?:[,\\.][0-9]+)?Y)?([0-9]+(?:[,\\.][0-9]+)?M)?([0-9]+(?:[,\\.][0-9]+)?D)?(?:T([0-9]+(?:[,\\.][0-9]+)?H)?([0-9]+(?:[,\\.][0-9]+)?M)?([0-9]+(?:[,\\.][0-9]+)?S)?)?$", + "AllowEmptyString": true, + "ValidationFailedMessage": "Invalid Timeout. The Timeout follows the ISO8601 duration format: PT#H#M#S where each # is the number of hours, minutes, and/or seconds, respectively." + } + } + ] + } + ] } ] } diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ElasticBeanStalkOptionSettingItemValidationTests.cs b/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ElasticBeanStalkOptionSettingItemValidationTests.cs index 0c9b5c1cc..dd25e3462 100644 --- a/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ElasticBeanStalkOptionSettingItemValidationTests.cs +++ b/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ElasticBeanStalkOptionSettingItemValidationTests.cs @@ -78,6 +78,20 @@ public void ElasticBeanstalkPlatformArnValidationTest(string value, bool isValid Validate(optionSettingItem, value, isValid); } + [Theory] + [InlineData("PT10M", true)] + [InlineData("PT1H", true)] + [InlineData("PT25S", true)] + [InlineData("PT1H20M30S", true)] + [InlineData("invalid", false)] + [InlineData("PTB1H20M30S", false)] + public void ElasticBeanstalkRollingUpdatesPauseTime(string value, bool isValid) + { + var optionSettingItem = new OptionSettingItem("id", "name", "description"); + optionSettingItem.Validators.Add(GetRegexValidatorConfig("^P([0-9]+(?:[,\\.][0-9]+)?Y)?([0-9]+(?:[,\\.][0-9]+)?M)?([0-9]+(?:[,\\.][0-9]+)?D)?(?:T([0-9]+(?:[,\\.][0-9]+)?H)?([0-9]+(?:[,\\.][0-9]+)?M)?([0-9]+(?:[,\\.][0-9]+)?S)?)?$")); + Validate(optionSettingItem, value, isValid); + } + private OptionSettingItemValidatorConfig GetRegexValidatorConfig(string regex) { var regexValidatorConfig = new OptionSettingItemValidatorConfig From 0b488e38035e27bdddcdb20063f88d7d4a49545f Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Mon, 29 Nov 2021 16:42:23 -0500 Subject: [PATCH 08/10] feat: Add CName Prefix setting to beanstalk deployments --- .../Generated/Configurations/Configuration.cs | 7 +++++++ .../AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs | 1 + .../RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe | 9 +++++++++ 3 files changed, 17 insertions(+) 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 e93dcd279..047172b9b 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs @@ -81,6 +81,11 @@ public partial class Configuration /// public ElasticBeanstalkRollingUpdatesConfiguration ElasticBeanstalkRollingUpdates { get; set; } + /// + /// The CName Prefix used for the Beanstalk Environment. + /// + public string CNamePrefix { get; set; } + /// A parameterless constructor is needed for /// or the classes will fail to initialize. /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. @@ -101,6 +106,7 @@ public Configuration( ElasticBeanstalkManagedPlatformUpdatesConfiguration elasticBeanstalkManagedPlatformUpdates, string healthCheckURL, ElasticBeanstalkRollingUpdatesConfiguration elasticBeanstalkRollingUpdates, + string cnamePrefix, string environmentType = Recipe.ENVIRONMENTTYPE_SINGLEINSTANCE, string loadBalancerType = Recipe.LOADBALANCERTYPE_APPLICATION, string reverseProxy = Recipe.REVERSEPROXY_NGINX, @@ -121,6 +127,7 @@ public Configuration( ReverseProxy = reverseProxy; EnhancedHealthReporting = enhancedHealthReporting; HealthCheckURL = healthCheckURL; + CNamePrefix = cnamePrefix; } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs index 5651d5ae9..70c12a667 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs @@ -323,6 +323,7 @@ private void ConfigureBeanstalkEnvironment(Configuration settings) ApplicationName = settings.BeanstalkApplication.ApplicationName, PlatformArn = settings.ElasticBeanstalkPlatformArn, OptionSettings = optionSettingProperties.ToArray(), + CnamePrefix = !string.IsNullOrEmpty(settings.CNamePrefix) ? settings.CNamePrefix : null, // This line is critical - reference the label created in this same stack VersionLabel = ApplicationVersion.Ref, })); diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe index 1511dea37..795de9dec 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe @@ -530,6 +530,15 @@ ] } ] + }, + { + "Id": "CNamePrefix", + "Name": "CName Prefix", + "Description": "If specified, the environment attempts to use this value as the prefix for the CNAME in your Elastic Beanstalk environment URL. If not specified, the CNAME is generated automatically by appending a random alphanumeric string to the environment name.", + "Type": "String", + "DefaultValue": "", + "AdvancedSetting": true, + "Updatable": false } ] } From 1a1fd92a0fd7282bbbc9caf91190c56b0c2ccd1d Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Wed, 24 Nov 2021 14:30:56 -0500 Subject: [PATCH 09/10] feat: add environment variables config to beanstalk deployments --- src/AWS.Deploy.CLI/Commands/DeployCommand.cs | 25 +++++++- src/AWS.Deploy.CLI/ConsoleUtilities.cs | 63 +++++++++++++++++++ .../OptionSettingItem.ValueOverride.cs | 2 +- .../Recipes/OptionSettingValueType.cs | 1 + src/AWS.Deploy.Common/Recommendation.cs | 15 ++++- .../Generated/Configurations/Configuration.cs | 9 +++ .../Generated/Recipe.cs | 13 ++++ .../ASP.NETAppElasticBeanstalk.recipe | 8 +++ .../ElasticBeanStalkKeyValueDeploymentTest.cs | 46 ++++++++++++++ .../ElasticBeanStalkKeyPairConfigFile.json | 33 ++++++++++ 10 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/ElasticBeanStalkKeyValueDeploymentTest.cs create mode 100644 test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/UnitTestFiles/ElasticBeanStalkKeyPairConfigFile.json diff --git a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs index 5f8be7ab2..2fca339ed 100644 --- a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs @@ -369,6 +369,9 @@ private void ConfigureDeploymentFromConfigFile(Recommendation recommendation, Us var optionSetting = recommendation.GetOptionSetting(optionSettingJsonPath); + if (optionSetting == null) + throw new OptionSettingItemDoesNotExistException(DeployToolErrorCode.OptionSettingItemDoesNotExistInRecipe, $"The Option Setting Item {optionSettingJsonPath} does not exist."); + if (!recommendation.IsExistingCloudApplication || optionSetting.Updatable) { object settingValue; @@ -388,6 +391,13 @@ private void ConfigureDeploymentFromConfigFile(Recommendation recommendation, Us case OptionSettingValueType.Double: settingValue = double.Parse(optionSettingValue); break; + case OptionSettingValueType.KeyValue: + var optionSettingKey = optionSettingJsonPath.Split(".").Last(); + var existingValue = recommendation.GetOptionSettingValue>(optionSetting); + existingValue ??= new Dictionary(); + existingValue[optionSettingKey] = optionSettingValue; + settingValue = existingValue; + break; default: throw new InvalidOverrideValueException(DeployToolErrorCode.InvalidValueForOptionSettingItem, $"Invalid value {optionSettingValue} for option setting item {optionSettingJsonPath}"); } @@ -772,6 +782,9 @@ private async Task ConfigureDeploymentFromCli(Recommendation recommendation, Opt var answer = _consoleUtilities.AskYesNoQuestion(string.Empty, recommendation.GetOptionSettingValue(setting).ToString()); settingValue = answer == YesNo.Yes ? "true" : "false"; break; + case OptionSettingValueType.KeyValue: + settingValue = _consoleUtilities.AskUserForKeyValue(!string.IsNullOrEmpty(currentValue.ToString()) ? (Dictionary) currentValue : new Dictionary()); + break; case OptionSettingValueType.Object: foreach (var childSetting in setting.ChildOptionSettings) { @@ -809,6 +822,7 @@ private async Task ConfigureDeploymentFromCli(Recommendation recommendation, Opt private void DisplayValue(Recommendation recommendation, OptionSettingItem optionSetting, int optionSettingNumber, int optionSettingsCount, Type? typeHintResponseType, DisplayOptionSettingsMode mode) { object? displayValue = null; + Dictionary? keyValuePair = null; Dictionary? objectValues = null; if (typeHintResponseType != null) { @@ -823,7 +837,8 @@ private void DisplayValue(Recommendation recommendation, OptionSettingItem optio { var value = recommendation.GetOptionSettingValue(optionSetting); objectValues = value as Dictionary; - displayValue = objectValues == null ? value : string.Empty; + keyValuePair = value as Dictionary; + displayValue = objectValues == null && keyValuePair == null ? value : string.Empty; } if (mode == DisplayOptionSettingsMode.Editable) @@ -835,6 +850,14 @@ private void DisplayValue(Recommendation recommendation, OptionSettingItem optio _toolInteractiveService.WriteLine($"{optionSetting.Name}: {displayValue}"); } + if (keyValuePair != null) + { + foreach (var (key, value) in keyValuePair) + { + _toolInteractiveService.WriteLine($"\t{key}: {value}"); + } + } + if (objectValues != null) { var displayableValues = new Dictionary(); diff --git a/src/AWS.Deploy.CLI/ConsoleUtilities.cs b/src/AWS.Deploy.CLI/ConsoleUtilities.cs index bbbe1d61f..13b52f98a 100644 --- a/src/AWS.Deploy.CLI/ConsoleUtilities.cs +++ b/src/AWS.Deploy.CLI/ConsoleUtilities.cs @@ -31,6 +31,7 @@ T AskUserToChoose(IList options, string title, T defaultValue, string? def YesNo AskYesNoQuestion(string question, string? defaultValue); YesNo AskYesNoQuestion(string question, YesNo? defaultValue = default); void DisplayValues(Dictionary objectValues, string indent); + Dictionary AskUserForKeyValue(Dictionary keyValue); } public class ConsoleUtilities : IConsoleUtilities @@ -306,6 +307,68 @@ public string AskUserForValue(string message, string defaultValue, bool allowEmp return userValue; } + public Dictionary AskUserForKeyValue(Dictionary keyValue) + { + keyValue ??= new Dictionary(); + + if (keyValue.Keys.Count == 0) + { + AskToAddKeyValuePair(keyValue); + return keyValue; + } + + const string ADD = "Add new"; + const string UPDATE = "Update existing"; + const string DELETE = "Delete existing"; + var operations = new List { ADD, UPDATE, DELETE }; + + var selectedOperation = AskUserToChoose(operations, "Select which operation you want to perform:", ADD); + + if(selectedOperation.Equals(ADD)) + AskToAddKeyValuePair(keyValue); + + if(selectedOperation.Equals(UPDATE)) + AskToUpdateKeyValuePair(keyValue); + + if(selectedOperation.Equals(DELETE)) + AskToDeleteKeyValuePair(keyValue); + + return keyValue; + } + + private void AskToAddKeyValuePair(Dictionary keyValue) + { + const string RESET = ""; + var variableName = string.Empty; + while (string.IsNullOrEmpty(variableName)) + { + _interactiveService.WriteLine("Enter the name:"); + variableName = _interactiveService.ReadLine()?.Trim() ?? ""; + } + + _interactiveService.WriteLine($"Enter the value (type {RESET} to reset):"); + var variableValue = _interactiveService.ReadLine()?.Trim() ?? ""; + if (string.Equals(RESET, variableValue.Trim(), StringComparison.OrdinalIgnoreCase) || + string.Equals($"'{RESET}'", variableValue.Trim(), StringComparison.OrdinalIgnoreCase)) + variableValue = keyValue.ContainsKey(variableName) ? keyValue[variableName] : ""; + + keyValue[variableName] = variableValue; + } + + private void AskToUpdateKeyValuePair(Dictionary keyValue) + { + var selectedKey = AskUserToChoose(keyValue.Keys.ToList(), "Select the one you wish to update:", null); + var selectedValue = AskUserForValue("Enter the value:", keyValue[selectedKey], true); + + keyValue[selectedKey] = selectedValue; + } + + private void AskToDeleteKeyValuePair(Dictionary keyValue) + { + var selectedKey = AskUserToChoose(keyValue.Keys.ToList(), "Select the one you wish to delete:", null); + keyValue.Remove(selectedKey); + } + public string AskForEC2KeyPairSaveDirectory(string projectPath) { _interactiveService.WriteLine("Enter a directory to save the newly created Key Pair: (avoid from using your project directory)"); diff --git a/src/AWS.Deploy.Common/Recipes/OptionSettingItem.ValueOverride.cs b/src/AWS.Deploy.Common/Recipes/OptionSettingItem.ValueOverride.cs index 1196cb3ce..87a0cbf76 100644 --- a/src/AWS.Deploy.Common/Recipes/OptionSettingItem.ValueOverride.cs +++ b/src/AWS.Deploy.Common/Recipes/OptionSettingItem.ValueOverride.cs @@ -114,7 +114,7 @@ public void SetValueOverride(object valueOverride) !AllowedValues.Contains(valueOverride.ToString() ?? "")) throw new InvalidOverrideValueException(DeployToolErrorCode.InvalidValueForOptionSettingItem, $"Invalid value for option setting item '{Name}'"); - if (valueOverride is bool || valueOverride is int || valueOverride is long || valueOverride is double) + if (valueOverride is bool || valueOverride is int || valueOverride is long || valueOverride is double || valueOverride is Dictionary) { _valueOverride = valueOverride; } diff --git a/src/AWS.Deploy.Common/Recipes/OptionSettingValueType.cs b/src/AWS.Deploy.Common/Recipes/OptionSettingValueType.cs index 2ace088be..02687b9c1 100644 --- a/src/AWS.Deploy.Common/Recipes/OptionSettingValueType.cs +++ b/src/AWS.Deploy.Common/Recipes/OptionSettingValueType.cs @@ -9,6 +9,7 @@ public enum OptionSettingValueType Int, Double, Bool, + KeyValue, Object }; } diff --git a/src/AWS.Deploy.Common/Recommendation.cs b/src/AWS.Deploy.Common/Recommendation.cs index c30c2f5a2..8180d1170 100644 --- a/src/AWS.Deploy.Common/Recommendation.cs +++ b/src/AWS.Deploy.Common/Recommendation.cs @@ -89,6 +89,9 @@ public IEnumerable GetConfigurableOptionSettingItems() /// /// Interactively traverses given json path and returns target option setting. /// Throws exception if there is no that matches /> + /// In case an option setting of type is encountered, + /// that can have the key value pair name as the leaf node with the option setting Id as the node before that. + /// In case there are multiple nodes after a , then that indicates an invalid /// /// /// Dot (.) separated key values string pointing to an option setting. @@ -104,15 +107,23 @@ public OptionSettingItem GetOptionSetting(string? jsonPath) var ids = jsonPath.Split('.'); OptionSettingItem? optionSetting = null; - foreach (var id in ids) + for (int i = 0; i < ids.Length; i++) { var optionSettings = optionSetting?.ChildOptionSettings ?? GetConfigurableOptionSettingItems(); - optionSetting = optionSettings.FirstOrDefault(os => os.Id.Equals(id)); + optionSetting = optionSettings.FirstOrDefault(os => os.Id.Equals(ids[i])); if (optionSetting == null) { throw new OptionSettingItemDoesNotExistException(DeployToolErrorCode.OptionSettingItemDoesNotExistInRecipe, $"The Option Setting Item {jsonPath} does not exist as part of the" + $" {Recipe.Name} recipe"); } + if (optionSetting.Type.Equals(OptionSettingValueType.KeyValue)) + { + if (i + 2 == ids.Length) + return optionSetting; + else + throw new OptionSettingItemDoesNotExistException(DeployToolErrorCode.OptionSettingItemDoesNotExistInRecipe, $"The Option Setting Item {jsonPath} does not exist as part of the" + + $" {Recipe.Name} recipe"); + } } return optionSetting!; 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 047172b9b..b7a1acd3a 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Configurations/Configuration.cs @@ -7,6 +7,8 @@ // This class is marked as a partial class. If you add new settings to the recipe file, those settings should be // added to partial versions of this class outside of the Generated folder for example in the Configuration folder. +using System.Collections.Generic; + namespace AspNetAppElasticBeanstalkLinux.Configurations { public partial class Configuration @@ -86,6 +88,11 @@ public partial class Configuration /// public string CNamePrefix { get; set; } + /// + /// The environment variables that are set for the beanstalk environment. + /// + public Dictionary ElasticBeanstalkEnvironmentVariables { get; set; } = new Dictionary { }; + /// A parameterless constructor is needed for /// or the classes will fail to initialize. /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. @@ -107,6 +114,7 @@ public Configuration( string healthCheckURL, ElasticBeanstalkRollingUpdatesConfiguration elasticBeanstalkRollingUpdates, string cnamePrefix, + Dictionary elasticBeanstalkEnvironmentVariables, string environmentType = Recipe.ENVIRONMENTTYPE_SINGLEINSTANCE, string loadBalancerType = Recipe.LOADBALANCERTYPE_APPLICATION, string reverseProxy = Recipe.REVERSEPROXY_NGINX, @@ -121,6 +129,7 @@ public Configuration( EC2KeyPair = ec2KeyPair; ElasticBeanstalkManagedPlatformUpdates = elasticBeanstalkManagedPlatformUpdates; ElasticBeanstalkRollingUpdates = elasticBeanstalkRollingUpdates; + ElasticBeanstalkEnvironmentVariables = elasticBeanstalkEnvironmentVariables; EnvironmentType = environmentType; LoadBalancerType = loadBalancerType; XRayTracingSupportEnabled = xrayTracingSupportEnabled; diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs index 70c12a667..1b5453ca3 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Generated/Recipe.cs @@ -317,6 +317,19 @@ private void ConfigureBeanstalkEnvironment(Configuration settings) } } + if (settings.ElasticBeanstalkEnvironmentVariables != null) + { + foreach (var (key, value) in settings.ElasticBeanstalkEnvironmentVariables) + { + optionSettingProperties.Add(new CfnEnvironment.OptionSettingProperty + { + Namespace = "aws:elasticbeanstalk:application:environment", + OptionName = key, + Value = value + }); + } + } + BeanstalkEnvironment = new CfnEnvironment(this, nameof(BeanstalkEnvironment), InvokeCustomizeCDKPropsEvent(nameof(BeanstalkEnvironment), this, new CfnEnvironmentProps { EnvironmentName = settings.EnvironmentName, diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe index 795de9dec..1431791a5 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe @@ -539,6 +539,14 @@ "DefaultValue": "", "AdvancedSetting": true, "Updatable": false + }, + { + "Id": "ElasticBeanstalkEnvironmentVariables", + "Name": "Environment Variables", + "Description": "Configure environment properties for your application.", + "Type": "KeyValue", + "AdvancedSetting": false, + "Updatable": true } ] } diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/ElasticBeanStalkKeyValueDeploymentTest.cs b/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/ElasticBeanStalkKeyValueDeploymentTest.cs new file mode 100644 index 000000000..a7556da94 --- /dev/null +++ b/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/ElasticBeanStalkKeyValueDeploymentTest.cs @@ -0,0 +1,46 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using AWS.Deploy.Common; +using Xunit; + +namespace AWS.Deploy.CLI.Common.UnitTests.ConfigFileDeployment +{ + public class ElasticBeanStalkKeyValueDeploymentTest + { + private readonly UserDeploymentSettings _userDeploymentSettings; + public ElasticBeanStalkKeyValueDeploymentTest() + { + var filePath = Path.Combine("ConfigFileDeployment", "TestFiles", "UnitTestFiles", "ElasticBeanStalkKeyPairConfigFile.json"); + var userDeploymentSettings = UserDeploymentSettings.ReadSettings(filePath); + _userDeploymentSettings = userDeploymentSettings; + } + + [Fact] + public void VerifyJsonParsing() + { + Assert.Equal("default", _userDeploymentSettings.AWSProfile); + Assert.Equal("us-west-2", _userDeploymentSettings.AWSRegion); + Assert.Equal("MyAppStack", _userDeploymentSettings.StackName); + Assert.Equal("AspNetAppElasticBeanstalkLinux", _userDeploymentSettings.RecipeId); + + var optionSettingDictionary = _userDeploymentSettings.LeafOptionSettingItems; + Assert.Equal("True", optionSettingDictionary["BeanstalkApplication.CreateNew"]); + Assert.Equal("MyApplication", optionSettingDictionary["BeanstalkApplication.ApplicationName"]); + Assert.Equal("MyEnvironment", optionSettingDictionary["EnvironmentName"]); + Assert.Equal("MyInstance", optionSettingDictionary["InstanceType"]); + Assert.Equal("SingleInstance", optionSettingDictionary["EnvironmentType"]); + Assert.Equal("application", optionSettingDictionary["LoadBalancerType"]); + Assert.Equal("True", optionSettingDictionary["ApplicationIAMRole.CreateNew"]); + Assert.Equal("MyPlatformArn", optionSettingDictionary["ElasticBeanstalkPlatformArn"]); + Assert.Equal("True", optionSettingDictionary["ElasticBeanstalkManagedPlatformUpdates.ManagedActionsEnabled"]); + Assert.Equal("Mon:12:00", optionSettingDictionary["ElasticBeanstalkManagedPlatformUpdates.PreferredStartTime"]); + Assert.Equal("minor", optionSettingDictionary["ElasticBeanstalkManagedPlatformUpdates.UpdateLevel"]); + Assert.Equal("VarValue", optionSettingDictionary["ElasticBeanstalkEnvironmentVariables.VarName"]); + } + } +} diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/UnitTestFiles/ElasticBeanStalkKeyPairConfigFile.json b/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/UnitTestFiles/ElasticBeanStalkKeyPairConfigFile.json new file mode 100644 index 000000000..0889be927 --- /dev/null +++ b/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/UnitTestFiles/ElasticBeanStalkKeyPairConfigFile.json @@ -0,0 +1,33 @@ +{ + "AWSProfile": "default", + "AWSRegion": "us-west-2", + "StackName": "MyAppStack", + "RecipeId": "AspNetAppElasticBeanstalkLinux", + "OptionSettingsConfig": + { + "BeanstalkApplication": + { + "CreateNew": true, + "ApplicationName": "MyApplication" + }, + "EnvironmentName": "MyEnvironment", + "InstanceType": "MyInstance", + "EnvironmentType": "SingleInstance", + "LoadBalancerType": "application", + "ApplicationIAMRole": + { + "CreateNew": true + }, + "ElasticBeanstalkPlatformArn": "MyPlatformArn", + "ElasticBeanstalkManagedPlatformUpdates": + { + "ManagedActionsEnabled": true, + "PreferredStartTime": "Mon:12:00", + "UpdateLevel": "minor" + }, + "ElasticBeanstalkEnvironmentVariables": { + "VarName": "VarValue" + } + } + } + \ No newline at end of file From a35ad220c0f610842380488a25ab6c84e7c94da6 Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Wed, 8 Dec 2021 16:49:02 -0500 Subject: [PATCH 10/10] feat: Add type hints on child settings --- .../TypeHints/DynamoDBTableCommand.cs | 5 +- .../Commands/TypeHints/InstanceTypeCommand.cs | 93 +++++++++++++++++++ .../Commands/TypeHints/S3BucketNameCommand.cs | 5 +- .../Commands/TypeHints/SNSTopicArnsCommand.cs | 4 +- .../Commands/TypeHints/SQSQueueUrlCommand.cs | 4 +- .../TypeHints/TypeHintCommandFactory.cs | 5 + .../Recipes/OptionSettingTypeHint.cs | 6 +- .../TypeHintData/DynamoDBTableTypeHintData.cs | 23 +++++ .../TypeHintData/S3BucketNameTypeHintData.cs | 23 +++++ .../TypeHintData/SNSTopicArnsTypeHintData.cs | 23 +++++ .../TypeHintData/SQSQueueUrlTypeHintData.cs | 23 +++++ .../Data/AWSResourceQueryer.cs | 15 +++ .../ASP.NETAppAppRunner.recipe | 8 ++ .../ASP.NETAppECSFargate.recipe | 6 ++ .../ASP.NETAppElasticBeanstalk.recipe | 6 +- .../RecipeDefinitions/BlazorWasm.recipe | 4 + .../ConsoleAppECSFargateScheduleTask.recipe | 6 ++ .../ConsoleAppECSFargateService.recipe | 6 ++ .../Utilities/TestToolAWSResourceQueryer.cs | 1 + .../Utilities/TestToolAWSResourceQueryer.cs | 1 + 20 files changed, 261 insertions(+), 6 deletions(-) create mode 100644 src/AWS.Deploy.CLI/Commands/TypeHints/InstanceTypeCommand.cs create mode 100644 src/AWS.Deploy.Common/TypeHintData/DynamoDBTableTypeHintData.cs create mode 100644 src/AWS.Deploy.Common/TypeHintData/S3BucketNameTypeHintData.cs create mode 100644 src/AWS.Deploy.Common/TypeHintData/SNSTopicArnsTypeHintData.cs create mode 100644 src/AWS.Deploy.Common/TypeHintData/SQSQueueUrlTypeHintData.cs diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/DynamoDBTableCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/DynamoDBTableCommand.cs index 766834f7a..35231e01e 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/DynamoDBTableCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/DynamoDBTableCommand.cs @@ -38,9 +38,12 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt { const string NO_VALUE = "*** Do not select table ***"; var currentValue = recommendation.GetOptionSettingValue(optionSetting); + var typeHintData = optionSetting.GetTypeHintData(); var tables = await GetData(); - tables.Add(NO_VALUE); + if (typeHintData?.AllowNoValue ?? false) + tables.Add(NO_VALUE); + var userResponse = _consoleUtilities.AskUserToChoose( values: tables, title: "Select a DynamoDB table:", diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/InstanceTypeCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/InstanceTypeCommand.cs new file mode 100644 index 000000000..098b2b800 --- /dev/null +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/InstanceTypeCommand.cs @@ -0,0 +1,93 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Amazon.EC2.Model; +using AWS.Deploy.Common; +using AWS.Deploy.Common.Recipes; +using AWS.Deploy.Common.TypeHintData; +using AWS.Deploy.Orchestration.Data; + +namespace AWS.Deploy.CLI.Commands.TypeHints +{ + public class InstanceTypeCommand : ITypeHintCommand + { + private readonly IAWSResourceQueryer _awsResourceQueryer; + private readonly IConsoleUtilities _consoleUtilities; + + public InstanceTypeCommand(IAWSResourceQueryer awsResourceQueryer, IConsoleUtilities consoleUtilities) + { + _awsResourceQueryer = awsResourceQueryer; + _consoleUtilities = consoleUtilities; + } + + private async Task?> GetData(Recommendation recommendation, OptionSettingItem optionSetting) + { + return await _awsResourceQueryer.ListOfAvailableInstanceTypes(); + } + + public async Task?> GetResources(Recommendation recommendation, OptionSettingItem optionSetting) + { + var instanceType = await GetData(recommendation, optionSetting); + return instanceType? + .Select(x => new TypeHintResource(x.InstanceType.Value, x.InstanceType.Value)) + .Distinct() + .OrderBy(x => x) + .ToList(); + } + + public async Task Execute(Recommendation recommendation, OptionSettingItem optionSetting) + { + var instanceTypes = await GetData(recommendation, optionSetting); + var instanceTypeDefaultValue = recommendation.GetOptionSettingDefaultValue(optionSetting); + if (instanceTypes == null) + { + return _consoleUtilities.AskUserForValue("Select EC2 Instance Type:", instanceTypeDefaultValue ?? string.Empty, true); + } + + var freeTierEligibleAnswer = _consoleUtilities.AskYesNoQuestion("Do you want the EC2 instance to be free tier eligible?", "true"); + var freeTierEligible = freeTierEligibleAnswer == YesNo.Yes; + + var architectureAllowedValues = new List { "x86_64", "arm64"}; + + var architecture = _consoleUtilities.AskUserToChoose(architectureAllowedValues, "The architecture of the EC2 instances created for the environment.", "x86_64"); + + var cpuCores = instanceTypes + .Where(x => x.FreeTierEligible.Equals(freeTierEligible)) + .Where(x => x.ProcessorInfo.SupportedArchitectures.Contains(architecture)) + .Select(x => x.VCpuInfo.DefaultCores).Distinct().OrderBy(x => x).ToList(); + + if (cpuCores.Count == 0) + return _consoleUtilities.AskUserForValue("Select EC2 Instance Type:", instanceTypeDefaultValue ?? string.Empty, true); + + var cpuCoreCount = int.Parse(_consoleUtilities.AskUserToChoose(cpuCores.Select(x => x.ToString()).ToList(), "Select EC2 Instance CPU Cores:", "1")); + + var memory = instanceTypes + .Where(x => x.FreeTierEligible.Equals(freeTierEligible)) + .Where(x => x.ProcessorInfo.SupportedArchitectures.Contains(architecture)) + .Where(x => x.VCpuInfo.DefaultCores.Equals(cpuCoreCount)) + .Select(x => x.MemoryInfo.SizeInMiB).Distinct().OrderBy(x => x).ToList(); + + if (memory.Count == 0) + return _consoleUtilities.AskUserForValue("Select EC2 Instance Type:", instanceTypeDefaultValue ?? string.Empty, true); + + var memoryCount = _consoleUtilities.AskUserToChoose(memory.Select(x => x.ToString()).ToList(), "Select EC2 Instance Memory:", "1024"); + + var availableInstanceTypes = instanceTypes + .Where(x => x.FreeTierEligible.Equals(freeTierEligible)) + .Where(x => x.ProcessorInfo.SupportedArchitectures.Contains(architecture)) + .Where(x => x.VCpuInfo.DefaultCores.Equals(cpuCoreCount)) + .Where(x => x.MemoryInfo.SizeInMiB.Equals(long.Parse(memoryCount))) + .Select(x => x.InstanceType.Value).Distinct().OrderBy(x => x).ToList(); + + if (availableInstanceTypes.Count == 0) + return _consoleUtilities.AskUserForValue("Select EC2 Instance Type:", instanceTypeDefaultValue ?? string.Empty, true); + + var userResponse = _consoleUtilities.AskUserToChoose(availableInstanceTypes, "Select EC2 Instance Type:", availableInstanceTypes.First()); + + return userResponse; + } + } +} diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/S3BucketNameCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/S3BucketNameCommand.cs index 9c3763036..a76be855f 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/S3BucketNameCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/S3BucketNameCommand.cs @@ -39,9 +39,12 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt { const string NO_VALUE = "*** Do not select bucket ***"; var currentValue = recommendation.GetOptionSettingValue(optionSetting); + var typeHintData = optionSetting.GetTypeHintData(); var buckets = (await GetData()).Select(bucket => bucket.BucketName).ToList(); - buckets.Add(NO_VALUE); + if (typeHintData?.AllowNoValue ?? false) + buckets.Add(NO_VALUE); + var userResponse = _consoleUtilities.AskUserToChoose( values: buckets, title: "Select a S3 bucket:", diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/SNSTopicArnsCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/SNSTopicArnsCommand.cs index 024bf19af..148a1a496 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/SNSTopicArnsCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/SNSTopicArnsCommand.cs @@ -33,6 +33,7 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt { const string NO_VALUE = "*** Do not select topic ***"; var currentValue = recommendation.GetOptionSettingValue(optionSetting); + var typeHintData = optionSetting.GetTypeHintData(); var currentValueStr = currentValue.ToString() ?? string.Empty; var topicArns = await GetResources(recommendation, optionSetting); @@ -43,7 +44,8 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt currentName = currentValueStr.Substring(currentValueStr.LastIndexOf(':') + 1); } - topicNames.Add(NO_VALUE); + if (typeHintData?.AllowNoValue ?? false) + topicNames.Add(NO_VALUE); var userResponse = _consoleUtilities.AskUserToChoose( values: topicNames, title: "Select a SNS topic:", diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/SQSQueueUrlCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/SQSQueueUrlCommand.cs index 2572bbd15..8fcba0ff0 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/SQSQueueUrlCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/SQSQueueUrlCommand.cs @@ -33,6 +33,7 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt { const string NO_VALUE = "*** Do not select queue ***"; var currentValue = recommendation.GetOptionSettingValue(optionSetting); + var typeHintData = optionSetting.GetTypeHintData(); var currentValueStr = currentValue.ToString() ?? string.Empty; var queueUrls = await GetResources(recommendation, optionSetting); @@ -43,7 +44,8 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt currentName = currentValueStr.Substring(currentValueStr.LastIndexOf('/') + 1); } - queueNames.Add(NO_VALUE); + if (typeHintData?.AllowNoValue ?? false) + queueNames.Add(NO_VALUE); var userResponse = _consoleUtilities.AskUserToChoose( values: queueNames, title: "Select a SQS queue:", diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs index c4443b527..050dad9af 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs @@ -38,22 +38,27 @@ public TypeHintCommandFactory(IToolInteractiveService toolInteractiveService, IA _commands = new Dictionary { { OptionSettingTypeHint.BeanstalkApplication, new BeanstalkApplicationCommand(awsResourceQueryer, consoleUtilities) }, + { OptionSettingTypeHint.ExistingBeanstalkApplication, new BeanstalkApplicationCommand(awsResourceQueryer, consoleUtilities) }, { OptionSettingTypeHint.BeanstalkEnvironment, new BeanstalkEnvironmentCommand(awsResourceQueryer, consoleUtilities) }, { OptionSettingTypeHint.DotnetBeanstalkPlatformArn, new DotnetBeanstalkPlatformArnCommand(awsResourceQueryer, consoleUtilities) }, { OptionSettingTypeHint.EC2KeyPair, new EC2KeyPairCommand(toolInteractiveService, awsResourceQueryer, consoleUtilities) }, { OptionSettingTypeHint.IAMRole, new IAMRoleCommand(awsResourceQueryer, consoleUtilities) }, + { OptionSettingTypeHint.ExistingIAMRole, new IAMRoleCommand(awsResourceQueryer, consoleUtilities) }, { OptionSettingTypeHint.Vpc, new VpcCommand(awsResourceQueryer, consoleUtilities) }, + { OptionSettingTypeHint.ExistingVpc, new VpcCommand(awsResourceQueryer, consoleUtilities) }, { OptionSettingTypeHint.DotnetPublishAdditionalBuildArguments, new DotnetPublishArgsCommand(consoleUtilities) }, { OptionSettingTypeHint.DotnetPublishSelfContainedBuild, new DotnetPublishSelfContainedBuildCommand(consoleUtilities) }, { OptionSettingTypeHint.DotnetPublishBuildConfiguration, new DotnetPublishBuildConfigurationCommand(consoleUtilities) }, { OptionSettingTypeHint.DockerExecutionDirectory, new DockerExecutionDirectoryCommand(consoleUtilities, directoryManager) }, { OptionSettingTypeHint.DockerBuildArgs, new DockerBuildArgsCommand(consoleUtilities) }, { OptionSettingTypeHint.ECSCluster, new ECSClusterCommand(awsResourceQueryer, consoleUtilities) }, + { OptionSettingTypeHint.ExistingECSCluster, new ECSClusterCommand(awsResourceQueryer, consoleUtilities) }, { OptionSettingTypeHint.ExistingApplicationLoadBalancer, new ExistingApplicationLoadBalancerCommand(awsResourceQueryer, consoleUtilities) }, { OptionSettingTypeHint.DynamoDBTableName, new DynamoDBTableCommand(awsResourceQueryer, consoleUtilities) }, { OptionSettingTypeHint.SQSQueueUrl, new SQSQueueUrlCommand(awsResourceQueryer, consoleUtilities) }, { OptionSettingTypeHint.SNSTopicArn, new SNSTopicArnsCommand(awsResourceQueryer, consoleUtilities) }, { OptionSettingTypeHint.S3BucketName, new S3BucketNameCommand(awsResourceQueryer, consoleUtilities) }, + { OptionSettingTypeHint.InstanceType, new InstanceTypeCommand(awsResourceQueryer, consoleUtilities) }, }; } diff --git a/src/AWS.Deploy.Common/Recipes/OptionSettingTypeHint.cs b/src/AWS.Deploy.Common/Recipes/OptionSettingTypeHint.cs index 95c60759b..cc7d20aef 100644 --- a/src/AWS.Deploy.Common/Recipes/OptionSettingTypeHint.cs +++ b/src/AWS.Deploy.Common/Recipes/OptionSettingTypeHint.cs @@ -26,6 +26,10 @@ public enum OptionSettingTypeHint SQSQueueUrl, SNSTopicArn, S3BucketName, - BeanstalkRollingUpdates + BeanstalkRollingUpdates, + ExistingIAMRole, + ExistingECSCluster, + ExistingVpc, + ExistingBeanstalkApplication }; } diff --git a/src/AWS.Deploy.Common/TypeHintData/DynamoDBTableTypeHintData.cs b/src/AWS.Deploy.Common/TypeHintData/DynamoDBTableTypeHintData.cs new file mode 100644 index 000000000..af4cd1908 --- /dev/null +++ b/src/AWS.Deploy.Common/TypeHintData/DynamoDBTableTypeHintData.cs @@ -0,0 +1,23 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r +// SPDX-License-Identifier: Apache-2.0 + +using AWS.Deploy.Common.Recipes; + +namespace AWS.Deploy.Common.TypeHintData +{ + /// + /// Holds additional data for processing. + /// + public class DynamoDBTableTypeHintData + { + /// + /// Determines whether to allow no value or not. + /// + public bool AllowNoValue { get; set; } + + public DynamoDBTableTypeHintData(bool allowNoValue) + { + AllowNoValue = allowNoValue; + } + } +} diff --git a/src/AWS.Deploy.Common/TypeHintData/S3BucketNameTypeHintData.cs b/src/AWS.Deploy.Common/TypeHintData/S3BucketNameTypeHintData.cs new file mode 100644 index 000000000..807b5e0e6 --- /dev/null +++ b/src/AWS.Deploy.Common/TypeHintData/S3BucketNameTypeHintData.cs @@ -0,0 +1,23 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r +// SPDX-License-Identifier: Apache-2.0 + +using AWS.Deploy.Common.Recipes; + +namespace AWS.Deploy.Common.TypeHintData +{ + /// + /// Holds additional data for processing. + /// + public class S3BucketNameTypeHintData + { + /// + /// Determines whether to allow no value or not. + /// + public bool AllowNoValue { get; set; } + + public S3BucketNameTypeHintData(bool allowNoValue) + { + AllowNoValue = allowNoValue; + } + } +} diff --git a/src/AWS.Deploy.Common/TypeHintData/SNSTopicArnsTypeHintData.cs b/src/AWS.Deploy.Common/TypeHintData/SNSTopicArnsTypeHintData.cs new file mode 100644 index 000000000..14eda19d1 --- /dev/null +++ b/src/AWS.Deploy.Common/TypeHintData/SNSTopicArnsTypeHintData.cs @@ -0,0 +1,23 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r +// SPDX-License-Identifier: Apache-2.0 + +using AWS.Deploy.Common.Recipes; + +namespace AWS.Deploy.Common.TypeHintData +{ + /// + /// Holds additional data for processing. + /// + public class SNSTopicArnsTypeHintData + { + /// + /// Determines whether to allow no value or not. + /// + public bool AllowNoValue { get; set; } + + public SNSTopicArnsTypeHintData(bool allowNoValue) + { + AllowNoValue = allowNoValue; + } + } +} diff --git a/src/AWS.Deploy.Common/TypeHintData/SQSQueueUrlTypeHintData.cs b/src/AWS.Deploy.Common/TypeHintData/SQSQueueUrlTypeHintData.cs new file mode 100644 index 000000000..7cc7652e1 --- /dev/null +++ b/src/AWS.Deploy.Common/TypeHintData/SQSQueueUrlTypeHintData.cs @@ -0,0 +1,23 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r +// SPDX-License-Identifier: Apache-2.0 + +using AWS.Deploy.Common.Recipes; + +namespace AWS.Deploy.Common.TypeHintData +{ + /// + /// Holds additional data for processing. + /// + public class SQSQueueUrlTypeHintData + { + /// + /// Determines whether to allow no value or not. + /// + public bool AllowNoValue { get; set; } + + public SQSQueueUrlTypeHintData(bool allowNoValue) + { + AllowNoValue = allowNoValue; + } + } +} diff --git a/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs b/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs index df5f30ab4..cd5017bd7 100644 --- a/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs +++ b/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs @@ -39,6 +39,7 @@ namespace AWS.Deploy.Orchestration.Data { public interface IAWSResourceQueryer { + Task> ListOfAvailableInstanceTypes(); Task DescribeAppRunnerService(string serviceArn); Task> DescribeCloudFormationResources(string stackName); Task DescribeElasticBeanstalkEnvironment(string environmentId); @@ -78,6 +79,20 @@ public AWSResourceQueryer(IAWSClientFactory awsClientFactory) _awsClientFactory = awsClientFactory; } + public async Task> ListOfAvailableInstanceTypes() + { + var ec2Client = _awsClientFactory.GetAWSClient(); + var instanceTypes = new List(); + var listInstanceTypesPaginator = ec2Client.Paginators.DescribeInstanceTypes(new DescribeInstanceTypesRequest()); + + await foreach (var response in listInstanceTypesPaginator.Responses) + { + instanceTypes.AddRange(response.InstanceTypes); + } + + return instanceTypes; + } + public async Task DescribeAppRunnerService(string serviceArn) { var appRunnerClient = _awsClientFactory.GetAWSClient(); diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe index ea6c09692..e76f38c36 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe @@ -108,6 +108,10 @@ "Name": "Existing Role ARN", "Description": "The ARN of the existing role to use.", "Type": "String", + "TypeHint": "ExistingIAMRole", + "TypeHintData": { + "ServicePrincipal": "tasks.apprunner.amazonaws.com" + }, "AdvancedSetting": false, "Updatable": true, "DependsOn": [ @@ -145,6 +149,10 @@ "Name": "Existing Role ARN", "Description": "The ARN of the existing role to use.", "Type": "String", + "TypeHint": "ExistingIAMRole", + "TypeHintData": { + "ServicePrincipal": "build.apprunner.amazonaws.com" + }, "AdvancedSetting": false, "Updatable": true, "DependsOn": [ diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe index e26be9b28..b835bab1e 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe @@ -92,6 +92,7 @@ "Name": "Existing Cluster ARN", "Description": "The ARN of the existing cluster to use.", "Type": "String", + "TypeHint": "ExistingECSCluster", "AdvancedSetting": false, "Updatable": false, "Validators": [ @@ -202,6 +203,10 @@ "Name": "Existing Role ARN", "Description": "The ARN of the existing role to use.", "Type": "String", + "TypeHint": "ExistingIAMRole", + "TypeHintData": { + "ServicePrincipal": "ecs-tasks.amazonaws.com" + }, "AdvancedSetting": false, "Updatable": true, "Validators": [ @@ -261,6 +266,7 @@ "Name": "Existing VPC ID", "Description": "The ID of the existing VPC to use.", "Type": "String", + "TypeHint": "ExistingVpc", "DefaultValue": null, "AdvancedSetting": false, "Updatable": false, diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe index 1431791a5..b926cab41 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe @@ -98,6 +98,7 @@ "Name": "Application Name", "Description": "The Elastic Beanstalk application name.", "Type": "String", + "TypeHint": "ExistingBeanstalkApplication", "DefaultValue": "{StackName}", "AdvancedSetting": false, "Updatable": false, @@ -139,7 +140,6 @@ "Description": "The EC2 instance type of the EC2 instances created for the environment.", "Type": "String", "TypeHint": "InstanceType", - "DefaultValue": "", "AdvancedSetting": true, "Updatable": true }, @@ -211,6 +211,10 @@ "Name": "Existing Role ARN", "Description": "The ARN of the existing role to use.", "Type": "String", + "TypeHint": "ExistingIAMRole", + "TypeHintData": { + "ServicePrincipal": "elasticbeanstalk.amazonaws.com" + }, "AdvancedSetting": false, "Updatable": false, "DependsOn": [ diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/BlazorWasm.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/BlazorWasm.recipe index 9425baca2..2f38583ca 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/BlazorWasm.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/BlazorWasm.recipe @@ -222,6 +222,10 @@ "Name": "Logging Bucket", "Description": "S3 bucket to use for storing access logs", "Type": "String", + "TypeHint": "S3BucketName", + "TypeHintData": { + "AllowNoValue": true + }, "DefaultValue": true, "AdvancedSetting": true, "Updatable": true, diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe index 4975447cf..a1b027d12 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe @@ -119,6 +119,7 @@ "Name": "Existing Cluster ARN", "Description": "The ARN of the existing cluster to use.", "Type": "String", + "TypeHint": "ExistingECSCluster", "AdvancedSetting": false, "Updatable": false, "Validators": [ @@ -191,6 +192,10 @@ "Name": "Existing Role ARN", "Description": "The ARN of the existing role to use.", "Type": "String", + "TypeHint": "ExistingIAMRole", + "TypeHintData": { + "ServicePrincipal": "ecs-tasks.amazonaws.com" + }, "AdvancedSetting": false, "Updatable": true, "Validators": [ @@ -260,6 +265,7 @@ "Name": "Existing VPC ID", "Description": "The ID of the existing VPC to use.", "Type": "String", + "TypeHint": "ExistingVpc", "DefaultValue": null, "AdvancedSetting": false, "Updatable": false, diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe index 1a94d8d39..38e01dfda 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe @@ -119,6 +119,7 @@ "Name": "Existing Cluster ARN", "Description": "The ARN of the existing cluster to use.", "Type": "String", + "TypeHint": "ExistingECSCluster", "AdvancedSetting": false, "Updatable": false, "Validators": [ @@ -229,6 +230,10 @@ "Name": "Existing Role ARN", "Description": "The ARN of the existing role to use.", "Type": "String", + "TypeHint": "ExistingIAMRole", + "TypeHintData": { + "ServicePrincipal": "ecs-tasks.amazonaws.com" + }, "AdvancedSetting": false, "Updatable": true, "Validators": [ @@ -288,6 +293,7 @@ "Name": "Existing VPC ID", "Description": "The ID of the existing VPC to use.", "Type": "String", + "TypeHint": "ExistingVpc", "DefaultValue": null, "AdvancedSetting": false, "Updatable": false, diff --git a/test/AWS.Deploy.CLI.IntegrationTests/Utilities/TestToolAWSResourceQueryer.cs b/test/AWS.Deploy.CLI.IntegrationTests/Utilities/TestToolAWSResourceQueryer.cs index 20ebe5099..2aca9720d 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/Utilities/TestToolAWSResourceQueryer.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/Utilities/TestToolAWSResourceQueryer.cs @@ -53,5 +53,6 @@ public Task GetLatestElasticBeanstalkPlatformArn() public Task> ListOfSQSQueuesUrls() => throw new NotImplementedException(); public Task> ListOfSNSTopicArns() => throw new NotImplementedException(); public Task> ListOfS3Buckets() => throw new NotImplementedException(); + public Task> ListOfAvailableInstanceTypes() => throw new NotImplementedException(); } } diff --git a/test/AWS.Deploy.CLI.UnitTests/Utilities/TestToolAWSResourceQueryer.cs b/test/AWS.Deploy.CLI.UnitTests/Utilities/TestToolAWSResourceQueryer.cs index a5c53781f..9401b8511 100644 --- a/test/AWS.Deploy.CLI.UnitTests/Utilities/TestToolAWSResourceQueryer.cs +++ b/test/AWS.Deploy.CLI.UnitTests/Utilities/TestToolAWSResourceQueryer.cs @@ -78,5 +78,6 @@ public Task GetLatestElasticBeanstalkPlatformArn() public Task> ListOfSQSQueuesUrls() => throw new NotImplementedException(); public Task> ListOfSNSTopicArns() => throw new NotImplementedException(); public Task> ListOfS3Buckets() => throw new NotImplementedException(); + public Task> ListOfAvailableInstanceTypes() => throw new NotImplementedException(); } }