Skip to content

Commit

Permalink
fix: remove auto self-contained build for .NET 8 beanstalk deployments
Browse files Browse the repository at this point in the history
  • Loading branch information
philasmar committed Jul 25, 2024
1 parent db9ec45 commit 87f809f
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/AWS.Deploy.Common/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ public enum DeployToolErrorCode
InvalidWindowsManifestFile = 10010700,
UserDeploymentFileNotFound = 10010800,
DockerInspectFailed = 10004200,
InvalidElasticBeanstalkPlatform = 10010900
}

public class ProjectFileNotFoundException : DeployToolException
Expand Down
6 changes: 5 additions & 1 deletion src/AWS.Deploy.Orchestration/CDK/CDKBootstrapTemplate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
68 changes: 64 additions & 4 deletions src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,9 @@ public async Task<List<PlatformSummary>> GetElasticBeanstalkPlatformArns(params
var allPlatformSummaries = new List<PlatformSummary>();
if (platformTypes.Contains(BeanstalkPlatformType.Linux))
{
allPlatformSummaries.AddRange(await fetchPlatforms(Constants.ElasticBeanstalk.LinuxPlatformType));
var linuxPlatforms = await fetchPlatforms(Constants.ElasticBeanstalk.LinuxPlatformType);
linuxPlatforms = SortElasticBeanstalkLinuxPlatforms(linuxPlatforms);
allPlatformSummaries.AddRange(linuxPlatforms);

Check warning on line 562 in src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs#L560-L562

Added lines #L560 - L562 were not covered by tests
}
if (platformTypes.Contains(BeanstalkPlatformType.Windows))
{
Expand Down Expand Up @@ -593,15 +595,73 @@ public async Task<PlatformSummary> GetLatestElasticBeanstalkPlatformArn(Beanstal
"or that the configured credentials lack permission to call ListPlatformVersions.");
}

return platforms.First();
var sortedPlatforms = SortElasticBeanstalkLinuxPlatforms(platforms);

Check warning on line 598 in src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs#L598

Added line #L598 was not covered by tests

return sortedPlatforms.First();

Check warning on line 600 in src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs#L600

Added line #L600 was not covered by tests
}

/// <summary>
/// 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 use the latest version of each platform first as well as the latest version of .NET first. Also .NET x should come before .NET Core.
/// 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
/// </summary>
/// <param name="platforms"></param>
public static List<PlatformSummary> SortElasticBeanstalkLinuxPlatforms(List<PlatformSummary> platforms)
{
var dotnetVersionMap = new Dictionary<string, int>();
foreach (var platform in platforms)
{
var runningIndexOf = platform.PlatformBranchName.IndexOf("running", StringComparison.InvariantCultureIgnoreCase);
if (runningIndexOf == -1)
{
dotnetVersionMap[platform.PlatformArn] = 0;
continue;

Check warning on line 638 in src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs#L637-L638

Added lines #L637 - L638 were not covered by tests
}

var framework = platform.PlatformBranchName.Substring(0, runningIndexOf).Trim();
var frameworkSplit = framework.Split(" ");
if (frameworkSplit.Length != 2)
{
dotnetVersionMap[platform.PlatformArn] = 0;
continue;

Check warning on line 646 in src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs#L645-L646

Added lines #L645 - L646 were not covered by tests
}

if (!int.TryParse(frameworkSplit[1], out var dotnetVersion))
{
dotnetVersionMap[platform.PlatformArn] = 0;
continue;
}

dotnetVersionMap[platform.PlatformArn] = dotnetVersion;
}

return platforms.OrderByDescending(x => new Version(x.PlatformVersion)).ThenByDescending(x => dotnetVersionMap[x.PlatformArn]).ToList();
}

/// <summary>
/// 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)
Expand All @@ -613,7 +673,7 @@ public async Task<PlatformSummary> 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)
Expand Down
75 changes: 64 additions & 11 deletions src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -43,21 +44,24 @@ public class DeploymentBundleHandler : IDeploymentBundleHandler
private readonly IDirectoryManager _directoryManager;
private readonly IZipFileManager _zipFileManager;
private readonly IFileManager _fileManager;
private readonly IOptionSettingHandler _optionSettingHandler;

