From 52561fa7ba86a3f8df591293a1f91a81b56af3fd Mon Sep 17 00:00:00 2001 From: Philippe El Asmar Date: Sun, 1 Sep 2024 13:07:31 -0400 Subject: [PATCH 1/5] chore: add ability to select a supported architecture --- codecov.yml | 6 +- .../ASP.NET Core App to AWS App Runner.md | 4 ++ ...e App to AWS Elastic Beanstalk on Linux.md | 4 ++ ...App to AWS Elastic Beanstalk on Windows.md | 4 ++ ...ore App to Amazon ECS using AWS Fargate.md | 4 ++ ...sting AWS Elastic Beanstalk Environment.md | 4 ++ ...S Elastic Beanstalk Windows Environment.md | 4 ++ .../cicd/recipes/Blazor WebAssembly App.md | 4 ++ ...Amazon Elastic Container Registry (ECR).md | 4 ++ ...ntainer Service (ECS) using AWS Fargate.md | 4 ++ ...ntainer Service (ECS) using AWS Fargate.md | 4 ++ src/AWS.Deploy.CLI/Commands/DeployCommand.cs | 35 ++++----- .../EnvironmentArchitectureCommand.cs | 66 +++++++++++++++++ .../TypeHints/TypeHintCommandFactory.cs | 1 + .../DeploymentBundles/DeploymentBundle.cs | 7 ++ .../Recipes/OptionSettingTypeHint.cs | 3 +- .../Recipes/RecipeDefinition.cs | 5 ++ .../Recipes/SupportedArchitecture.cs | 10 +++ .../RequiredValidator.cs | 2 +- src/AWS.Deploy.Constants/RecipeIdentifier.cs | 6 ++ .../OptionSettingHandler.cs | 3 + src/AWS.Deploy.Orchestration/Orchestrator.cs | 7 ++ .../Container.deploymentbundle | 10 +++ .../DotnetPublishZipFile.deploymentbundle | 10 +++ .../ASP.NETAppAppRunner.recipe | 3 +- .../ASP.NETAppECSFargate.recipe | 3 +- .../ASP.NETAppElasticBeanstalkLinux.recipe | 3 +- .../ASP.NETAppElasticBeanstalkWindows.recipe | 3 +- ....NETAppExistingBeanstalkEnvironment.recipe | 3 +- ...ExistingBeanstalkWindowsEnvironment.recipe | 3 +- .../RecipeDefinitions/BlazorWasm.recipe | 3 +- .../ConsoleAppECSFargateScheduleTask.recipe | 3 +- .../ConsoleAppECSFargateService.recipe | 3 +- .../PushContainerImageECR.recipe | 3 +- .../aws-deploy-recipe-schema.json | 10 +++ .../DeploymentSettingsHandlerTests.cs | 4 ++ .../TestFiles/SettingsSnapshot_Container.json | 1 + .../SettingsSnapshot_NonContainer.json | 1 + .../SettingsSnapshot_PushImageECR.json | 1 + .../WebAppWithDockerFileTests.cs | 41 ++++++++++- .../AWS.Deploy.CLI.UnitTests/TypeHintTests.cs | 50 +++++++++++++ .../OrchestratorTests.cs | 71 +++++++++++++++++++ 42 files changed, 389 insertions(+), 31 deletions(-) create mode 100644 src/AWS.Deploy.CLI/Commands/TypeHints/EnvironmentArchitectureCommand.cs create mode 100644 src/AWS.Deploy.Common/Recipes/SupportedArchitecture.cs create mode 100644 test/AWS.Deploy.Orchestration.UnitTests/OrchestratorTests.cs diff --git a/codecov.yml b/codecov.yml index 44818a8f2..6bf782f44 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,2 +1,6 @@ codecov: - branch: dev # set the default branch to 'dev'. This branch provides the coverage baselines during pull requests. \ No newline at end of file + branch: dev # set the default branch to 'dev'. This branch provides the coverage baselines during pull requests. + +coverage: + exclude: + - "test/**" \ No newline at end of file diff --git a/site/content/docs/cicd/recipes/ASP.NET Core App to AWS App Runner.md b/site/content/docs/cicd/recipes/ASP.NET Core App to AWS App Runner.md index e641ac541..47cffe2bc 100644 --- a/site/content/docs/cicd/recipes/ASP.NET Core App to AWS App Runner.md +++ b/site/content/docs/cicd/recipes/ASP.NET Core App to AWS App Runner.md @@ -115,6 +115,10 @@ * ID: AppRunnerEnvironmentVariables * Description: Configure environment properties for your application. * Type: KeyValue +* **Environment Architecture** + * ID: EnvironmentArchitecture + * Description: The CPU architecture of the environment to create. + * Type: String * **Docker Build Args** * ID: DockerBuildArgs * Description: The list of additional options to append to the `docker build` command. 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 2edfb8021..87d06621a 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 @@ -175,6 +175,10 @@ * ID: SecurityGroups * Description: Lists the Amazon EC2 security groups to assign to the EC2 instances in the Auto Scaling group to define firewall rules for the instances. * Type: List +* **Environment Architecture** + * ID: EnvironmentArchitecture + * Description: The CPU architecture of the environment to create. + * Type: String * **Dotnet Build Configuration** * ID: DotnetBuildConfiguration * Description: The build configuration to use for the dotnet build 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 4c60763f5..bb5271596 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 @@ -174,6 +174,10 @@ * ID: SecurityGroups * Description: Lists the Amazon EC2 security groups to assign to the EC2 instances in the Auto Scaling group to define firewall rules for the instances. * Type: List +* **Environment Architecture** + * ID: EnvironmentArchitecture + * Description: The CPU architecture of the environment to create. + * Type: String * **Dotnet Build Configuration** * ID: DotnetBuildConfiguration * Description: The build configuration to use for the dotnet build diff --git a/site/content/docs/cicd/recipes/ASP.NET Core App to Amazon ECS using AWS Fargate.md b/site/content/docs/cicd/recipes/ASP.NET Core App to Amazon ECS using AWS Fargate.md index 31d4e8036..3b0c2543e 100644 --- a/site/content/docs/cicd/recipes/ASP.NET Core App to Amazon ECS using AWS Fargate.md +++ b/site/content/docs/cicd/recipes/ASP.NET Core App to Amazon ECS using AWS Fargate.md @@ -189,6 +189,10 @@ * ID: ECSEnvironmentVariables * Description: Configure environment properties for your application. * Type: KeyValue +* **Environment Architecture** + * ID: EnvironmentArchitecture + * Description: The CPU architecture of the environment to create. + * Type: String * **Docker Build Args** * ID: DockerBuildArgs * Description: The list of additional options to append to the `docker build` command. diff --git a/site/content/docs/cicd/recipes/ASP.NET Core App to Existing AWS Elastic Beanstalk Environment.md b/site/content/docs/cicd/recipes/ASP.NET Core App to Existing AWS Elastic Beanstalk Environment.md index ef83da49f..9144b84f2 100644 --- a/site/content/docs/cicd/recipes/ASP.NET Core App to Existing AWS Elastic Beanstalk Environment.md +++ b/site/content/docs/cicd/recipes/ASP.NET Core App to Existing AWS Elastic Beanstalk Environment.md @@ -20,6 +20,10 @@ * ID: HealthCheckURL * 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 +* **Environment Architecture** + * ID: EnvironmentArchitecture + * Description: The CPU architecture of the environment to create. + * Type: String * **Dotnet Build Configuration** * ID: DotnetBuildConfiguration * Description: The build configuration to use for the dotnet build diff --git a/site/content/docs/cicd/recipes/ASP.NET Core App to Existing AWS Elastic Beanstalk Windows Environment.md b/site/content/docs/cicd/recipes/ASP.NET Core App to Existing AWS Elastic Beanstalk Windows Environment.md index 70ce5b49d..75841782c 100644 --- a/site/content/docs/cicd/recipes/ASP.NET Core App to Existing AWS Elastic Beanstalk Windows Environment.md +++ b/site/content/docs/cicd/recipes/ASP.NET Core App to Existing AWS Elastic Beanstalk Windows Environment.md @@ -24,6 +24,10 @@ * ID: HealthCheckURL * 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 +* **Environment Architecture** + * ID: EnvironmentArchitecture + * Description: The CPU architecture of the environment to create. + * Type: String * **Dotnet Build Configuration** * ID: DotnetBuildConfiguration * Description: The build configuration to use for the dotnet build diff --git a/site/content/docs/cicd/recipes/Blazor WebAssembly App.md b/site/content/docs/cicd/recipes/Blazor WebAssembly App.md index f3b7dbf08..24c7fcf87 100644 --- a/site/content/docs/cicd/recipes/Blazor WebAssembly App.md +++ b/site/content/docs/cicd/recipes/Blazor WebAssembly App.md @@ -74,6 +74,10 @@ * ID: WebAclId * Description: The AWS WAF (web application firewall) ACL arn * Type: String +* **Environment Architecture** + * ID: EnvironmentArchitecture + * Description: The CPU architecture of the environment to create. + * Type: String * **Dotnet Build Configuration** * ID: DotnetBuildConfiguration * Description: The build configuration to use for the dotnet build diff --git a/site/content/docs/cicd/recipes/Container Image to Amazon Elastic Container Registry (ECR).md b/site/content/docs/cicd/recipes/Container Image to Amazon Elastic Container Registry (ECR).md index 19235e884..f83698e31 100644 --- a/site/content/docs/cicd/recipes/Container Image to Amazon Elastic Container Registry (ECR).md +++ b/site/content/docs/cicd/recipes/Container Image to Amazon Elastic Container Registry (ECR).md @@ -8,6 +8,10 @@ * ID: ImageTag * Description: This tag will be associated to the container images which are pushed to Amazon Elastic Container Registry. * Type: String +* **Environment Architecture** + * ID: EnvironmentArchitecture + * Description: The CPU architecture of the environment to create. + * Type: String * **Docker Build Args** * ID: DockerBuildArgs * Description: The list of additional options to append to the `docker build` command. diff --git a/site/content/docs/cicd/recipes/Scheduled Task on Amazon Elastic Container Service (ECS) using AWS Fargate.md b/site/content/docs/cicd/recipes/Scheduled Task on Amazon Elastic Container Service (ECS) using AWS Fargate.md index 29705c8ba..063f79ce3 100644 --- a/site/content/docs/cicd/recipes/Scheduled Task on Amazon Elastic Container Service (ECS) using AWS Fargate.md +++ b/site/content/docs/cicd/recipes/Scheduled Task on Amazon Elastic Container Service (ECS) using AWS Fargate.md @@ -67,6 +67,10 @@ * ID: ECSEnvironmentVariables * Description: Configure environment properties for your application. * Type: KeyValue +* **Environment Architecture** + * ID: EnvironmentArchitecture + * Description: The CPU architecture of the environment to create. + * Type: String * **Docker Build Args** * ID: DockerBuildArgs * Description: The list of additional options to append to the `docker build` command. diff --git a/site/content/docs/cicd/recipes/Service on Amazon Elastic Container Service (ECS) using AWS Fargate.md b/site/content/docs/cicd/recipes/Service on Amazon Elastic Container Service (ECS) using AWS Fargate.md index 51c4ba2d2..8ea686ce6 100644 --- a/site/content/docs/cicd/recipes/Service on Amazon Elastic Container Service (ECS) using AWS Fargate.md +++ b/site/content/docs/cicd/recipes/Service on Amazon Elastic Container Service (ECS) using AWS Fargate.md @@ -120,6 +120,10 @@ * ID: ECSEnvironmentVariables * Description: Configure environment properties for your application. * Type: KeyValue +* **Environment Architecture** + * ID: EnvironmentArchitecture + * Description: The CPU architecture of the environment to create. + * Type: String * **Docker Build Args** * ID: DockerBuildArgs * Description: The list of additional options to append to the `docker build` command. diff --git a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs index 612818a20..702d8116f 100644 --- a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs @@ -726,28 +726,29 @@ private async Task ConfigureDeploymentFromCli(Recommendation recommendation, Opt object currentValue = _optionSettingHandler.GetOptionSettingValue(recommendation, setting); object? settingValue = null; - if (setting.AllowedValues?.Count > 0) - { - var userInputConfig = new UserInputConfiguration( - idSelector: x => x, - displaySelector: x => setting.ValueMapping.ContainsKey(x) ? setting.ValueMapping[x] : x, - defaultSelector: x => x.Equals(currentValue)) - { - CreateNew = false - }; - var userResponse = _consoleUtilities.AskUserToChooseOrCreateNew(setting.AllowedValues, string.Empty, userInputConfig); - settingValue = userResponse.SelectedOption; - - // If they didn't change the value then don't store so we can rely on using the default in the recipe. - if (Equals(settingValue, currentValue)) - return; + if (setting.TypeHint.HasValue && _typeHintCommandFactory.GetCommand(setting.TypeHint.Value) is var typeHintCommand && typeHintCommand != null) + { + settingValue = await typeHintCommand.Execute(recommendation, setting); } else { - if (setting.TypeHint.HasValue && _typeHintCommandFactory.GetCommand(setting.TypeHint.Value) is var typeHintCommand && typeHintCommand != null) + if (setting.AllowedValues?.Count > 0) { - settingValue = await typeHintCommand.Execute(recommendation, setting); + var userInputConfig = new UserInputConfiguration( + idSelector: x => x, + displaySelector: x => setting.ValueMapping.ContainsKey(x) ? setting.ValueMapping[x] : x, + defaultSelector: x => x.Equals(currentValue)) + { + CreateNew = false + }; + + var userResponse = _consoleUtilities.AskUserToChooseOrCreateNew(setting.AllowedValues, string.Empty, userInputConfig); + settingValue = userResponse.SelectedOption; + + // If they didn't change the value then don't store so we can rely on using the default in the recipe. + if (Equals(settingValue, currentValue)) + return; } else { diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/EnvironmentArchitectureCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/EnvironmentArchitectureCommand.cs new file mode 100644 index 000000000..71f97138a --- /dev/null +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/EnvironmentArchitectureCommand.cs @@ -0,0 +1,66 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using AWS.Deploy.Common; +using AWS.Deploy.Common.Recipes; +using AWS.Deploy.Common.TypeHintData; + +namespace AWS.Deploy.CLI.Commands.TypeHints +{ + public class EnvironmentArchitectureCommand : ITypeHintCommand + { + private readonly IConsoleUtilities _consoleUtilities; + private readonly IOptionSettingHandler _optionSettingHandler; + + public EnvironmentArchitectureCommand(IConsoleUtilities consoleUtilities, IOptionSettingHandler optionSettingHandler) + { + _consoleUtilities = consoleUtilities; + _optionSettingHandler = optionSettingHandler; + } + + public Task GetResources(Recommendation recommendation, OptionSettingItem optionSetting) + { + var resourceTable = new TypeHintResourceTable + { + Columns = new List() + { + new TypeHintResourceColumn("Architecture") + } + }; + + foreach (var value in recommendation.Recipe.SupportedArchitectures ?? new List { SupportedArchitecture.X86_64 }) + { + var stringValue = value.ToString(); + var row = new TypeHintResource(stringValue, stringValue); + row.ColumnValues.Add(stringValue); + + resourceTable.Rows.Add(row); + } + + return Task.FromResult(resourceTable); + } + + public async Task Execute(Recommendation recommendation, OptionSettingItem optionSetting) + { + var currentValue = _optionSettingHandler.GetOptionSettingValue(recommendation, optionSetting); + var resourceTable = await GetResources(recommendation, optionSetting); + + var userInputConfiguration = new UserInputConfiguration( + idSelector: platform => platform.SystemName, + displaySelector: platform => platform.DisplayName, + defaultSelector: platform => platform.SystemName.Equals(currentValue)) + { + CreateNew = false + }; + + var userResponse = _consoleUtilities.AskUserToChooseOrCreateNew(resourceTable.Rows, "Select the Platform to use:", userInputConfiguration); + + var result = userResponse.SelectedOption?.SystemName!; + recommendation.DeploymentBundle.EnvironmentArchitecture = Enum.Parse(result); + return result; + } + } +} diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs index 4463ee24a..a7eabe253 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs @@ -73,6 +73,7 @@ public TypeHintCommandFactory(IServiceProvider serviceProvider, IToolInteractive { OptionSettingTypeHint.FilePath, ActivatorUtilities.CreateInstance(serviceProvider) }, { OptionSettingTypeHint.ElasticBeanstalkVpc, ActivatorUtilities.CreateInstance(serviceProvider) }, { OptionSettingTypeHint.DockerHttpPort, ActivatorUtilities.CreateInstance(serviceProvider) }, + { OptionSettingTypeHint.EnvironmentArchitecture, ActivatorUtilities.CreateInstance(serviceProvider) }, }; } diff --git a/src/AWS.Deploy.Common/DeploymentBundles/DeploymentBundle.cs b/src/AWS.Deploy.Common/DeploymentBundles/DeploymentBundle.cs index 377e7b690..677fd7b2f 100644 --- a/src/AWS.Deploy.Common/DeploymentBundles/DeploymentBundle.cs +++ b/src/AWS.Deploy.Common/DeploymentBundles/DeploymentBundle.cs @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +using AWS.Deploy.Common.Recipes; + namespace AWS.Deploy.Common { /// @@ -63,5 +65,10 @@ public class DeploymentBundle /// The list of additional dotnet publish args passed to the target application. /// public string DotnetPublishAdditionalBuildArguments { get; set; } = ""; + + /// + /// The CPU architecture of the environment to create. + /// + public SupportedArchitecture EnvironmentArchitecture { get; set; } = SupportedArchitecture.X86_64; } } diff --git a/src/AWS.Deploy.Common/Recipes/OptionSettingTypeHint.cs b/src/AWS.Deploy.Common/Recipes/OptionSettingTypeHint.cs index 4249787e6..ce99d3841 100644 --- a/src/AWS.Deploy.Common/Recipes/OptionSettingTypeHint.cs +++ b/src/AWS.Deploy.Common/Recipes/OptionSettingTypeHint.cs @@ -40,6 +40,7 @@ public enum OptionSettingTypeHint VPCConnector, FilePath, ElasticBeanstalkVpc, - DockerHttpPort + DockerHttpPort, + EnvironmentArchitecture }; } diff --git a/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs b/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs index 62e050389..2a76e8798 100644 --- a/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs +++ b/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs @@ -60,6 +60,11 @@ public class RecipeDefinition /// public TargetPlatform? TargetPlatform { get; set; } + /// + /// The CPU architecture that is supported by the recipe. + /// + public List? SupportedArchitectures { get; set; } + /// /// The list of DisplayedResources that lists logical CloudFormation IDs with a description. /// diff --git a/src/AWS.Deploy.Common/Recipes/SupportedArchitecture.cs b/src/AWS.Deploy.Common/Recipes/SupportedArchitecture.cs new file mode 100644 index 000000000..eb5786f5f --- /dev/null +++ b/src/AWS.Deploy.Common/Recipes/SupportedArchitecture.cs @@ -0,0 +1,10 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r +// SPDX-License-Identifier: Apache-2.0 + +namespace AWS.Deploy.Common.Recipes; + +public enum SupportedArchitecture +{ + X86_64, + Arm64 +} diff --git a/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/RequiredValidator.cs b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/RequiredValidator.cs index 9f63b30f5..20dd906c6 100644 --- a/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/RequiredValidator.cs +++ b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/RequiredValidator.cs @@ -19,7 +19,7 @@ public class RequiredValidator : IOptionSettingItemValidator public Task Validate(object input, Recommendation recommendation, OptionSettingItem optionSettingItem) { var message = ValidationFailedMessage.Replace("{{OptionSetting}}", optionSettingItem.Name); - if (input?.TryDeserialize>(out var inputList) ?? false && inputList != null) + if (input?.TryDeserialize>(out var inputList) ?? false) { return Task.FromResult(new() { diff --git a/src/AWS.Deploy.Constants/RecipeIdentifier.cs b/src/AWS.Deploy.Constants/RecipeIdentifier.cs index 6ba7ba0c5..c7ea55287 100644 --- a/src/AWS.Deploy.Constants/RecipeIdentifier.cs +++ b/src/AWS.Deploy.Constants/RecipeIdentifier.cs @@ -22,6 +22,7 @@ internal static class RecipeIdentifier public const string REPLACE_TOKEN_HAS_DEFAULT_VPC = "{HasDefaultVpc}"; public const string REPLACE_TOKEN_HAS_NOT_VPCS = "{HasNotVpcs}"; public const string REPLACE_TOKEN_DEFAULT_CONTAINER_PORT = "{DefaultContainerPort}"; + public const string REPLACE_TOKEN_DEFAULT_ENVIRONMENT_ARCHITECTURE = "{DefaultEnvironmentArchitecture}"; /// /// Id for the 'dotnet publish --configuration' recipe option @@ -38,6 +39,11 @@ internal static class RecipeIdentifier /// public const string DotnetPublishSelfContainedBuildOptionId = "SelfContainedBuild"; + /// + /// Id for the environment architecture recipe option + /// + public const string EnvironmentArchitectureOptionId = "EnvironmentArchitecture"; + public const string TARGET_SERVICE_ELASTIC_BEANSTALK = "AWS Elastic Beanstalk"; } } diff --git a/src/AWS.Deploy.Orchestration/OptionSettingHandler.cs b/src/AWS.Deploy.Orchestration/OptionSettingHandler.cs index 5ae0988e1..862412e0f 100644 --- a/src/AWS.Deploy.Orchestration/OptionSettingHandler.cs +++ b/src/AWS.Deploy.Orchestration/OptionSettingHandler.cs @@ -144,6 +144,9 @@ private void SetDeploymentBundleProperty(Recommendation recommendation, OptionSe case Constants.RecipeIdentifier.DotnetPublishSelfContainedBuildOptionId: recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild = Convert.ToBoolean(value); break; + case Constants.RecipeIdentifier.EnvironmentArchitectureOptionId: + recommendation.DeploymentBundle.EnvironmentArchitecture = Enum.Parse(value.ToString() ?? string.Empty); + break; default: return; } diff --git a/src/AWS.Deploy.Orchestration/Orchestrator.cs b/src/AWS.Deploy.Orchestration/Orchestrator.cs index 4f2e40961..659096ddb 100644 --- a/src/AWS.Deploy.Orchestration/Orchestrator.cs +++ b/src/AWS.Deploy.Orchestration/Orchestrator.cs @@ -244,6 +244,13 @@ public async Task ApplyAllReplacementTokens(Recommendation recommendation, strin recommendation.AddReplacementToken(Constants.RecipeIdentifier.REPLACE_TOKEN_DEFAULT_CONTAINER_PORT, defaultPort); recommendation.DeploymentBundle.DockerfileHttpPort = defaultPort; } + if (recommendation.ReplacementTokens.ContainsKey(Constants.RecipeIdentifier.REPLACE_TOKEN_DEFAULT_ENVIRONMENT_ARCHITECTURE)) + { + if (_dockerEngine == null) + throw new InvalidOperationException($"{nameof(_dockerEngine)} is null as part of the Orchestrator object"); + + recommendation.AddReplacementToken(Constants.RecipeIdentifier.REPLACE_TOKEN_DEFAULT_ENVIRONMENT_ARCHITECTURE, SupportedArchitecture.X86_64.ToString()); + } } public async Task DeployRecommendation(CloudApplication cloudApplication, Recommendation recommendation) diff --git a/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/Container.deploymentbundle b/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/Container.deploymentbundle index 22f521d78..9645dc655 100644 --- a/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/Container.deploymentbundle +++ b/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/Container.deploymentbundle @@ -1,6 +1,16 @@ { "Type": "Container", "Parameters": [ + { + "Id": "EnvironmentArchitecture", + "Name": "Environment Architecture", + "Description": "The CPU architecture of the environment to create.", + "TypeHint": "EnvironmentArchitecture", + "Type": "String", + "DefaultValue": "{DefaultEnvironmentArchitecture}", + "AdvancedSetting": false, + "Updatable": true + }, { "Id": "DockerBuildArgs", "Name": "Docker Build Args", diff --git a/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/DotnetPublishZipFile.deploymentbundle b/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/DotnetPublishZipFile.deploymentbundle index aae6026a1..40200c1a9 100644 --- a/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/DotnetPublishZipFile.deploymentbundle +++ b/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/DotnetPublishZipFile.deploymentbundle @@ -1,6 +1,16 @@ { "Type": "DotnetPublishZipFile", "Parameters": [ + { + "Id": "EnvironmentArchitecture", + "Name": "Environment Architecture", + "Description": "The CPU architecture of the environment to create.", + "TypeHint": "EnvironmentArchitecture", + "Type": "String", + "DefaultValue": "{DefaultEnvironmentArchitecture}", + "AdvancedSetting": false, + "Updatable": true + }, { "Id": "DotnetBuildConfiguration", "Name": "Dotnet Build Configuration", diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe index 001346e6a..186260631 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe @@ -1,7 +1,7 @@ { "$schema": "./aws-deploy-recipe-schema.json", "Id": "AspNetAppAppRunner", - "Version": "1.0.2", + "Version": "1.0.3", "Name": "ASP.NET Core App to AWS App Runner", "DeploymentType": "CdkProject", "DeploymentBundle": "Container", @@ -11,6 +11,7 @@ "Description": "This ASP.NET Core application will be built as a container image on Linux and deployed to AWS App Runner, a fully managed service for web applications and APIs. If your project does not contain a Dockerfile, it will be automatically generated, otherwise an existing Dockerfile will be used. Recommended if you want to deploy your web application as a Linux container image on a fully managed environment.", "TargetService": "AWS App Runner", "TargetPlatform": "Linux", + "SupportedArchitectures": [ "x86_64" ], "DisplayedResources": [ { diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe index 4ad115995..8c3482861 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe @@ -1,7 +1,7 @@ { "$schema": "./aws-deploy-recipe-schema.json", "Id": "AspNetAppEcsFargate", - "Version": "1.1.1", + "Version": "1.1.2", "Name": "ASP.NET Core App to Amazon ECS using AWS Fargate", "DeploymentType": "CdkProject", "DeploymentBundle": "Container", @@ -11,6 +11,7 @@ "Description": "This ASP.NET Core application will be deployed to Amazon Elastic Container Service (Amazon ECS) with compute power managed by AWS Fargate compute engine. If your project does not contain a Dockerfile, it will be automatically generated, otherwise an existing Dockerfile will be used. Recommended if you want to deploy your application as a container image on Linux.", "TargetService": "Amazon Elastic Container Service", "TargetPlatform": "Linux", + "SupportedArchitectures": [ "x86_64" ], "DisplayedResources": [ { diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalkLinux.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalkLinux.recipe index ddc5c6266..5f09b8bb3 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalkLinux.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalkLinux.recipe @@ -1,7 +1,7 @@ { "$schema": "./aws-deploy-recipe-schema.json", "Id": "AspNetAppElasticBeanstalkLinux", - "Version": "1.0.2", + "Version": "1.0.3", "Name": "ASP.NET Core App to AWS Elastic Beanstalk on Linux", "DeploymentType": "CdkProject", "DeploymentBundle": "DotnetPublishZipFile", @@ -11,6 +11,7 @@ "Description": "This ASP.NET Core application will be built and deployed to AWS Elastic Beanstalk on Linux. Recommended if you want to deploy your application directly to EC2 hosts, not as a container image.", "TargetService": "AWS Elastic Beanstalk", "TargetPlatform": "Linux", + "SupportedArchitectures": [ "x86_64" ], "DisplayedResources": [ { diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalkWindows.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalkWindows.recipe index 0784dead9..cceebea49 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalkWindows.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalkWindows.recipe @@ -1,7 +1,7 @@ { "$schema": "./aws-deploy-recipe-schema.json", "Id": "AspNetAppElasticBeanstalkWindows", - "Version": "1.0.2", + "Version": "1.0.3", "Name": "ASP.NET Core App to AWS Elastic Beanstalk on Windows", "DeploymentType": "CdkProject", "DeploymentBundle": "DotnetPublishZipFile", @@ -11,6 +11,7 @@ "ShortDescription": "ASP.NET Core application deployed to AWS Elastic Beanstalk on Windows.", "TargetService": "AWS Elastic Beanstalk", "TargetPlatform": "Windows", + "SupportedArchitectures": [ "x86_64" ], "DisplayedResources": [ { diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppExistingBeanstalkEnvironment.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppExistingBeanstalkEnvironment.recipe index 101ab7404..9dc1508ec 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppExistingBeanstalkEnvironment.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppExistingBeanstalkEnvironment.recipe @@ -1,7 +1,7 @@ { "$schema": "./aws-deploy-recipe-schema.json", "Id": "AspNetAppExistingBeanstalkEnvironment", - "Version": "1.0.1", + "Version": "1.0.2", "Name": "ASP.NET Core App to Existing AWS Elastic Beanstalk Environment", "DisableNewDeployments": true, "DeploymentType": "BeanstalkEnvironment", @@ -10,6 +10,7 @@ "Description": "This ASP.NET Core application will be built and deployed to an existing AWS Elastic Beanstalk environment. Recommended if you want to deploy your application directly to EC2 hosts, not as a container image.", "TargetService": "AWS Elastic Beanstalk", "TargetPlatform": "Linux", + "SupportedArchitectures": [ "x86_64" ], "RecipePriority": 0, "RecommendationRules": [ diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppExistingBeanstalkWindowsEnvironment.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppExistingBeanstalkWindowsEnvironment.recipe index 318ae7a95..212d73ad4 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppExistingBeanstalkWindowsEnvironment.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppExistingBeanstalkWindowsEnvironment.recipe @@ -1,7 +1,7 @@ { "$schema": "./aws-deploy-recipe-schema.json", "Id": "AspNetAppExistingBeanstalkWindowsEnvironment", - "Version": "1.0.0", + "Version": "1.0.1", "Name": "ASP.NET Core App to Existing AWS Elastic Beanstalk Windows Environment", "DisableNewDeployments": true, "DeploymentType": "BeanstalkEnvironment", @@ -10,6 +10,7 @@ "Description": "This ASP.NET Core application will be built and deployed to an existing AWS Elastic Beanstalk Windows environment. Recommended if you want to deploy your application directly to EC2 hosts, not as a container image.", "TargetService": "AWS Elastic Beanstalk", "TargetPlatform": "Windows", + "SupportedArchitectures": [ "x86_64" ], "RecipePriority": 0, "RecommendationRules": [ diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/BlazorWasm.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/BlazorWasm.recipe index 21b19ffff..e2268c4ca 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/BlazorWasm.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/BlazorWasm.recipe @@ -1,7 +1,7 @@ { "$schema": "./aws-deploy-recipe-schema.json", "Id": "BlazorWasm", - "Version": "1.0.1", + "Version": "1.0.2", "Name": "Blazor WebAssembly App", "DeploymentType": "CdkProject", "DeploymentBundle": "DotnetPublishZipFile", @@ -11,6 +11,7 @@ "Description": "This Blazor WebAssembly application will be built and hosted in a new Amazon Simple Storage Service (Amazon S3) bucket. The Blazor application will be exposed publicly through a CloudFront distribution using the Amazon S3 bucket as the origin.", "TargetService": "Amazon S3", "TargetPlatform": "Linux", + "SupportedArchitectures": [ "x86_64" ], "DisplayedResources": [ { diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe index f76659031..b4a027f89 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe @@ -1,7 +1,7 @@ { "$schema": "./aws-deploy-recipe-schema.json", "Id": "ConsoleAppEcsFargateScheduleTask", - "Version": "1.0.2", + "Version": "1.0.3", "Name": "Scheduled Task on Amazon Elastic Container Service (ECS) using AWS Fargate", "DeploymentType": "CdkProject", "DeploymentBundle": "Container", @@ -11,6 +11,7 @@ "ShortDescription": "Deploys a scheduled task as a Linux container image to a fully managed container orchestration service. Dockerfile will be automatically generated if needed.", "TargetService": "Amazon Elastic Container Service", "TargetPlatform": "Linux", + "SupportedArchitectures": [ "x86_64" ], "DisplayedResources": [ { diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe index fa7026f8f..6501e246a 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe @@ -1,7 +1,7 @@ { "$schema": "./aws-deploy-recipe-schema.json", "Id": "ConsoleAppEcsFargateService", - "Version": "1.0.2", + "Version": "1.0.3", "Name": "Service on Amazon Elastic Container Service (ECS) using AWS Fargate", "DeploymentType": "CdkProject", "DeploymentBundle": "Container", @@ -11,6 +11,7 @@ "ShortDescription": "Deploys a service as a Linux container image to a fully managed container orchestration service. Dockerfile will be automatically generated if needed.", "TargetService": "Amazon Elastic Container Service", "TargetPlatform": "Linux", + "SupportedArchitectures": [ "x86_64" ], "DisplayedResources": [ { diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/PushContainerImageECR.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/PushContainerImageECR.recipe index ef627e586..d879aa3f8 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/PushContainerImageECR.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/PushContainerImageECR.recipe @@ -1,7 +1,7 @@ { "$schema": "./aws-deploy-recipe-schema.json", "Id": "PushContainerImageEcr", - "Version": "1.0.1", + "Version": "1.0.2", "Name": "Container Image to Amazon Elastic Container Registry (ECR)", "DeploymentType": "ElasticContainerRegistryImage", "DeploymentBundle": "Container", @@ -9,6 +9,7 @@ "ShortDescription": "Pushes container image to a fully managed container registry.", "TargetService": "Amazon Elastic Container Service", "TargetPlatform": "Linux", + "SupportedArchitectures": [ "x86_64" ], "RecipePriority": 0, "RecommendationRules": [ diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/aws-deploy-recipe-schema.json b/src/AWS.Deploy.Recipes/RecipeDefinitions/aws-deploy-recipe-schema.json index 99f053e26..f75c113f3 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/aws-deploy-recipe-schema.json +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/aws-deploy-recipe-schema.json @@ -17,6 +17,7 @@ "ShortDescription", "TargetService", "TargetPlatform", + "SupportedArchitectures", "RecipePriority", "RecommendationRules", "OptionSettings" @@ -103,6 +104,15 @@ "minLength": 1, "enum": [ "Linux", "Windows" ] }, + "SupportedArchitectures": { + "type": "array", + "title": "Supported Architectures", + "description": "The CPU architecture that is supported by the recipe.", + "items": { + "type": "string", + "enum": [ "x86_64", "arm64" ] + } + }, "DeploymentConfirmation": { "$ref": "#/definitions/DeploymentConfirmation" }, diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/DeploymentSettingsHandlerTests.cs b/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/DeploymentSettingsHandlerTests.cs index 33a761ba0..50205b6ca 100644 --- a/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/DeploymentSettingsHandlerTests.cs +++ b/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/DeploymentSettingsHandlerTests.cs @@ -34,6 +34,7 @@ public class DeploymentSettingsHandlerTests private const string STACK_NAME_TOKEN = "{StackName}"; private const string DEFAULT_VPC_ID_TOKEN = "{DefaultVpcId}"; private const string DEFAULT_CONTAINER_PORT_TOKEN = "{DefaultContainerPort}"; + private const string DEFAULT_ENVIRONMENT_ARCHITECTURE = "{DefaultEnvironmentArchitecture}"; public DeploymentSettingsHandlerTests() { @@ -163,6 +164,7 @@ public async Task SaveSettings_NonContainerBased(SaveSettingsType saveSettingsTy selectedRecommendation.AddReplacementToken(BEANSTALK_PLATFORM_ARN_TOKEN, "Latest-ARN"); selectedRecommendation.AddReplacementToken(STACK_NAME_TOKEN, "MyAppStack"); selectedRecommendation.AddReplacementToken(DEFAULT_VPC_ID_TOKEN, "vpc-12345678"); + selectedRecommendation.AddReplacementToken(DEFAULT_ENVIRONMENT_ARCHITECTURE, "X86_64"); // ARRANGE - Modify option setting items await _optionSettingHandler.SetOptionSettingValue(selectedRecommendation, "BeanstalkApplication", "MyBeanstalkApplication"); @@ -202,6 +204,7 @@ public async Task SaveSettings_ContainerBased(SaveSettingsType saveSettingsType, selectedRecommendation.AddReplacementToken(STACK_NAME_TOKEN, "MyAppStack"); selectedRecommendation.AddReplacementToken(DEFAULT_VPC_ID_TOKEN, "vpc-12345678"); selectedRecommendation.AddReplacementToken(DEFAULT_CONTAINER_PORT_TOKEN, 80); + selectedRecommendation.AddReplacementToken(DEFAULT_ENVIRONMENT_ARCHITECTURE, "X86_64"); // ARRANGE - Modify option setting items await _optionSettingHandler.SetOptionSettingValue(selectedRecommendation, "ServiceName", "MyAppRunnerService"); @@ -234,6 +237,7 @@ public async Task SaveSettings_PushImageToECR() // ARRANGE - Modify option setting items await _optionSettingHandler.SetOptionSettingValue(selectedRecommendation, "ImageTag", "123456789"); await _optionSettingHandler.SetOptionSettingValue(selectedRecommendation, "ECRRepositoryName", "my-ecr-repository"); + await _optionSettingHandler.SetOptionSettingValue(selectedRecommendation, "EnvironmentArchitecture", "X86_64"); await _optionSettingHandler.SetOptionSettingValue(selectedRecommendation, "DockerfilePath", Path.Combine("DockerAssets", "Dockerfile")); // relative path await _optionSettingHandler.SetOptionSettingValue(selectedRecommendation, "DockerExecutionDirectory", Path.Combine(_projectPath, "DockerAssets")); // absolute path diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/SettingsSnapshot_Container.json b/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/SettingsSnapshot_Container.json index 1717e6e1f..b482f66ba 100644 --- a/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/SettingsSnapshot_Container.json +++ b/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/SettingsSnapshot_Container.json @@ -26,6 +26,7 @@ "UseVPCConnector": false }, "AppRunnerEnvironmentVariables": "", + "EnvironmentArchitecture": "X86_64", "DockerBuildArgs": "", "DockerfilePath": "DockerAssets/Dockerfile", "DockerExecutionDirectory": "DockerAssets", diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/SettingsSnapshot_NonContainer.json b/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/SettingsSnapshot_NonContainer.json index 0d75b926c..e96c3d097 100644 --- a/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/SettingsSnapshot_NonContainer.json +++ b/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/SettingsSnapshot_NonContainer.json @@ -40,6 +40,7 @@ "VPC": { "UseVPC": false }, + "EnvironmentArchitecture": "X86_64", "DotnetBuildConfiguration": "Release", "DotnetPublishArgs": "", "SelfContainedBuild": false diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/SettingsSnapshot_PushImageECR.json b/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/SettingsSnapshot_PushImageECR.json index 93a75a98b..c8b74ea7b 100644 --- a/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/SettingsSnapshot_PushImageECR.json +++ b/test/AWS.Deploy.CLI.Common.UnitTests/ConfigFileDeployment/TestFiles/SettingsSnapshot_PushImageECR.json @@ -4,6 +4,7 @@ "RecipeId": "PushContainerImageEcr", "Settings": { "ImageTag": 123456789, + "EnvironmentArchitecture": "X86_64", "DockerBuildArgs": "", "DockerfilePath": "DockerAssets/Dockerfile", "DockerExecutionDirectory": "DockerAssets", diff --git a/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs index fa75bbc53..2464abbc9 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs @@ -57,6 +57,45 @@ public WebAppWithDockerFileTests() _testAppManager = new TestAppManager(); } + [Fact] + public async Task ApplySettingsWithoutDeploying() + { + _stackName = $"WebAppWithDockerFile{Guid.NewGuid().ToString().Split('-').Last()}"; + + // Arrange input for deploy + await _interactiveService.StdInWriter.WriteAsync(Environment.NewLine); // Select default recommendation + await _interactiveService.StdInWriter.WriteLineAsync("more"); // Select 'more' + await _interactiveService.StdInWriter.WriteLineAsync("13"); // Select 'Environment Architecture' + await _interactiveService.StdInWriter.WriteLineAsync("1"); // Select 'X86_64' + await _interactiveService.StdInWriter.WriteLineAsync("13"); // Select 'Environment Architecture' again for Code Coverage + await _interactiveService.StdInWriter.WriteLineAsync("1"); // Select 'X86_64' + await _interactiveService.StdInWriter.WriteLineAsync("8"); // Select 'Task CPU' + await _interactiveService.StdInWriter.WriteLineAsync("2"); // Select '512 (.5 vCPU)' + await _interactiveService.StdInWriter.FlushAsync(); + + // Deploy + var projectPath = _testAppManager.GetProjectPath(Path.Combine("testapps", "WebAppWithDockerFile", "WebAppWithDockerFile.csproj")); + var deployArgs = new[] { "deploy", "--project-path", projectPath, "--application-name", _stackName, "--diagnostics" }; + + await _app.Run(deployArgs); + + var consoleOutput = _interactiveService.StdOutReader.ReadAllLines(); + + // Assert 'Environment Architecture' is set to 'X86_64' + var environmentArchitecture = consoleOutput.LastOrDefault(x => x.StartsWith("13. Environment Architecture:")); + Assert.NotNull(environmentArchitecture); + var environmentArchitectureSplit = environmentArchitecture.Split(':').ToList().Select(x => x.Trim()).ToList(); + Assert.Equal(2, environmentArchitectureSplit.Count); + Assert.Equal("X86_64", environmentArchitectureSplit[1]); + + // Assert 'Task CPU' is set to '512' + var taskCpu = consoleOutput.LastOrDefault(x => x.StartsWith("8 . Task CPU:")); + Assert.NotNull(taskCpu); + var taskCpuSplit = taskCpu.Split(':').ToList().Select(x => x.Trim()).ToList(); + Assert.Equal(2, taskCpuSplit.Count); + Assert.Equal("512", taskCpuSplit[1]); + } + [Fact] public async Task DefaultConfigurations() { @@ -188,7 +227,7 @@ public async Task AppRunnerDeployment() // URL could take few more minutes to come live, therefore, we want to wait and keep trying for a specified timeout await _httpHelper.WaitUntilSuccessStatusCode(applicationUrl, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(5)); - // Ensure environemnt variables specified in AppRunnerConfigFile.json are set for the service. + // Ensure environemnt variables specified in AppRunnerConfigFile.json are set for the service. var checkEnvironmentVariableUrl = applicationUrl + "envvar/TEST_Key1"; using var httpClient = new HttpClient(); var envVarValue = await httpClient.GetStringAsync(checkEnvironmentVariableUrl); diff --git a/test/AWS.Deploy.CLI.UnitTests/TypeHintTests.cs b/test/AWS.Deploy.CLI.UnitTests/TypeHintTests.cs index 826e9e1f0..b5b69e159 100644 --- a/test/AWS.Deploy.CLI.UnitTests/TypeHintTests.cs +++ b/test/AWS.Deploy.CLI.UnitTests/TypeHintTests.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Amazon.DynamoDBv2; @@ -25,6 +27,7 @@ using Xunit; using AWS.Deploy.Common.Data; using AWS.Deploy.Common.IO; +using Should; namespace AWS.Deploy.CLI.UnitTests { @@ -156,6 +159,53 @@ public async Task TestS3BucketNameTypeHint() Assert.Equal("Bucket2", resources.Rows[1].SystemName); } + [Theory] + [InlineData(null)] + [InlineData(new [] { SupportedArchitecture.X86_64 })] + [InlineData(new [] { SupportedArchitecture.Arm64 })] + [InlineData(new [] { SupportedArchitecture.X86_64, SupportedArchitecture.Arm64 })] + public async Task TestEnvironmentArchitectureTypeHint_NoArchitectureDefined(SupportedArchitecture[] architectures) + { + var archList = architectures?.ToList(); + var recipeDefinition = new Mock( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()).Object; + var projectDefinitionParser = new ProjectDefinitionParser(new FileManager(), new DirectoryManager()); + var projectPath = SystemIOUtilities.ResolvePath("ConsoleAppTask"); + var project = await projectDefinitionParser.Parse(projectPath); + recipeDefinition.SupportedArchitectures = archList; + var recommendation = new Recommendation(recipeDefinition, project, 0, new Dictionary()); + + var typeHintCommand = new EnvironmentArchitectureCommand(null, _optionSettingHandler); + + var resources = await typeHintCommand.GetResources(recommendation, null); + + if (archList is null) + { + var expectedArchitecture = SupportedArchitecture.X86_64.ToString(); + var row = Assert.Single(resources.Rows); + Assert.Equal(expectedArchitecture, row.SystemName); + Assert.Equal(expectedArchitecture, row.DisplayName); + } + else + { + Assert.Equal(architectures.Length, resources.Rows.Count); + foreach (var row in resources.Rows) + { + Assert.Contains(archList, x => x.ToString().Equals(row.DisplayName)); + Assert.Contains(archList, x => x.ToString().Equals(row.SystemName)); + } + } + } + [Fact] public async Task DotnetBeanstalkPlatformArnCommandTest() { diff --git a/test/AWS.Deploy.Orchestration.UnitTests/OrchestratorTests.cs b/test/AWS.Deploy.Orchestration.UnitTests/OrchestratorTests.cs new file mode 100644 index 000000000..b1842ed84 --- /dev/null +++ b/test/AWS.Deploy.Orchestration.UnitTests/OrchestratorTests.cs @@ -0,0 +1,71 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Linq; +using System.Threading.Tasks; +using Amazon.Runtime; +using AWS.Deploy.Common; +using AWS.Deploy.Common.DeploymentManifest; +using AWS.Deploy.Common.IO; +using AWS.Deploy.Common.Recipes; +using AWS.Deploy.Common.Recipes.Validation; +using AWS.Deploy.Orchestration.UnitTests.Utilities; +using Moq; +using Xunit; + +namespace AWS.Deploy.Orchestration.UnitTests; + +public class OrchestratorTests +{ + private readonly IRecipeHandler _recipeHandler; + private OrchestratorSession _session; + private readonly IDeploymentManifestEngine _deploymentManifestEngine; + private readonly Mock _orchestratorInteractiveService; + private readonly IDirectoryManager _directoryManager; + private readonly IFileManager _fileManager; + + public OrchestratorTests() + { + _directoryManager = new DirectoryManager(); + _fileManager = new FileManager(); + _deploymentManifestEngine = new DeploymentManifestEngine(_directoryManager, _fileManager); + _orchestratorInteractiveService = new Mock(); + var serviceProvider = new Mock(); + var validatorFactory = new ValidatorFactory(serviceProvider.Object); + var optionSettingHandler = new OptionSettingHandler(validatorFactory); + _recipeHandler = new RecipeHandler(_deploymentManifestEngine, _orchestratorInteractiveService.Object, _directoryManager, _fileManager, optionSettingHandler, validatorFactory); + } + + private async Task BuildRecommendationEngine(string testProjectName) + { + var fullPath = SystemIOUtilities.ResolvePath(testProjectName); + + var parser = new ProjectDefinitionParser(new FileManager(), new DirectoryManager()); + var awsCredentials = new Mock(); + _session = new OrchestratorSession( + await parser.Parse(fullPath), + awsCredentials.Object, + "us-west-2", + "123456789012") + { + AWSProfileName = "default" + }; + + return new RecommendationEngine.RecommendationEngine(_session, _recipeHandler); + } + + [Fact] + public async Task ApplyAllReplacementTokensTest() + { + var engine = await BuildRecommendationEngine("WebAppNoDockerFile"); + var recommendations = await engine.ComputeRecommendations(); + var recommendation = recommendations.First(r => r.Recipe.Id.Equals("AspNetAppElasticBeanstalkLinux")); + var orchestrator = new Orchestrator(_session, _recipeHandler); + + recommendation.ReplacementTokens.Clear(); + recommendation.ReplacementTokens.Add(Constants.RecipeIdentifier.REPLACE_TOKEN_DEFAULT_ENVIRONMENT_ARCHITECTURE, true); + + await Assert.ThrowsAsync(async () => await orchestrator.ApplyAllReplacementTokens(recommendation, "WebAppNoDockerFile")); + } +} From 2ea4f02053798b52efc8db61677a642bb4f363a2 Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Thu, 10 Oct 2024 09:25:01 -0400 Subject: [PATCH 2/5] address norm's comments --- src/AWS.Deploy.Orchestration/Orchestrator.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/AWS.Deploy.Orchestration/Orchestrator.cs b/src/AWS.Deploy.Orchestration/Orchestrator.cs index 659096ddb..b20221d06 100644 --- a/src/AWS.Deploy.Orchestration/Orchestrator.cs +++ b/src/AWS.Deploy.Orchestration/Orchestrator.cs @@ -246,9 +246,6 @@ public async Task ApplyAllReplacementTokens(Recommendation recommendation, strin } if (recommendation.ReplacementTokens.ContainsKey(Constants.RecipeIdentifier.REPLACE_TOKEN_DEFAULT_ENVIRONMENT_ARCHITECTURE)) { - if (_dockerEngine == null) - throw new InvalidOperationException($"{nameof(_dockerEngine)} is null as part of the Orchestrator object"); - recommendation.AddReplacementToken(Constants.RecipeIdentifier.REPLACE_TOKEN_DEFAULT_ENVIRONMENT_ARCHITECTURE, SupportedArchitecture.X86_64.ToString()); } } From a1e96999e60ee023e51a9b27c3bb47fbb0b1c8f7 Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Fri, 11 Oct 2024 14:54:06 -0400 Subject: [PATCH 3/5] address chris' comments --- .../TypeHints/EnvironmentArchitectureCommand.cs | 3 ++- .../DeploymentBundles/DeploymentBundle.cs | 3 ++- .../AWS.Deploy.Constants.projitems | 1 + src/AWS.Deploy.Constants/Recipe.cs | 13 +++++++++++++ src/AWS.Deploy.Orchestration/Orchestrator.cs | 2 +- .../OrchestratorTests.cs | 4 +++- 6 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 src/AWS.Deploy.Constants/Recipe.cs diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/EnvironmentArchitectureCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/EnvironmentArchitectureCommand.cs index 71f97138a..e55a842e1 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/EnvironmentArchitectureCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/EnvironmentArchitectureCommand.cs @@ -31,7 +31,8 @@ public Task GetResources(Recommendation recommendation, O } }; - foreach (var value in recommendation.Recipe.SupportedArchitectures ?? new List { SupportedArchitecture.X86_64 }) + foreach (var value in recommendation.Recipe.SupportedArchitectures ?? + new List { Enum.Parse(Constants.Recipe.DefaultSupportedArchitecture) }) { var stringValue = value.ToString(); var row = new TypeHintResource(stringValue, stringValue); diff --git a/src/AWS.Deploy.Common/DeploymentBundles/DeploymentBundle.cs b/src/AWS.Deploy.Common/DeploymentBundles/DeploymentBundle.cs index 677fd7b2f..360511167 100644 --- a/src/AWS.Deploy.Common/DeploymentBundles/DeploymentBundle.cs +++ b/src/AWS.Deploy.Common/DeploymentBundles/DeploymentBundle.cs @@ -1,6 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +using System; using AWS.Deploy.Common.Recipes; namespace AWS.Deploy.Common @@ -69,6 +70,6 @@ public class DeploymentBundle /// /// The CPU architecture of the environment to create. /// - public SupportedArchitecture EnvironmentArchitecture { get; set; } = SupportedArchitecture.X86_64; + public SupportedArchitecture EnvironmentArchitecture { get; set; } = Enum.Parse(Constants.Recipe.DefaultSupportedArchitecture); } } diff --git a/src/AWS.Deploy.Constants/AWS.Deploy.Constants.projitems b/src/AWS.Deploy.Constants/AWS.Deploy.Constants.projitems index 7ea42a2bc..1860c5594 100644 --- a/src/AWS.Deploy.Constants/AWS.Deploy.Constants.projitems +++ b/src/AWS.Deploy.Constants/AWS.Deploy.Constants.projitems @@ -15,6 +15,7 @@ + \ No newline at end of file diff --git a/src/AWS.Deploy.Constants/Recipe.cs b/src/AWS.Deploy.Constants/Recipe.cs new file mode 100644 index 000000000..c1da5b7c7 --- /dev/null +++ b/src/AWS.Deploy.Constants/Recipe.cs @@ -0,0 +1,13 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r +// SPDX-License-Identifier: Apache-2.0 + +namespace AWS.Deploy.Constants +{ + internal class Recipe + { + /// + /// The default supported architecture + /// + public const string DefaultSupportedArchitecture = "X86_64"; + } +} diff --git a/src/AWS.Deploy.Orchestration/Orchestrator.cs b/src/AWS.Deploy.Orchestration/Orchestrator.cs index b20221d06..1c8245b63 100644 --- a/src/AWS.Deploy.Orchestration/Orchestrator.cs +++ b/src/AWS.Deploy.Orchestration/Orchestrator.cs @@ -246,7 +246,7 @@ public async Task ApplyAllReplacementTokens(Recommendation recommendation, strin } if (recommendation.ReplacementTokens.ContainsKey(Constants.RecipeIdentifier.REPLACE_TOKEN_DEFAULT_ENVIRONMENT_ARCHITECTURE)) { - recommendation.AddReplacementToken(Constants.RecipeIdentifier.REPLACE_TOKEN_DEFAULT_ENVIRONMENT_ARCHITECTURE, SupportedArchitecture.X86_64.ToString()); + recommendation.AddReplacementToken(Constants.RecipeIdentifier.REPLACE_TOKEN_DEFAULT_ENVIRONMENT_ARCHITECTURE, Constants.Recipe.DefaultSupportedArchitecture); } } diff --git a/test/AWS.Deploy.Orchestration.UnitTests/OrchestratorTests.cs b/test/AWS.Deploy.Orchestration.UnitTests/OrchestratorTests.cs index b1842ed84..67e5adfe8 100644 --- a/test/AWS.Deploy.Orchestration.UnitTests/OrchestratorTests.cs +++ b/test/AWS.Deploy.Orchestration.UnitTests/OrchestratorTests.cs @@ -66,6 +66,8 @@ public async Task ApplyAllReplacementTokensTest() recommendation.ReplacementTokens.Clear(); recommendation.ReplacementTokens.Add(Constants.RecipeIdentifier.REPLACE_TOKEN_DEFAULT_ENVIRONMENT_ARCHITECTURE, true); - await Assert.ThrowsAsync(async () => await orchestrator.ApplyAllReplacementTokens(recommendation, "WebAppNoDockerFile")); + await orchestrator.ApplyAllReplacementTokens(recommendation, "WebAppNoDockerFile"); + + Assert.Equal(Constants.Recipe.DefaultSupportedArchitecture, recommendation.ReplacementTokens[Constants.RecipeIdentifier.REPLACE_TOKEN_DEFAULT_ENVIRONMENT_ARCHITECTURE]); } } From 6adaba233f535d12df1e542067217cb18da3a5f8 Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Sat, 12 Oct 2024 23:51:11 -0400 Subject: [PATCH 4/5] feat: add support for Web App ARM deployments on ECS Fargate --- .../3bbbb972-6181-431c-8313-097ce38a2463.json | 11 +++ .../CdkAppSettingsSerializer.cs | 3 +- .../DeploymentBundleHandler.cs | 6 +- .../RecipeProps.cs | 10 +++ .../AspNetAppEcsFargate/Generated/Recipe.cs | 6 +- .../ASP.NETAppECSFargate.recipe | 4 +- .../WebAppWithDockerFileTests.cs | 49 +++++++++++++ .../DeploymentBundleHandlerTests.cs | 68 +++++++++++++++++-- 8 files changed, 146 insertions(+), 11 deletions(-) create mode 100644 .autover/changes/3bbbb972-6181-431c-8313-097ce38a2463.json diff --git a/.autover/changes/3bbbb972-6181-431c-8313-097ce38a2463.json b/.autover/changes/3bbbb972-6181-431c-8313-097ce38a2463.json new file mode 100644 index 000000000..f3b441712 --- /dev/null +++ b/.autover/changes/3bbbb972-6181-431c-8313-097ce38a2463.json @@ -0,0 +1,11 @@ +{ + "Projects": [ + { + "Name": "AWS.Deploy.CLI", + "Type": "Minor", + "ChangelogMessages": [ + "Add support for deploying ARM web apps to ECS Fargate" + ] + } + ] +} \ No newline at end of file diff --git a/src/AWS.Deploy.Orchestration/CdkAppSettingsSerializer.cs b/src/AWS.Deploy.Orchestration/CdkAppSettingsSerializer.cs index 98454fe3b..c8d8b642b 100644 --- a/src/AWS.Deploy.Orchestration/CdkAppSettingsSerializer.cs +++ b/src/AWS.Deploy.Orchestration/CdkAppSettingsSerializer.cs @@ -53,7 +53,8 @@ public string Build(CloudApplication cloudApplication, Recommendation recommenda ECRRepositoryName = recommendation.DeploymentBundle.ECRRepositoryName ?? "", ECRImageTag = recommendation.DeploymentBundle.ECRImageTag ?? "", DotnetPublishZipPath = recommendation.DeploymentBundle.DotnetPublishZipPath ?? "", - DotnetPublishOutputDirectory = recommendation.DeploymentBundle.DotnetPublishOutputDirectory ?? "" + DotnetPublishOutputDirectory = recommendation.DeploymentBundle.DotnetPublishOutputDirectory ?? "", + EnvironmentArchitecture = recommendation.DeploymentBundle.EnvironmentArchitecture.ToString() }; // Persist deployment bundle settings diff --git a/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs b/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs index 237548dec..d6c50edc7 100644 --- a/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs +++ b/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs @@ -74,9 +74,11 @@ public async Task BuildDockerImage(CloudApplication cloudApplication, Recommenda DockerUtilities.TryGetAbsoluteDockerfile(recommendation, _fileManager, _directoryManager, out var dockerFile); var dockerBuildCommand = $"docker build -t {imageTag} -f \"{dockerFile}\"{buildArgs} ."; - if (RuntimeInformation.OSArchitecture != Architecture.X64) + var currentArchitecture = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? SupportedArchitecture.Arm64 : SupportedArchitecture.X86_64; + var dockerPlatform = recommendation.DeploymentBundle.EnvironmentArchitecture == SupportedArchitecture.Arm64 ? "linux/arm64" : "linux/amd64"; + if (currentArchitecture != recommendation.DeploymentBundle.EnvironmentArchitecture) { - dockerBuildCommand = $"docker buildx build --platform linux/amd64 -t {imageTag} -f \"{dockerFile}\"{buildArgs} ."; + dockerBuildCommand = $"docker buildx build --platform {dockerPlatform} -t {imageTag} -f \"{dockerFile}\"{buildArgs} ."; } _interactiveService.LogInfoMessage($"Docker Execution Directory: {Path.GetFullPath(dockerExecutionDirectory)}"); diff --git a/src/AWS.Deploy.Recipes.CDK.Common/RecipeProps.cs b/src/AWS.Deploy.Recipes.CDK.Common/RecipeProps.cs index 76f4c0d39..112cdb9a2 100644 --- a/src/AWS.Deploy.Recipes.CDK.Common/RecipeProps.cs +++ b/src/AWS.Deploy.Recipes.CDK.Common/RecipeProps.cs @@ -41,6 +41,11 @@ public interface IRecipeProps /// string? DotnetPublishOutputDirectory { get; set; } + /// + /// The CPU architecture of the environment to create. + /// + string? EnvironmentArchitecture { get; set; } + /// /// The ID of the recipe being used to deploy the application. /// @@ -108,6 +113,11 @@ public class RecipeProps : IRecipeProps /// public string? DotnetPublishOutputDirectory { get; set; } + /// + /// The CPU architecture of the environment to create. + /// + public string? EnvironmentArchitecture { get; set; } + /// /// The ID of the recipe being used to deploy the application. /// diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Generated/Recipe.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Generated/Recipe.cs index a8c73000e..83f127d4a 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Generated/Recipe.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Generated/Recipe.cs @@ -135,7 +135,11 @@ private void ConfigureECSClusterAndService(IRecipeProps recipeCon { TaskRole = AppIAMTaskRole, Cpu = settings.TaskCpu, - MemoryLimitMiB = settings.TaskMemory + MemoryLimitMiB = settings.TaskMemory, + RuntimePlatform = new RuntimePlatform + { + CpuArchitecture = CpuArchitecture.Of(recipeConfiguration.EnvironmentArchitecture ?? string.Empty) + } })); AppLogging = new AwsLogDriver(InvokeCustomizeCDKPropsEvent(nameof(AppLogging), this, new AwsLogDriverProps diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe index 8c3482861..fc2972816 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe @@ -1,7 +1,7 @@ { "$schema": "./aws-deploy-recipe-schema.json", "Id": "AspNetAppEcsFargate", - "Version": "1.1.2", + "Version": "1.2.0", "Name": "ASP.NET Core App to Amazon ECS using AWS Fargate", "DeploymentType": "CdkProject", "DeploymentBundle": "Container", @@ -11,7 +11,7 @@ "Description": "This ASP.NET Core application will be deployed to Amazon Elastic Container Service (Amazon ECS) with compute power managed by AWS Fargate compute engine. If your project does not contain a Dockerfile, it will be automatically generated, otherwise an existing Dockerfile will be used. Recommended if you want to deploy your application as a container image on Linux.", "TargetService": "Amazon Elastic Container Service", "TargetPlatform": "Linux", - "SupportedArchitectures": [ "x86_64" ], + "SupportedArchitectures": [ "x86_64", "arm64" ], "DisplayedResources": [ { diff --git a/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs index 2464abbc9..5493101b5 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs @@ -253,6 +253,55 @@ public async Task AppRunnerDeployment() Assert.True(await _cloudFormationHelper.IsStackDeleted(_stackName)); } + [Fact] + public async Task FargateArmDeployment() + { + _stackName = $"FargateArmDeployment{Guid.NewGuid().ToString().Split('-').Last()}"; + + // Arrange input for deploy + await _interactiveService.StdInWriter.WriteAsync(Environment.NewLine); // Select default recommendation + await _interactiveService.StdInWriter.WriteLineAsync("8"); // Select "Environment Architecture" + await _interactiveService.StdInWriter.WriteLineAsync("2"); // Select "Arm64" + await _interactiveService.StdInWriter.WriteAsync(Environment.NewLine); // Confirm selection and deploy + await _interactiveService.StdInWriter.FlushAsync(); + + // Deploy + var projectPath = _testAppManager.GetProjectPath(Path.Combine("testapps", "WebAppWithDockerFile", "WebAppWithDockerFile.csproj")); + var deployArgs = new[] { "deploy", "--project-path", projectPath, "--application-name", _stackName, "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deployArgs)); + + // Verify application is deployed and running + Assert.Equal(StackStatus.CREATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName)); + + var deployStdOut = _interactiveService.StdOutReader.ReadAllLines(); + + var applicationUrl = deployStdOut.First(line => line.Trim().StartsWith("Endpoint:")) + .Split(" ")[1] + .Trim(); + + // URL could take few more minutes to come live, therefore, we want to wait and keep trying for a specified timeout + await _httpHelper.WaitUntilSuccessStatusCode(applicationUrl, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(5)); + + // list + var listArgs = new[] { "list-deployments", "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(listArgs));; + + // Verify stack exists in list of deployments + var listDeployStdOut = _interactiveService.StdOutReader.ReadAllLines().Select(x => x.Split()[0]).ToList(); + Assert.Contains(listDeployStdOut, (deployment) => _stackName.Equals(deployment)); + + // Arrange input for delete + await _interactiveService.StdInWriter.WriteAsync("y"); // Confirm delete + await _interactiveService.StdInWriter.FlushAsync(); + var deleteArgs = new[] { "delete-deployment", _stackName, "--diagnostics" }; + + // Delete + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deleteArgs));; + + // Verify application is deleted + Assert.True(await _cloudFormationHelper.IsStackDeleted(_stackName)); + } + public void Dispose() { Dispose(true); diff --git a/test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs b/test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs index d23c56328..4d894ef07 100644 --- a/test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs +++ b/test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs @@ -73,6 +73,64 @@ public DeploymentBundleHandlerTests() It.IsAny()).Object; } + [Fact] + public async Task BuildDockerImage_EnvironmentArchitectureNotSet() + { + var projectPath = SystemIOUtilities.ResolvePath("WebAppWithDockerFile"); + var project = await _projectDefinitionParser.Parse(projectPath); + var recommendation = new Recommendation(_recipeDefinition, project, 100, new Dictionary()); + + var cloudApplication = new CloudApplication("WebAppWithDockerFile", string.Empty, CloudApplicationResourceType.CloudFormationStack, recommendation.Recipe.Id); + var imageTag = "imageTag"; + var dockerFilePath = Path.GetFullPath(Path.Combine(".", "Dockerfile"), recommendation.GetProjectDirectory()); + + await _deploymentBundleHandler.BuildDockerImage(cloudApplication, recommendation, imageTag); + + // Explicitly checking for X64 because the default value for EnvironmentArchitecture is X86_64 + // This test will help catch a change in the default value + if (RuntimeInformation.OSArchitecture.Equals(Architecture.X64)) + { + Assert.Equal($"docker build -t {imageTag} -f \"{dockerFilePath}\" .", + _commandLineWrapper.CommandsToExecute.First().Command); + } + else + { + Assert.Equal($"docker buildx build --platform linux/amd64 -t {imageTag} -f \"{dockerFilePath}\" .", + _commandLineWrapper.CommandsToExecute.First().Command); + } + } + + [Theory] + [InlineData(SupportedArchitecture.X86_64)] + [InlineData(SupportedArchitecture.Arm64)] + public async Task BuildDockerImage_EnvironmentArchitectureIsSet(SupportedArchitecture environmentArchitecture) + { + var projectPath = SystemIOUtilities.ResolvePath("WebAppWithDockerFile"); + var project = await _projectDefinitionParser.Parse(projectPath); + var recommendation = new Recommendation(_recipeDefinition, project, 100, new Dictionary()); + + var cloudApplication = new CloudApplication("WebAppWithDockerFile", string.Empty, CloudApplicationResourceType.CloudFormationStack, recommendation.Recipe.Id); + var imageTag = "imageTag"; + var dockerFilePath = Path.GetFullPath(Path.Combine(".", "Dockerfile"), recommendation.GetProjectDirectory()); + + recommendation.DeploymentBundle.EnvironmentArchitecture = environmentArchitecture; + + await _deploymentBundleHandler.BuildDockerImage(cloudApplication, recommendation, imageTag); + + var currentArchitecture = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? SupportedArchitecture.Arm64 : SupportedArchitecture.X86_64; + if (currentArchitecture.Equals(environmentArchitecture)) + { + Assert.Equal($"docker build -t {imageTag} -f \"{dockerFilePath}\" .", + _commandLineWrapper.CommandsToExecute.First().Command); + } + else + { + var dockerPlatform = recommendation.DeploymentBundle.EnvironmentArchitecture == SupportedArchitecture.Arm64 ? "linux/arm64" : "linux/amd64"; + Assert.Equal($"docker buildx build --platform {dockerPlatform} -t {imageTag} -f \"{dockerFilePath}\" .", + _commandLineWrapper.CommandsToExecute.First().Command); + } + } + [Fact] public async Task BuildDockerImage_DockerExecutionDirectoryNotSet() { @@ -198,15 +256,15 @@ public async Task InspectDockerImage_ExecutedCommandCheck() await _deploymentBundleHandler.InspectDockerImageEnvironmentVariables(recommendation, "imageTag"); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Assert.Equal("docker inspect --format '{{ index (index .Config.Env) }}' imageTag", - _commandLineWrapper.CommandsToExecute.First().Command); + Assert.Equal("docker inspect --format \"{{ index (index .Config.Env) }}\" imageTag", + _commandLineWrapper.CommandsToExecute.First().Command); } else { - Assert.Equal("docker inspect --format \"{{ index (index .Config.Env) }}\" imageTag", - _commandLineWrapper.CommandsToExecute.First().Command); + Assert.Equal("docker inspect --format '{{ index (index .Config.Env) }}' imageTag", + _commandLineWrapper.CommandsToExecute.First().Command); } } From 628fb391ba7570b37b4032ea8fe25206fc0ee43f Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Sun, 13 Oct 2024 14:48:36 -0400 Subject: [PATCH 5/5] feat: add support for deploying ARM console apps to ECS Fargate --- .../a2dd7cca-b2c5-47e1-8bce-34eb5239a047.json | 11 ++++ .../Generated/Recipe.cs | 6 +- .../Generated/Recipe.cs | 6 +- .../ConsoleAppECSFargateScheduleTask.recipe | 4 +- .../ConsoleAppECSFargateService.recipe | 4 +- .../ConsoleAppTests.cs | 64 +++++++++++++++++++ 6 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 .autover/changes/a2dd7cca-b2c5-47e1-8bce-34eb5239a047.json diff --git a/.autover/changes/a2dd7cca-b2c5-47e1-8bce-34eb5239a047.json b/.autover/changes/a2dd7cca-b2c5-47e1-8bce-34eb5239a047.json new file mode 100644 index 000000000..97fb9500c --- /dev/null +++ b/.autover/changes/a2dd7cca-b2c5-47e1-8bce-34eb5239a047.json @@ -0,0 +1,11 @@ +{ + "Projects": [ + { + "Name": "AWS.Deploy.CLI", + "Type": "Minor", + "ChangelogMessages": [ + "Add support for deploying ARM console apps to ECS Fargate" + ] + } + ] +} \ No newline at end of file diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Generated/Recipe.cs b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Generated/Recipe.cs index 5e36729ec..f7a694df2 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Generated/Recipe.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Generated/Recipe.cs @@ -110,7 +110,11 @@ private void ConfigureTaskDefinition(IRecipeProps props) { TaskRole = AppIAMTaskRole, Cpu = settings.TaskCpu, - MemoryLimitMiB = settings.TaskMemory + MemoryLimitMiB = settings.TaskMemory, + RuntimePlatform = new RuntimePlatform + { + CpuArchitecture = CpuArchitecture.Of(props.EnvironmentArchitecture ?? string.Empty) + } })); AppLogging = new AwsLogDriver(InvokeCustomizeCDKPropsEvent(nameof(AppLogging), this, new AwsLogDriverProps diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Generated/Recipe.cs b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Generated/Recipe.cs index 047a1dab6..1fdcf446a 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Generated/Recipe.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Generated/Recipe.cs @@ -117,7 +117,11 @@ private void ConfigureTaskDefinition(IRecipeProps props) { TaskRole = AppIAMTaskRole, Cpu = settings.TaskCpu, - MemoryLimitMiB = settings.TaskMemory + MemoryLimitMiB = settings.TaskMemory, + RuntimePlatform = new RuntimePlatform + { + CpuArchitecture = CpuArchitecture.Of(props.EnvironmentArchitecture ?? string.Empty) + } })); AppLogging = new AwsLogDriver(InvokeCustomizeCDKPropsEvent(nameof(AppLogging), this, new AwsLogDriverProps diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe index b4a027f89..cc2689685 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe @@ -1,7 +1,7 @@ { "$schema": "./aws-deploy-recipe-schema.json", "Id": "ConsoleAppEcsFargateScheduleTask", - "Version": "1.0.3", + "Version": "1.1.0", "Name": "Scheduled Task on Amazon Elastic Container Service (ECS) using AWS Fargate", "DeploymentType": "CdkProject", "DeploymentBundle": "Container", @@ -11,7 +11,7 @@ "ShortDescription": "Deploys a scheduled task as a Linux container image to a fully managed container orchestration service. Dockerfile will be automatically generated if needed.", "TargetService": "Amazon Elastic Container Service", "TargetPlatform": "Linux", - "SupportedArchitectures": [ "x86_64" ], + "SupportedArchitectures": [ "x86_64", "arm64" ], "DisplayedResources": [ { diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe index 6501e246a..a46fa825e 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe @@ -1,7 +1,7 @@ { "$schema": "./aws-deploy-recipe-schema.json", "Id": "ConsoleAppEcsFargateService", - "Version": "1.0.3", + "Version": "1.1.0", "Name": "Service on Amazon Elastic Container Service (ECS) using AWS Fargate", "DeploymentType": "CdkProject", "DeploymentBundle": "Container", @@ -11,7 +11,7 @@ "ShortDescription": "Deploys a service as a Linux container image to a fully managed container orchestration service. Dockerfile will be automatically generated if needed.", "TargetService": "Amazon Elastic Container Service", "TargetPlatform": "Linux", - "SupportedArchitectures": [ "x86_64" ], + "SupportedArchitectures": [ "x86_64", "arm64" ], "DisplayedResources": [ { diff --git a/test/AWS.Deploy.CLI.IntegrationTests/ConsoleAppTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/ConsoleAppTests.cs index 890860915..3a9aec60f 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/ConsoleAppTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/ConsoleAppTests.cs @@ -118,6 +118,70 @@ public async Task DefaultConfigurations(params string[] components) Assert.True(await _cloudFormationHelper.IsStackDeleted(_stackName), $"{_stackName} still exists."); } + [Theory] + [InlineData("testapps", "ConsoleAppService", "ConsoleAppService.csproj")] + [InlineData("testapps", "ConsoleAppTask", "ConsoleAppTask.csproj")] + public async Task FargateArmDeployment(params string[] components) + { + _stackName = $"{components[1]}Arm{Guid.NewGuid().ToString().Split('-').Last()}"; + + // Arrange input for deploy + await _interactiveService.StdInWriter.WriteAsync(Environment.NewLine); // Select default recommendation + await _interactiveService.StdInWriter.WriteLineAsync("8"); // Select "Environment Architecture" + await _interactiveService.StdInWriter.WriteLineAsync("2"); // Select "Arm64" + await _interactiveService.StdInWriter.WriteAsync(Environment.NewLine); // Confirm selection and deploy + await _interactiveService.StdInWriter.FlushAsync(); + + // Deploy + var deployArgs = new[] { "deploy", "--project-path", _testAppManager.GetProjectPath(Path.Combine(components)), "--application-name", _stackName, "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deployArgs)); + + // Verify application is deployed and running + Assert.Equal(StackStatus.CREATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName)); + + var cluster = await _ecsHelper.GetCluster(_stackName); + Assert.Equal("ACTIVE", cluster.Status); + + // Verify CloudWatch logs + var logGroup = await _ecsHelper.GetLogGroup(_stackName); + var logMessages = await _cloudWatchLogsHelper.GetLogMessages(logGroup); + Assert.Contains("Hello World!", logMessages); + + var deployStdOut = _interactiveService.StdOutReader.ReadAllLines(); + + var tempCdkProjectLine = deployStdOut.First(line => line.StartsWith("Saving AWS CDK deployment project to: ")); + var tempCdkProject = tempCdkProjectLine.Split(": ")[1].Trim(); + Assert.False(Directory.Exists(tempCdkProject), $"{tempCdkProject} must not exist."); + + // list + var listArgs = new[] { "list-deployments", "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(listArgs));; + + // Verify stack exists in list of deployments + var listStdOut = _interactiveService.StdOutReader.ReadAllLines().Select(x => x.Split()[0]).ToList(); + Assert.Contains(listStdOut, (deployment) => _stackName.Equals(deployment)); + + // Arrange input for re-deployment + await _interactiveService.StdInWriter.WriteAsync(Environment.NewLine); // Select default option settings + await _interactiveService.StdInWriter.FlushAsync(); + + // Perform re-deployment + deployArgs = new[] { "deploy", "--project-path", _testAppManager.GetProjectPath(Path.Combine(components)), "--application-name", _stackName, "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deployArgs)); + Assert.Equal(StackStatus.UPDATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName)); + + // Arrange input for delete + await _interactiveService.StdInWriter.WriteAsync("y"); // Confirm delete + await _interactiveService.StdInWriter.FlushAsync(); + var deleteArgs = new[] { "delete-deployment", _stackName, "--diagnostics" }; + + // Delete + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deleteArgs));; + + // Verify application is deleted + Assert.True(await _cloudFormationHelper.IsStackDeleted(_stackName), $"{_stackName} still exists."); + } + public void Dispose() { Dispose(true);