From ca7d45d79b6830cbc1b955631aa5e82504eb5e2c Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Fri, 19 Jul 2024 15:40:34 -0400 Subject: [PATCH] fix: remove auto self-contained build for .NET 8 beanstalk deployments --- .../DotnetBeanstalkPlatformArnCommand.cs | 6 +- ...otnetWindowsBeanstalkPlatformArnCommand.cs | 6 +- .../Services/SessionAWSResourceQuery.cs | 8 +- .../Data/IAWSResourceQueryer.cs | 4 +- src/AWS.Deploy.Common/Exceptions.cs | 1 + .../CDK/CDKBootstrapTemplate.yaml | 6 +- .../Data/AWSResourceQueryer.cs | 92 +++++++++++++++++-- .../DeploymentBundleHandler.cs | 75 ++++++++++++--- src/AWS.Deploy.Orchestration/Exceptions.cs | 10 +- src/AWS.Deploy.Orchestration/Orchestrator.cs | 10 +- .../Utilities/DeployedApplicationQueryer.cs | 2 +- .../WindowsTestContextFixture.cs | 2 +- .../TestContextFixture.cs | 2 +- .../Helpers/ElasticBeanstalkHelper.cs | 4 +- .../Utilities/TestToolAWSResourceQueryer.cs | 4 +- .../DeploymentBundleHandlerTests.cs | 28 +++++- .../Utilities/TestToolAWSResourceQueryer.cs | 4 +- .../AWSResourceQueryerTests.cs | 79 ++++++++++++++++ .../DeployedApplicationQueryerTests.cs | 6 +- 19 files changed, 296 insertions(+), 53 deletions(-) diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetBeanstalkPlatformArnCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetBeanstalkPlatformArnCommand.cs index 598c373bc..d6cae52c9 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetBeanstalkPlatformArnCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetBeanstalkPlatformArnCommand.cs @@ -28,14 +28,14 @@ public DotnetBeanstalkPlatformArnCommand(IAWSResourceQueryer awsResourceQueryer, _optionSettingHandler = optionSettingHandler; } - private async Task> GetData() + private async Task> GetData(Recommendation recommendation) { - return await _awsResourceQueryer.GetElasticBeanstalkPlatformArns(BeanstalkPlatformType.Linux); + return await _awsResourceQueryer.GetElasticBeanstalkPlatformArns(recommendation.ProjectDefinition.TargetFramework, BeanstalkPlatformType.Linux); } public async Task GetResources(Recommendation recommendation, OptionSettingItem optionSetting) { - var platformArns = await GetData(); + var platformArns = await GetData(recommendation); var resourceTable = new TypeHintResourceTable { diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetWindowsBeanstalkPlatformArnCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetWindowsBeanstalkPlatformArnCommand.cs index 71a672b6e..6d2f2c6af 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetWindowsBeanstalkPlatformArnCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetWindowsBeanstalkPlatformArnCommand.cs @@ -28,14 +28,14 @@ public DotnetWindowsBeanstalkPlatformArnCommand(IAWSResourceQueryer awsResourceQ _optionSettingHandler = optionSettingHandler; } - private async Task> GetData() + private async Task> GetData(Recommendation recommendation) { - return await _awsResourceQueryer.GetElasticBeanstalkPlatformArns(BeanstalkPlatformType.Windows); + return await _awsResourceQueryer.GetElasticBeanstalkPlatformArns(recommendation.ProjectDefinition.TargetFramework, BeanstalkPlatformType.Windows); } public async Task GetResources(Recommendation recommendation, OptionSettingItem optionSetting) { - var platformArns = await GetData(); + var platformArns = await GetData(recommendation); var resourceTable = new TypeHintResourceTable { diff --git a/src/AWS.Deploy.CLI/ServerMode/Services/SessionAWSResourceQuery.cs b/src/AWS.Deploy.CLI/ServerMode/Services/SessionAWSResourceQuery.cs index c2f27853d..1e0fbe638 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Services/SessionAWSResourceQuery.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Services/SessionAWSResourceQuery.cs @@ -256,15 +256,15 @@ public async Task> GetECRRepositories(List? repositoryN } /// - public async Task> GetElasticBeanstalkPlatformArns(params BeanstalkPlatformType[]? platformTypes) + public async Task> GetElasticBeanstalkPlatformArns(string? targetFramework, params BeanstalkPlatformType[]? platformTypes) { - return (await GetAndCache(async () => await _awsResourceQueryer.GetElasticBeanstalkPlatformArns(platformTypes), new object?[] { platformTypes }))!; + return (await GetAndCache(async () => await _awsResourceQueryer.GetElasticBeanstalkPlatformArns(targetFramework, platformTypes), new object?[] { platformTypes }))!; } /// - public async Task GetLatestElasticBeanstalkPlatformArn(BeanstalkPlatformType platformType) + public async Task GetLatestElasticBeanstalkPlatformArn(string? targetFramework, BeanstalkPlatformType platformType) { - return (await GetAndCache(async () => await _awsResourceQueryer.GetLatestElasticBeanstalkPlatformArn(platformType), new object[] { platformType }))!; + return (await GetAndCache(async () => await _awsResourceQueryer.GetLatestElasticBeanstalkPlatformArn(targetFramework, platformType), new object[] { platformType }))!; } /// diff --git a/src/AWS.Deploy.Common/Data/IAWSResourceQueryer.cs b/src/AWS.Deploy.Common/Data/IAWSResourceQueryer.cs index a51d009b5..43080bdf0 100644 --- a/src/AWS.Deploy.Common/Data/IAWSResourceQueryer.cs +++ b/src/AWS.Deploy.Common/Data/IAWSResourceQueryer.cs @@ -69,8 +69,8 @@ public interface IAWSResourceQueryer Task CreateEC2KeyPair(string keyName, string saveLocation); Task> ListOfIAMRoles(string? servicePrincipal); Task> GetListOfVpcs(); - Task> GetElasticBeanstalkPlatformArns(params BeanstalkPlatformType[]? platformTypes); - Task GetLatestElasticBeanstalkPlatformArn(BeanstalkPlatformType platformType); + Task> GetElasticBeanstalkPlatformArns(string? targetFramework, params BeanstalkPlatformType[]? platformTypes); + Task GetLatestElasticBeanstalkPlatformArn(string? targetFramework, BeanstalkPlatformType platformType); Task> GetECRAuthorizationToken(); Task> GetECRRepositories(List? repositoryNames = null); diff --git a/src/AWS.Deploy.Common/Exceptions.cs b/src/AWS.Deploy.Common/Exceptions.cs index 2cd2a6c06..f6992b17d 100644 --- a/src/AWS.Deploy.Common/Exceptions.cs +++ b/src/AWS.Deploy.Common/Exceptions.cs @@ -129,6 +129,7 @@ public enum DeployToolErrorCode InvalidWindowsManifestFile = 10010700, UserDeploymentFileNotFound = 10010800, DockerInspectFailed = 10004200, + InvalidElasticBeanstalkPlatform = 10010900 } public class ProjectFileNotFoundException : DeployToolException diff --git a/src/AWS.Deploy.Orchestration/CDK/CDKBootstrapTemplate.yaml b/src/AWS.Deploy.Orchestration/CDK/CDKBootstrapTemplate.yaml index 1fcabf9e1..2ba952fcf 100644 --- a/src/AWS.Deploy.Orchestration/CDK/CDKBootstrapTemplate.yaml +++ b/src/AWS.Deploy.Orchestration/CDK/CDKBootstrapTemplate.yaml @@ -370,6 +370,10 @@ Resources: Resource: - Fn::Sub: ${StagingBucket.Arn} - Fn::Sub: ${StagingBucket.Arn}/* + Condition: + StringEquals: + aws:ResourceAccount: + - Fn::Sub: ${AWS::AccountId} Effect: Allow - Action: - kms:Decrypt @@ -585,7 +589,7 @@ Resources: Type: String Name: Fn::Sub: /cdk-bootstrap/${Qualifier}/version - Value: "20" + Value: "21" Outputs: BucketName: Description: The name of the S3 bucket owned by the CDK toolkit stack diff --git a/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs b/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs index 01c6628ec..ea9fdf6dc 100644 --- a/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs +++ b/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs @@ -516,7 +516,7 @@ public async Task> GetListOfVpcs() "Error attempting to retrieve the default VPC"); } - public async Task> GetElasticBeanstalkPlatformArns(params BeanstalkPlatformType[]? platformTypes) + public async Task> GetElasticBeanstalkPlatformArns(string? targetFramework, params BeanstalkPlatformType[]? platformTypes) { if(platformTypes == null || platformTypes.Length == 0) { @@ -557,7 +557,9 @@ public async Task> GetElasticBeanstalkPlatformArns(params var allPlatformSummaries = new List(); if (platformTypes.Contains(BeanstalkPlatformType.Linux)) { - allPlatformSummaries.AddRange(await fetchPlatforms(Constants.ElasticBeanstalk.LinuxPlatformType)); + var linuxPlatforms = await fetchPlatforms(Constants.ElasticBeanstalk.LinuxPlatformType); + linuxPlatforms = SortElasticBeanstalkLinuxPlatforms(targetFramework, linuxPlatforms); + allPlatformSummaries.AddRange(linuxPlatforms); } if (platformTypes.Contains(BeanstalkPlatformType.Windows)) { @@ -581,9 +583,9 @@ public async Task> GetElasticBeanstalkPlatformArns(params return platformVersions; } - public async Task GetLatestElasticBeanstalkPlatformArn(BeanstalkPlatformType platformType) + public async Task GetLatestElasticBeanstalkPlatformArn(string? targetFramework, BeanstalkPlatformType platformType) { - var platforms = await GetElasticBeanstalkPlatformArns(platformType); + var platforms = await GetElasticBeanstalkPlatformArns(targetFramework, platformType); if (!platforms.Any()) { @@ -596,12 +598,90 @@ public async Task GetLatestElasticBeanstalkPlatformArn(Beanstal return platforms.First(); } + /// + /// For Linux beanstalk platforms the describe calls return a collection of .NET x and .NET Core based platforms. + /// The order returned will be sorted by .NET version in increasing order then by platform versions. So for example we could get a result like the following + /// + /// .NET 6 running on 64bit Amazon Linux 2023 v3.1.3 + /// .NET 6 running on 64bit Amazon Linux 2023 v3.1.2 + /// .NET 6 running on 64bit Amazon Linux 2023 v3.0.6 + /// .NET 6 running on 64bit Amazon Linux 2023 v3.0.5 + /// .NET 8 running on 64bit Amazon Linux 2023 v3.1.3 + /// .NET Core running on 64bit Amazon Linux 2 v2.8.0 + /// .NET Core running on 64bit Amazon Linux 2 v2.7.3 + /// .NET Core running on 64bit Amazon Linux 2 v2.6.0 + /// + /// We want the user to see the .NET version corresponding to their application on the latest Beanstalk platform first. + /// If the user is trying to deploy a .NET 6 application, the above example will be sorted into the following. + /// + /// .NET 6 running on 64bit Amazon Linux 2023 v3.1.3 + /// .NET 8 running on 64bit Amazon Linux 2023 v3.1.3 + /// .NET 6 running on 64bit Amazon Linux 2023 v3.1.2 + /// .NET 6 running on 64bit Amazon Linux 2023 v3.0.6 + /// .NET 6 running on 64bit Amazon Linux 2023 v3.0.5 + /// .NET Core running on 64bit Amazon Linux 2 v2.8.0 + /// .NET Core running on 64bit Amazon Linux 2 v2.7.3 + /// .NET Core running on 64bit Amazon Linux 2 v2.6.0 + /// + /// In case the target framework is not known in advance, the platforms will be sorted by Beanstalk Platform followed by the .NET version, with .NET Core coming in last. + /// The above example will be sorted into the following. + /// + /// .NET 8 running on 64bit Amazon Linux 2023 v3.1.3 + /// .NET 6 running on 64bit Amazon Linux 2023 v3.1.3 + /// .NET 6 running on 64bit Amazon Linux 2023 v3.1.2 + /// .NET 6 running on 64bit Amazon Linux 2023 v3.0.6 + /// .NET 6 running on 64bit Amazon Linux 2023 v3.0.5 + /// .NET Core running on 64bit Amazon Linux 2 v2.8.0 + /// .NET Core running on 64bit Amazon Linux 2 v2.7.3 + /// .NET Core running on 64bit Amazon Linux 2 v2.6.0 + /// + /// + /// + public static List SortElasticBeanstalkLinuxPlatforms(string? targetFramework, List platforms) + { + var dotnetVersionMap = new System.Collections.Generic.Dictionary(); + foreach (var platform in platforms) + { + var runningIndexOf = platform.PlatformBranchName.IndexOf("running", StringComparison.InvariantCultureIgnoreCase); + if (runningIndexOf == -1) + { + dotnetVersionMap[platform.PlatformArn] = 0; + continue; + } + + var framework = platform.PlatformBranchName.Substring(0, runningIndexOf).Trim(); + var frameworkSplit = framework.Split(" "); + if (frameworkSplit.Length != 2) + { + dotnetVersionMap[platform.PlatformArn] = 0; + continue; + } + if (!decimal.TryParse(frameworkSplit[1], out var dotnetVersion)) + { + dotnetVersionMap[platform.PlatformArn] = 0; + continue; + } + + if (decimal.TryParse(targetFramework?.Replace("net", ""), out var currentTargetFramework)) + { + if (currentTargetFramework.Equals(dotnetVersion)) + { + dotnetVersionMap[platform.PlatformArn] = 1000; + continue; + } + } + + dotnetVersionMap[platform.PlatformArn] = dotnetVersion; + } + + return platforms.OrderByDescending(x => new Version(x.PlatformVersion)).ThenByDescending(x => dotnetVersionMap[x.PlatformArn]).ToList(); + } /// /// For Windows beanstalk platforms the describe calls return a collection of Windows Server Code and Windows Server based platforms. /// The order return will be sorted by platform versions but not OS. So for example we could get a result like the following - /// + /// /// IIS 10.0 running on 64bit Windows Server 2016 (1.1.0) /// IIS 10.0 running on 64bit Windows Server 2016 (1.0.0) /// IIS 10.0 running on 64bit Windows Server Core 2016 (1.1.0) @@ -613,7 +693,7 @@ public async Task GetLatestElasticBeanstalkPlatformArn(Beanstal /// /// We want the user to use the latest version of each OS first as well as the latest version of Windows first. Also Windows Server should come before Windows Server Core. /// This matches the behavior of the existing VS toolkit picker. The above example will be sorted into the following. - /// + /// /// IIS 10.0 running on 64bit Windows Server 2019 (1.1.0) /// IIS 10.0 running on 64bit Windows Server Core 2019 (1.1.0) /// IIS 10.0 running on 64bit Windows Server 2016 (1.1.0) diff --git a/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs b/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs index 56c6fdaf8..5bb56870e 100644 --- a/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs +++ b/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -43,6 +44,7 @@ public class DeploymentBundleHandler : IDeploymentBundleHandler private readonly IDirectoryManager _directoryManager; private readonly IZipFileManager _zipFileManager; private readonly IFileManager _fileManager; + private readonly IOptionSettingHandler _optionSettingHandler; public DeploymentBundleHandler( ICommandLineWrapper commandLineWrapper, @@ -50,7 +52,8 @@ public DeploymentBundleHandler( IOrchestratorInteractiveService interactiveService, IDirectoryManager directoryManager, IZipFileManager zipFileManager, - IFileManager fileManager) + IFileManager fileManager, + IOptionSettingHandler optionSettingHandler) { _commandLineWrapper = commandLineWrapper; _awsResourceQueryer = awsResourceQueryer; @@ -58,6 +61,7 @@ public DeploymentBundleHandler( _directoryManager = directoryManager; _zipFileManager = zipFileManager; _fileManager = fileManager; + _optionSettingHandler = optionSettingHandler; } public async Task BuildDockerImage(CloudApplication cloudApplication, Recommendation recommendation, string imageTag) @@ -108,21 +112,70 @@ public async Task PushDockerImageToECR(Recommendation recommendation, string rep recommendation.DeploymentBundle.ECRImageTag = tagSuffix; } + /// + /// The supported .NET versions on Elastic Beanstalk are dependent on the available platform versions. + /// These versions do not always have the required .NET runtimes installed so we need to perform extra checks + /// and perform a self-contained publish when creating the deployment bundle if needed. + /// + private void SwitchToSelfContainedBuildIfNeeded(Recommendation recommendation) + { + if (recommendation.Recipe.TargetService == RecipeIdentifier.TARGET_SERVICE_ELASTIC_BEANSTALK) + { + var targetFramework = recommendation.ProjectDefinition.TargetFramework ?? string.Empty; + + // Elastic Beanstalk doesn't currently have .NET 7 preinstalled. + var unavailableFramework = new List { "net7.0" }; + var frameworkNames = new Dictionary { { "net7.0", ".NET 7" } }; + if (unavailableFramework.Contains(targetFramework)) + { + _interactiveService.LogInfoMessage($"Using self-contained publish since AWS Elastic Beanstalk does not currently have {frameworkNames[targetFramework]} preinstalled"); + recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild = true; + return; + } + + var beanstalkPlatformSetting = recommendation.Recipe.OptionSettings.FirstOrDefault(x => x.Id.Equals("ElasticBeanstalkPlatformArn")); + if (beanstalkPlatformSetting != null) + { + var beanstalkPlatformSettingValue = _optionSettingHandler.GetOptionSettingValue(recommendation, beanstalkPlatformSetting); + var beanstalkPlatformSettingValueSplit = beanstalkPlatformSettingValue?.Split("/"); + if (beanstalkPlatformSettingValueSplit?.Length != 3) + throw new InvalidElasticBeanstalkPlatformException(DeployToolErrorCode.InvalidElasticBeanstalkPlatform, $"The selected Elastic Beanstalk platform version '{beanstalkPlatformSettingValue}' is invalid."); + var beanstalkPlatformName = beanstalkPlatformSettingValueSplit[1]; + if (!Version.TryParse(beanstalkPlatformSettingValueSplit[2], out var beanstalkPlatformVersion)) + throw new InvalidElasticBeanstalkPlatformException(DeployToolErrorCode.InvalidElasticBeanstalkPlatform, $"The selected Elastic Beanstalk platform version '{beanstalkPlatformSettingValue}' is invalid."); + + // Elastic Beanstalk recently added .NET8 support in + // platform '.NET 8 on AL2023 version 3.1.1' and '.NET Core on AL2 version 2.8.0'. + // If users are using platform versions other than the above or older than '2.8.0' for '.NET Core' + // we need to perform a self-contained publish. + if (targetFramework.Equals("net8.0")) + { + if (beanstalkPlatformName.Contains(".NET Core")) + { + if (beanstalkPlatformVersion < new Version(2, 8, 0)) + { + _interactiveService.LogInfoMessage($"Using self-contained publish since AWS Elastic Beanstalk does not currently have .NET 8 preinstalled on {beanstalkPlatformName} ({beanstalkPlatformVersion.ToString()})"); + recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild = true; + return; + } + } + else if (!beanstalkPlatformName.Contains(".NET 8")) + { + _interactiveService.LogInfoMessage($"Using self-contained publish since AWS Elastic Beanstalk does not currently have .NET 8 preinstalled on {beanstalkPlatformName} ({beanstalkPlatformVersion.ToString()})"); + recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild = true; + return; + } + } + } + } + } + public async Task CreateDotnetPublishZip(Recommendation recommendation) { _interactiveService.LogInfoMessage(string.Empty); _interactiveService.LogInfoMessage("Creating Dotnet Publish Zip file..."); - // Since Beanstalk doesn't currently have .NET 7 and .NET 8 preinstalled we need to make sure we are doing a self-contained publish when creating the deployment bundle. - var targetFramework = recommendation.ProjectDefinition.TargetFramework ?? string.Empty; - var unavailableFramework = new List { "net7.0", "net8.0" }; - var frameworkNames = new Dictionary { { "net7.0", ".NET 7" }, { "net8.0", ".NET 8" } }; - if (recommendation.Recipe.TargetService == RecipeIdentifier.TARGET_SERVICE_ELASTIC_BEANSTALK && - unavailableFramework.Contains(targetFramework)) - { - _interactiveService.LogInfoMessage($"Using self-contained publish since AWS Elastic Beanstalk does not currently have {frameworkNames[targetFramework]} preinstalled"); - recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild = true; - } + SwitchToSelfContainedBuildIfNeeded(recommendation); var publishDirectoryInfo = _directoryManager.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); var additionalArguments = recommendation.DeploymentBundle.DotnetPublishAdditionalBuildArguments; diff --git a/src/AWS.Deploy.Orchestration/Exceptions.cs b/src/AWS.Deploy.Orchestration/Exceptions.cs index d24af3321..dd13a9ff6 100644 --- a/src/AWS.Deploy.Orchestration/Exceptions.cs +++ b/src/AWS.Deploy.Orchestration/Exceptions.cs @@ -236,7 +236,7 @@ public class ElasticBeanstalkException : DeployToolException { public ElasticBeanstalkException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { } } - + /// /// Throw if unable to access the specified AWS Region. /// @@ -276,4 +276,12 @@ public class InvalidWindowsManifestFileException : DeployToolException { public InvalidWindowsManifestFileException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { } } + + /// + /// Throw if the deploy tool encounters an invalid Elastic Beanstalk platform version. + /// + public class InvalidElasticBeanstalkPlatformException : DeployToolException + { + public InvalidElasticBeanstalkPlatformException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { } + } } diff --git a/src/AWS.Deploy.Orchestration/Orchestrator.cs b/src/AWS.Deploy.Orchestration/Orchestrator.cs index 99903268e..4f2e40961 100644 --- a/src/AWS.Deploy.Orchestration/Orchestrator.cs +++ b/src/AWS.Deploy.Orchestration/Orchestrator.cs @@ -94,7 +94,7 @@ public async Task> GenerateDeploymentRecommendations() throw new InvalidOperationException($"{nameof(_session)} is null as part of the orchestartor object"); if (_recipeHandler == null) throw new InvalidOperationException($"{nameof(_recipeHandler)} is null as part of the orchestartor object"); - + var engine = new RecommendationEngine.RecommendationEngine(_session, _recipeHandler); var recipePaths = new HashSet { RecipeLocator.FindRecipeDefinitionsPath() }; var customRecipePaths = await _recipeHandler.LocateCustomRecipePaths(_session.ProjectDefinition); @@ -112,7 +112,7 @@ public async Task> GenerateRecommendationsToSaveDeploymentP throw new InvalidOperationException($"{nameof(_session)} is null as part of the orchestartor object"); if (_recipeHandler == null) throw new InvalidOperationException($"{nameof(_recipeHandler)} is null as part of the orchestartor object"); - + var engine = new RecommendationEngine.RecommendationEngine(_session, _recipeHandler); var compatibleRecommendations = await engine.ComputeRecommendations(); var cdkRecommendations = compatibleRecommendations.Where(x => x.Recipe.DeploymentType == DeploymentTypes.CdkProject).ToList(); @@ -180,7 +180,7 @@ public async Task ApplyAllReplacementTokens(Recommendation recommendation, strin if (_awsResourceQueryer == null) throw new InvalidOperationException($"{nameof(_awsResourceQueryer)} is null as part of the Orchestrator object"); - var latestPlatform = await _awsResourceQueryer.GetLatestElasticBeanstalkPlatformArn(BeanstalkPlatformType.Linux); + var latestPlatform = await _awsResourceQueryer.GetLatestElasticBeanstalkPlatformArn(recommendation.ProjectDefinition.TargetFramework, BeanstalkPlatformType.Linux); recommendation.AddReplacementToken(Constants.RecipeIdentifier.REPLACE_TOKEN_LATEST_DOTNET_BEANSTALK_PLATFORM_ARN, latestPlatform.PlatformArn); } if (recommendation.ReplacementTokens.ContainsKey(Constants.RecipeIdentifier.REPLACE_TOKEN_LATEST_DOTNET_WINDOWS_BEANSTALK_PLATFORM_ARN)) @@ -188,7 +188,7 @@ public async Task ApplyAllReplacementTokens(Recommendation recommendation, strin if (_awsResourceQueryer == null) throw new InvalidOperationException($"{nameof(_awsResourceQueryer)} is null as part of the Orchestrator object"); - var latestPlatform = await _awsResourceQueryer.GetLatestElasticBeanstalkPlatformArn(BeanstalkPlatformType.Windows); + var latestPlatform = await _awsResourceQueryer.GetLatestElasticBeanstalkPlatformArn(recommendation.ProjectDefinition.TargetFramework, BeanstalkPlatformType.Windows); recommendation.AddReplacementToken(Constants.RecipeIdentifier.REPLACE_TOKEN_LATEST_DOTNET_WINDOWS_BEANSTALK_PLATFORM_ARN, latestPlatform.PlatformArn); } if (recommendation.ReplacementTokens.ContainsKey(Constants.RecipeIdentifier.REPLACE_TOKEN_STACK_NAME)) @@ -315,7 +315,7 @@ private async Task CreateContainerDeploymentBundle(CloudApplication cloudApplica _dockerEngine.DetermineDockerExecutionDirectory(recommendation); // Read this from the OptionSetting instead of recommendation.DeploymentBundle. - // When its value comes from a replacement token, it wouldn't have been set back to the DeploymentBundle + // When its value comes from a replacement token, it wouldn't have been set back to the DeploymentBundle var respositoryName = _optionSettingHandler.GetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, Constants.Docker.ECRRepositoryNameOptionId)); if (respositoryName == null) throw new InvalidECRRepositoryNameException(DeployToolErrorCode.ECRRepositoryNameIsNull, "The ECR Repository Name is null."); diff --git a/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs b/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs index c938ccb5b..84c50cb8c 100644 --- a/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs +++ b/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs @@ -218,7 +218,7 @@ private async Task> GetExistingBeanstalkEnvironments() if (!environments.Any()) return validEnvironments; - var dotnetPlatforms = await _awsResourceQueryer.GetElasticBeanstalkPlatformArns(); + var dotnetPlatforms = await _awsResourceQueryer.GetElasticBeanstalkPlatformArns(string.Empty); var dotnetPlatformArns = dotnetPlatforms.Select(x => x.PlatformArn).ToList(); // only select environments that have a dotnet specific platform ARN. diff --git a/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/ExistingWindowsEnvironment/WindowsTestContextFixture.cs b/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/ExistingWindowsEnvironment/WindowsTestContextFixture.cs index 1bbe40d35..ad5d954a7 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/ExistingWindowsEnvironment/WindowsTestContextFixture.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/ExistingWindowsEnvironment/WindowsTestContextFixture.cs @@ -122,7 +122,7 @@ public async Task InitializeAsync() await EBHelper.CreateApplicationAsync(ApplicationName); await EBHelper.CreateApplicationVersionAsync(ApplicationName, VersionLabel, zipFilePath); - var success = await EBHelper.CreateEnvironmentAsync(ApplicationName, EnvironmentName, VersionLabel, BeanstalkPlatformType.Windows, RoleName); + var success = await EBHelper.CreateEnvironmentAsync(ApplicationName, EnvironmentName, "net6.0", VersionLabel, BeanstalkPlatformType.Windows, RoleName); Assert.True(success); var environmentDescription = await AWSResourceQueryer.DescribeElasticBeanstalkEnvironment(EnvironmentName); diff --git a/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/TestContextFixture.cs b/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/TestContextFixture.cs index 82d1affe1..c14b5e4ba 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/TestContextFixture.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/TestContextFixture.cs @@ -114,7 +114,7 @@ public async Task InitializeAsync() await EBHelper.CreateApplicationAsync(ApplicationName); await EBHelper.CreateApplicationVersionAsync(ApplicationName, VersionLabel, zipFilePath); - var success = await EBHelper.CreateEnvironmentAsync(ApplicationName, EnvironmentName, VersionLabel, BeanstalkPlatformType.Linux, RoleName); + var success = await EBHelper.CreateEnvironmentAsync(ApplicationName, EnvironmentName, "net6.0", VersionLabel, BeanstalkPlatformType.Linux, RoleName); Assert.True(success); var environmentDescription = await AWSResourceQueryer.DescribeElasticBeanstalkEnvironment(EnvironmentName); diff --git a/test/AWS.Deploy.CLI.IntegrationTests/Helpers/ElasticBeanstalkHelper.cs b/test/AWS.Deploy.CLI.IntegrationTests/Helpers/ElasticBeanstalkHelper.cs index 617ac2518..3935c6364 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/Helpers/ElasticBeanstalkHelper.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/Helpers/ElasticBeanstalkHelper.cs @@ -61,7 +61,7 @@ await _client.CreateApplicationVersionAsync(new CreateApplicationVersionRequest }); } - public async Task CreateEnvironmentAsync(string applicationName, string environmentName, string versionLabel, BeanstalkPlatformType platformType, string ec2Role) + public async Task CreateEnvironmentAsync(string applicationName, string environmentName, string targetFramework, string versionLabel, BeanstalkPlatformType platformType, string ec2Role) { _interactiveService.WriteLine($"Creating new Elastic Beanstalk environment {environmentName} with versionLabel {versionLabel}"); @@ -72,7 +72,7 @@ await _client.CreateEnvironmentAsync(new CreateEnvironmentRequest ApplicationName = applicationName, EnvironmentName = environmentName, VersionLabel = versionLabel, - PlatformArn = (await _awsResourceQueryer.GetLatestElasticBeanstalkPlatformArn(platformType)).PlatformArn, + PlatformArn = (await _awsResourceQueryer.GetLatestElasticBeanstalkPlatformArn(targetFramework, platformType)).PlatformArn, OptionSettings = new List { new ConfigurationOptionSetting("aws:autoscaling:launchconfiguration", "IamInstanceProfile", ec2Role), diff --git a/test/AWS.Deploy.CLI.IntegrationTests/Utilities/TestToolAWSResourceQueryer.cs b/test/AWS.Deploy.CLI.IntegrationTests/Utilities/TestToolAWSResourceQueryer.cs index c4b57ad5c..f78859b71 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/Utilities/TestToolAWSResourceQueryer.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/Utilities/TestToolAWSResourceQueryer.cs @@ -25,7 +25,7 @@ namespace AWS.Deploy.CLI.IntegrationTests.Utilities { public class TestToolAWSResourceQueryer : IAWSResourceQueryer { - public Task GetLatestElasticBeanstalkPlatformArn(BeanstalkPlatformType platformType) + public Task GetLatestElasticBeanstalkPlatformArn(string targetFramework, BeanstalkPlatformType platformType) { return System.Threading.Tasks.Task.FromResult(new PlatformSummary() { PlatformArn = string.Empty }); } @@ -41,7 +41,7 @@ public Task GetLatestElasticBeanstalkPlatformArn(BeanstalkPlatf public Task GetCloudFormationStack(string stackName) => throw new NotImplementedException(); public Task> GetECRAuthorizationToken() => throw new NotImplementedException(); public Task> GetECRRepositories(List repositoryNames) => throw new NotImplementedException(); - public Task> GetElasticBeanstalkPlatformArns(params BeanstalkPlatformType[] platformTypes) => throw new NotImplementedException(); + public Task> GetElasticBeanstalkPlatformArns(string targetFramework, params BeanstalkPlatformType[] platformTypes) => throw new NotImplementedException(); public Task> GetListOfVpcs() => throw new NotImplementedException(); public Task GetS3BucketLocation(string bucketName) => throw new NotImplementedException(); public Task GetS3BucketWebSiteConfiguration(string bucketName) => throw new NotImplementedException(); diff --git a/test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs b/test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs index 3e671f345..8a7f0ba66 100644 --- a/test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs +++ b/test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs @@ -58,7 +58,7 @@ public DeploymentBundleHandlerTests() _recipeHandler = new RecipeHandler(_deploymentManifestEngine, _orchestratorInteractiveService, _directoryManager, _fileManager, optionSettingHandler, validatorFactory); _projectDefinitionParser = new ProjectDefinitionParser(new FileManager(), new DirectoryManager()); - _deploymentBundleHandler = new DeploymentBundleHandler(_commandLineWrapper, awsResourceQueryer, interactiveService, _directoryManager, zipFileManager, new FileManager()); + _deploymentBundleHandler = new DeploymentBundleHandler(_commandLineWrapper, awsResourceQueryer, interactiveService, _directoryManager, zipFileManager, new FileManager(), optionSettingHandler); _recipeDefinition = new Mock( It.IsAny(), @@ -192,6 +192,12 @@ public async Task CreateDotnetPublishZip_NotSelfContained() { var projectPath = SystemIOUtilities.ResolvePath("ConsoleAppTask"); var project = await _projectDefinitionParser.Parse(projectPath); + _recipeDefinition.OptionSettings.Add( + new OptionSettingItem( + "ElasticBeanstalkPlatformArn", + "ElasticBeanstalkPlatformArn", + "Beanstalk Platform", + "The name of the Elastic Beanstalk platform to use with the environment.") { DefaultValue = "arn:aws:elasticbeanstalk:us-west-2::platform/.NET 8 running on 64bit Amazon Linux 2023/3.1.3" }); var recommendation = new Recommendation(_recipeDefinition, project, 100, new Dictionary()); recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild = false; @@ -215,6 +221,12 @@ public async Task CreateDotnetPublishZip_SelfContained() { var projectPath = SystemIOUtilities.ResolvePath("ConsoleAppTask"); var project = await _projectDefinitionParser.Parse(projectPath); + _recipeDefinition.OptionSettings.Add( + new OptionSettingItem( + "ElasticBeanstalkPlatformArn", + "ElasticBeanstalkPlatformArn", + "Beanstalk Platform", + "The name of the Elastic Beanstalk platform to use with the environment.") { DefaultValue = "arn:aws:elasticbeanstalk:us-west-2::platform/.NET 8 running on 64bit Amazon Linux 2023/3.1.3" }); var recommendation = new Recommendation(_recipeDefinition, project, 100, new Dictionary()); recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild = true; @@ -235,15 +247,21 @@ public async Task CreateDotnetPublishZip_SelfContained() } /// - /// Since Beanstalk doesn't currently have .NET 7 and .NET 8 preinstalled we need to make sure we are doing a self-contained publish when creating the deployment bundle. - /// This test checks when the target framework is net7.0 or net8.0, then we are performing a self-contained build. + /// Since Beanstalk doesn't currently have .NET 7 preinstalled we need to make sure we are doing a self-contained publish when creating the deployment bundle. + /// This test checks when the target framework is net7.0, then we are performing a self-contained build. /// [Fact] - public async Task CreateDotnetPublishZip_SelfContained_Net7_Net8() + public async Task CreateDotnetPublishZip_SelfContained_Net7() { - var projectPath = SystemIOUtilities.ResolvePath(Path.Combine("docker", "WebAppNet8")); + var projectPath = SystemIOUtilities.ResolvePath(Path.Combine("docker", "WebAppNet7")); var project = await _projectDefinitionParser.Parse(projectPath); _recipeDefinition.TargetService = RecipeIdentifier.TARGET_SERVICE_ELASTIC_BEANSTALK; + _recipeDefinition.OptionSettings.Add( + new OptionSettingItem( + "ElasticBeanstalkPlatformArn", + "ElasticBeanstalkPlatformArn", + "Beanstalk Platform", + "The name of the Elastic Beanstalk platform to use with the environment.") { DefaultValue = "arn:aws:elasticbeanstalk:us-west-2::platform/.NET 8 running on 64bit Amazon Linux 2023/3.1.3" }); var recommendation = new Recommendation(_recipeDefinition, project, 100, new Dictionary()); recommendation.DeploymentBundle.DotnetPublishBuildConfiguration = "Release"; diff --git a/test/AWS.Deploy.CLI.UnitTests/Utilities/TestToolAWSResourceQueryer.cs b/test/AWS.Deploy.CLI.UnitTests/Utilities/TestToolAWSResourceQueryer.cs index f756f809e..b38abbb39 100644 --- a/test/AWS.Deploy.CLI.UnitTests/Utilities/TestToolAWSResourceQueryer.cs +++ b/test/AWS.Deploy.CLI.UnitTests/Utilities/TestToolAWSResourceQueryer.cs @@ -56,12 +56,12 @@ public Task> GetECRRepositories(List repositoryNames) return Task.FromResult>(new List() { repository }); } - public Task GetLatestElasticBeanstalkPlatformArn(BeanstalkPlatformType platformType) + public Task GetLatestElasticBeanstalkPlatformArn(string targetFramework, BeanstalkPlatformType platformType) { return Task.FromResult(new PlatformSummary() { PlatformArn = string.Empty }); } - public Task> GetElasticBeanstalkPlatformArns(params BeanstalkPlatformType[] platformTypes) => throw new NotImplementedException(); + public Task> GetElasticBeanstalkPlatformArns(string targetFramework, params BeanstalkPlatformType[] platformTypes) => throw new NotImplementedException(); public Task> GetListOfVpcs() => throw new NotImplementedException(); public Task> ListOfEC2KeyPairs() => throw new NotImplementedException(); public Task> ListOfECSClusters(string ecsClusterName) => throw new NotImplementedException(); diff --git a/test/AWS.Deploy.Orchestration.UnitTests/AWSResourceQueryerTests.cs b/test/AWS.Deploy.Orchestration.UnitTests/AWSResourceQueryerTests.cs index b18d672d2..01bfd8f1a 100644 --- a/test/AWS.Deploy.Orchestration.UnitTests/AWSResourceQueryerTests.cs +++ b/test/AWS.Deploy.Orchestration.UnitTests/AWSResourceQueryerTests.cs @@ -175,6 +175,85 @@ public void SortElasticBeanstalkWindowsPlatforms() } } + [Fact] + public void SortElasticBeanstalkLinuxPlatforms() + { + // Use PlatformOwner as a placeholder to store where the summary should be sorted to. + var platforms = new List() + { + new PlatformSummary + { + PlatformArn = "arn:aws:elasticbeanstalk:us-west-2::platform/.NET 6 running on 64bit Amazon Linux 2023/3.1.3", + PlatformBranchName = ".NET 6 running on 64bit Amazon Linux 2023", + PlatformVersion = "3.1.3", + PlatformOwner = "1" + }, + new PlatformSummary + { + PlatformArn = "arn:aws:elasticbeanstalk:us-west-2::platform/.NET 6 running on 64bit Amazon Linux 2023/3.1.2", + PlatformBranchName = ".NET 6 running on 64bit Amazon Linux 2023", + PlatformVersion = "3.1.2", + PlatformOwner = "2" + }, + new PlatformSummary + { + PlatformArn = "arn:aws:elasticbeanstalk:us-west-2::platform/.NET 6 running on 64bit Amazon Linux 2023/3.0.6", + PlatformBranchName = ".NET 6 running on 64bit Amazon Linux 2023", + PlatformVersion = "3.0.6", + PlatformOwner = "3" + }, + new PlatformSummary + { + PlatformArn = "arn:aws:elasticbeanstalk:us-west-2::platform/.NET 6 running on 64bit Amazon Linux 2023/3.0.5", + PlatformBranchName = ".NET 6 running on 64bit Amazon Linux 2023", + PlatformVersion = "3.0.5", + PlatformOwner = "4" + }, + new PlatformSummary + { + PlatformArn = "arn:aws:elasticbeanstalk:us-west-2::platform/.NET 8 running on 64bit Amazon Linux 2023/3.1.3", + PlatformBranchName = ".NET 8 running on 64bit Amazon Linux 2023", + PlatformVersion = "3.1.3", + PlatformOwner = "0" + }, + new PlatformSummary + { + PlatformArn = "arn:aws:elasticbeanstalk:us-west-2::platform/.NET Core running on 64bit Amazon Linux 2/2.8.0", + PlatformBranchName = ".NET Core running on 64bit Amazon Linux 2", + PlatformVersion = "2.8.0", + PlatformOwner = "5" + }, + new PlatformSummary + { + PlatformArn = "arn:aws:elasticbeanstalk:us-west-2::platform/.NET Core running on 64bit Amazon Linux 2/2.7.3", + PlatformBranchName = ".NET Core running on 64bit Amazon Linux 2", + PlatformVersion = "2.7.3", + PlatformOwner = "6" + }, + new PlatformSummary + { + PlatformArn = "arn:aws:elasticbeanstalk:us-west-2::platform/.NET Core running on 64bit Amazon Linux 2/2.6.0", + PlatformBranchName = ".NET Core running on 64bit Amazon Linux 2", + PlatformVersion = "2.6.0", + PlatformOwner = "7" + }, + new PlatformSummary + { + PlatformArn = "arn:aws:elasticbeanstalk:us-west-2::platform/.NET Core running on 64bit Amazon Linux 2/2.5.7", + PlatformBranchName = ".NET Core running on 64bit Amazon Linux 2", + PlatformVersion = "2.5.7", + PlatformOwner = "8" + } + }; + + var sortedPlatforms = AWSResourceQueryer.SortElasticBeanstalkLinuxPlatforms(string.Empty, platforms); + + for (var i = 0; i < sortedPlatforms.Count; i++) + { + Assert.Equal(i.ToString(), sortedPlatforms[i].PlatformOwner); + } + } + [Fact] public async Task CreateRepository_TagsWithRecipeName_Success() { diff --git a/test/AWS.Deploy.Orchestration.UnitTests/DeployedApplicationQueryerTests.cs b/test/AWS.Deploy.Orchestration.UnitTests/DeployedApplicationQueryerTests.cs index c744cf342..73fca91ea 100644 --- a/test/AWS.Deploy.Orchestration.UnitTests/DeployedApplicationQueryerTests.cs +++ b/test/AWS.Deploy.Orchestration.UnitTests/DeployedApplicationQueryerTests.cs @@ -283,7 +283,7 @@ public async Task GetExistingDeployedApplications_ContainsValidBeanstalkEnvironm .Returns(Task.FromResult(environments)); _mockAWSResourceQueryer - .Setup(x => x.GetElasticBeanstalkPlatformArns()) + .Setup(x => x.GetElasticBeanstalkPlatformArns(It.IsAny())) .Returns(Task.FromResult(platforms)); _mockAWSResourceQueryer @@ -337,7 +337,7 @@ public async Task GetExistingDeployedApplication_SkipsEnvironmentsWithIncompatib .Returns(Task.FromResult(environments)); _mockAWSResourceQueryer - .Setup(x => x.GetElasticBeanstalkPlatformArns()) + .Setup(x => x.GetElasticBeanstalkPlatformArns(It.IsAny())) .Returns(Task.FromResult(platforms)); _mockAWSResourceQueryer @@ -399,7 +399,7 @@ public async Task GetExistingDeployedApplication_SkipsEnvironmentsCreatedFromThe .Returns(Task.FromResult(environments)); _mockAWSResourceQueryer - .Setup(x => x.GetElasticBeanstalkPlatformArns()) + .Setup(x => x.GetElasticBeanstalkPlatformArns(It.IsAny())) .Returns(Task.FromResult(platforms)); _mockAWSResourceQueryer