public DeploymentBundleHandler(
ICommandLineWrapper commandLineWrapper,
IAWSResourceQueryer awsResourceQueryer,
IOrchestratorInteractiveService interactiveService,
IDirectoryManager directoryManager,
IZipFileManager zipFileManager,
IFileManager fileManager)
IFileManager fileManager,
IOptionSettingHandler optionSettingHandler)
{
_commandLineWrapper = commandLineWrapper;
_awsResourceQueryer = awsResourceQueryer;
_interactiveService = interactiveService;
_directoryManager = directoryManager;
_zipFileManager = zipFileManager;
_fileManager = fileManager;
_optionSettingHandler = optionSettingHandler;
}

public async Task BuildDockerImage(CloudApplication cloudApplication, Recommendation recommendation, string imageTag)
Expand Down Expand Up @@ -108,21 +112,70 @@ public async Task PushDockerImageToECR(Recommendation recommendation, string rep
recommendation.DeploymentBundle.ECRImageTag = tagSuffix;
}

/// <summary>
/// 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.
/// </summary>
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<string> { "net7.0" };
var frameworkNames = new Dictionary<string, string> { { "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"));

Check warning on line 136 in src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs#L136

Added line #L136 was not covered by tests
if (beanstalkPlatformSetting != null)
{
var beanstalkPlatformSettingValue = _optionSettingHandler.GetOptionSettingValue<string>(recommendation, beanstalkPlatformSetting);

Check warning on line 139 in src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs#L139

Added line #L139 was not covered by tests
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];

Check warning on line 143 in src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs#L142-L143

Added lines #L142 - L143 were not covered by tests
if (!Version.TryParse(beanstalkPlatformSettingValueSplit[2], out var beanstalkPlatformVersion))
throw new InvalidElasticBeanstalkPlatformException(DeployToolErrorCode.InvalidElasticBeanstalkPlatform, $"The selected Elastic Beanstalk platform version '{beanstalkPlatformSettingValue}' is invalid.");

Check warning on line 145 in src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs#L145

Added line #L145 was not covered by tests

// 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;

Check warning on line 159 in src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs#L157-L159

Added lines #L157 - L159 were not covered by tests
}
}
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;

Check warning on line 166 in src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs#L164-L166

Added lines #L164 - L166 were not covered by tests
}
}
}
}
}

public async Task<string> 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<string> { "net7.0", "net8.0" };
var frameworkNames = new Dictionary<string, string> { { "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;
Expand Down
10 changes: 9 additions & 1 deletion src/AWS.Deploy.Orchestration/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public class ElasticBeanstalkException : DeployToolException
{
public ElasticBeanstalkException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }
}

/// <summary>
/// Throw if unable to access the specified AWS Region.
/// </summary>
Expand Down Expand Up @@ -276,4 +276,12 @@ public class InvalidWindowsManifestFileException : DeployToolException
{
public InvalidWindowsManifestFileException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }
}

/// <summary>
/// Throw if the deploy tool encounters an invalid Elastic Beanstalk platform version.
/// </summary>
public class InvalidElasticBeanstalkPlatformException : DeployToolException
{
public InvalidElasticBeanstalkPlatformException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }

Check warning on line 285 in src/AWS.Deploy.Orchestration/Exceptions.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.Orchestration/Exceptions.cs#L285

Added line #L285 was not covered by tests
}
}
28 changes: 23 additions & 5 deletions test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RecipeDefinition>(
It.IsAny<string>(),
Expand Down Expand Up @@ -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<string, object>());

recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild = false;
Expand All @@ -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<string, object>());

recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild = true;
Expand All @@ -235,15 +247,21 @@ public async Task CreateDotnetPublishZip_SelfContained()
}

/// <summary>
/// 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.
/// </summary>
[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<string, object>());

recommendation.DeploymentBundle.DotnetPublishBuildConfiguration = "Release";
Expand Down
Loading

0 comments on commit 87f809f

Please sign in to comment.