Skip to content

Commit 5f28fb8

Browse files
authored
chore: self-contain unsupported .net versions on beanstalk (#915)
1 parent 5fc8425 commit 5f28fb8

12 files changed

Lines changed: 137 additions & 46 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"Projects": [
3+
{
4+
"Name": "AWS.Deploy.CLI",
5+
"Type": "Minor",
6+
"ChangelogMessages": [
7+
"Automatically deploy unsupported .NET versions using a self-contained build to Elastic Beanstalk"
8+
]
9+
}
10+
]
11+
}

AWS.Deploy.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AWS.Deploy.DockerImageUploa
6969
EndProject
7070
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAppArmDeployment", "testapps\WebAppArmDeployment\WebAppArmDeployment.csproj", "{303A0323-3FEF-4640-80C2-E33A8BC0F1FC}"
7171
EndProject
72+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppArmDeployment", "testapps\ConsoleAppArmDeployment\ConsoleAppArmDeployment.csproj", "{BA7E7790-8E60-4133-B3BC-BDD50B1D7A76}"
73+
EndProject
7274
Global
7375
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7476
Debug|Any CPU = Debug|Any CPU
@@ -179,6 +181,10 @@ Global
179181
{303A0323-3FEF-4640-80C2-E33A8BC0F1FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
180182
{303A0323-3FEF-4640-80C2-E33A8BC0F1FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
181183
{303A0323-3FEF-4640-80C2-E33A8BC0F1FC}.Release|Any CPU.Build.0 = Release|Any CPU
184+
{BA7E7790-8E60-4133-B3BC-BDD50B1D7A76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
185+
{BA7E7790-8E60-4133-B3BC-BDD50B1D7A76}.Debug|Any CPU.Build.0 = Debug|Any CPU
186+
{BA7E7790-8E60-4133-B3BC-BDD50B1D7A76}.Release|Any CPU.ActiveCfg = Release|Any CPU
187+
{BA7E7790-8E60-4133-B3BC-BDD50B1D7A76}.Release|Any CPU.Build.0 = Release|Any CPU
182188
EndGlobalSection
183189
GlobalSection(SolutionProperties) = preSolution
184190
HideSolutionNode = FALSE
@@ -211,6 +217,7 @@ Global
211217
{7E661545-7DFD-4FE3-A5F9-767FAE30DFFE} = {BD466B5C-D8B0-4069-98A9-6DC8F01FA757}
212218
{49A1C020-F4C8-4B28-A6B2-6AD3C8452E69} = {BD466B5C-D8B0-4069-98A9-6DC8F01FA757}
213219
{303A0323-3FEF-4640-80C2-E33A8BC0F1FC} = {C3A0C716-BDEA-4393-B223-AF8F8531522A}
220+
{BA7E7790-8E60-4133-B3BC-BDD50B1D7A76} = {C3A0C716-BDEA-4393-B223-AF8F8531522A}
214221
EndGlobalSection
215222
GlobalSection(ExtensibilityGlobals) = postSolution
216223
SolutionGuid = {5A4B2863-1763-4496-B122-651A38A4F5D7}

buildtools/ci.buildspec.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ phases:
1212
build:
1313
commands:
1414
- dotnet build AWS.Deploy.sln -c Release
15-
- dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover AWS.Deploy.sln -c Release --no-build --logger trx --results-directory ./testresults
15+
- dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover AWS.Deploy.sln -c Release --no-build --logger trx --logger "console;verbosity=detailed" --results-directory ./testresults
1616

1717
post_build:
1818
steps:

src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,20 @@ private void SwitchToSelfContainedBuildIfNeeded(Recommendation recommendation)
135135
if (string.IsNullOrEmpty(targetFramework))
136136
return;
137137

138-
// Elastic Beanstalk doesn't currently have .NET 7 and 9 preinstalled.
139-
var unavailableFramework = new List<string> { "net7.0", "net9.0" };
140-
var frameworkNames = new Dictionary<string, string> { { "net7.0", ".NET 7" }, { "net9.0", ".NET 9" } };
141-
if (unavailableFramework.Contains(targetFramework))
138+
// Elastic Beanstalk currently has .NET 8 and 9 supported on their platforms.
139+
var supportedFrameworks = new List<string> { "net8.0", "net9.0" };
140+
var retiredFrameworks = new List<string> { "netcoreapp3.1", "net5.0", "net6.0", "net7.0" };
141+
if (!supportedFrameworks.Contains(targetFramework))
142142
{
143-
_interactiveService.LogInfoMessage($"Using self-contained publish since AWS Elastic Beanstalk does not currently have {frameworkNames[targetFramework]} preinstalled");
143+
if (retiredFrameworks.Contains(targetFramework))
144+
{
145+
_interactiveService.LogErrorMessage($"The version of .NET that you are targeting has reached its end-of-support and has been retired by Elastic Beanstalk");
146+
_interactiveService.LogInfoMessage($"Using self-contained publish to include the out of support version of .NET used by this application with the deployment bundle");
147+
}
148+
else
149+
{
150+
_interactiveService.LogInfoMessage($"Using self-contained publish since AWS Elastic Beanstalk does not currently have {targetFramework} preinstalled");
151+
}
144152
recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild = true;
145153
return;
146154
}

test/AWS.Deploy.CLI.IntegrationTests/ConsoleAppTests.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,14 @@ public async Task DefaultConfigurations(params string[] components)
119119
}
120120

121121
[Theory]
122-
[InlineData("testapps", "ConsoleAppService", "ConsoleAppService.csproj")]
123-
[InlineData("testapps", "ConsoleAppTask", "ConsoleAppTask.csproj")]
122+
[InlineData("testapps", "ConsoleAppArmDeployment", "ConsoleAppArmDeployment.csproj")]
124123
public async Task FargateArmDeployment(params string[] components)
125124
{
126125
_stackName = $"{components[1]}Arm{Guid.NewGuid().ToString().Split('-').Last()}";
127126

128127
// Arrange input for deploy
129128
await _interactiveService.StdInWriter.WriteAsync(Environment.NewLine); // Select default recommendation
130-
await _interactiveService.StdInWriter.WriteLineAsync("8"); // Select "Environment Architecture"
129+
await _interactiveService.StdInWriter.WriteLineAsync("7"); // Select "Environment Architecture"
131130
await _interactiveService.StdInWriter.WriteLineAsync("2"); // Select "Arm64"
132131
await _interactiveService.StdInWriter.WriteAsync(Environment.NewLine); // Confirm selection and deploy
133132
await _interactiveService.StdInWriter.FlushAsync();

test/AWS.Deploy.CLI.IntegrationTests/WebAppNoDockerFileTests.cs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -164,29 +164,74 @@ public async Task BeanstalkArmDeployment()
164164

165165
// Verify stack exists in list of deployments
166166
var listStdOut = _interactiveService.StdOutReader.ReadAllLines().Select(x => x.Split()[0]).ToList();
167-
Assert.Contains(listStdOut, (deployment) => _stackName.Equals(deployment));
168-
167+
Assert.Contains(listStdOut, (deployment) => _stackName.Equals(deployment));
168+
169169
// Try switching from ARM to X86_64 on redeployment
170170
await _interactiveService.StdInWriter.WriteLineAsync("3"); // Select "Environment Architecture"
171-
await _interactiveService.StdInWriter.WriteLineAsync("1"); // Select "X86_64"
171+
await _interactiveService.StdInWriter.WriteLineAsync("1"); // Select "X86_64"
172172
await _interactiveService.StdInWriter.WriteAsync(Environment.NewLine); // Confirm selection and deploy
173173
await _interactiveService.StdInWriter.WriteLineAsync("more"); // Select "Environment Architecture"
174174
await _interactiveService.StdInWriter.WriteLineAsync("1"); // Select "EC2 Instance Type"
175175
await _interactiveService.StdInWriter.WriteLineAsync("y"); // Select "Free tier"
176176
await _interactiveService.StdInWriter.WriteLineAsync("1"); // Select "x86_64"
177177
await _interactiveService.StdInWriter.WriteLineAsync("1"); // Select "CPU Cores"
178178
await _interactiveService.StdInWriter.WriteLineAsync("1"); // Select "Instance Memory"
179-
await _interactiveService.StdInWriter.WriteLineAsync("1"); // Select "Instance Type"
179+
await _interactiveService.StdInWriter.WriteLineAsync("1"); // Select "Instance Type"
180180
await _interactiveService.StdInWriter.WriteAsync(Environment.NewLine); // Confirm selection and deploy
181181
await _interactiveService.StdInWriter.FlushAsync();
182182

183183
// Perform re-deployment
184184
deployArgs = new[] { "deploy", "--project-path", projectPath, "--application-name", _stackName, "--diagnostics" };
185185
Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deployArgs));
186-
Assert.Equal(StackStatus.UPDATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName));
186+
Assert.Equal(StackStatus.UPDATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName));
187187

188188
deployStdOut = _interactiveService.StdOutReader.ReadAllLines();
189-
Assert.Contains(deployStdOut, x => x.Contains("Please select an Instance Type that supports the currently selected Environment Architecture."));
189+
Assert.Contains(deployStdOut, x => x.Contains("Please select an Instance Type that supports the currently selected Environment Architecture."));
190+
191+
// Arrange input for delete
192+
// Use --silent flag to delete without user prompts
193+
var deleteArgs = new[] { "delete-deployment", _stackName, "--diagnostics", "--silent" };
194+
195+
// Delete
196+
Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deleteArgs)); ;
197+
198+
// Verify application is deleted
199+
Assert.True(await _cloudFormationHelper.IsStackDeleted(_stackName), $"{_stackName} still exists.");
200+
}
201+
202+
[Fact]
203+
public async Task DeployRetiredDotnetVersion()
204+
{
205+
_stackName = $"RetiredDotnetBeanstalk{Guid.NewGuid().ToString().Split('-').Last()}";
206+
207+
var projectPath = _testAppManager.GetProjectPath(Path.Combine("testapps", "WebApiNET6", "WebApiNET6.csproj"));
208+
var deployArgs = new[] { "deploy", "--project-path", projectPath, "--application-name", _stackName, "--diagnostics", "--silent", "--apply", "ElasticBeanStalkConfigFile.json" };
209+
Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deployArgs));
210+
211+
// Verify application is deployed and running
212+
Assert.Equal(StackStatus.CREATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName));
213+
214+
var deployStdOut = _interactiveService.StdOutReader.ReadAllLines();
215+
216+
var tempCdkProjectLine = deployStdOut.First(line => line.StartsWith("Saving AWS CDK deployment project to: "));
217+
var tempCdkProject = tempCdkProjectLine.Split(": ")[1].Trim();
218+
Assert.False(Directory.Exists(tempCdkProject), $"{tempCdkProject} must not exist.");
219+
220+
// Example: Endpoint: http://52.36.216.238/
221+
var endpointLine = deployStdOut.First(line => line.Trim().StartsWith($"Endpoint"));
222+
var applicationUrl = endpointLine.Substring(endpointLine.IndexOf(":", StringComparison.Ordinal) + 1).Trim();
223+
Assert.True(Uri.IsWellFormedUriString(applicationUrl, UriKind.Absolute));
224+
225+
// URL could take few more minutes to come live, therefore, we want to wait and keep trying for a specified timeout
226+
await _httpHelper.WaitUntilSuccessStatusCode(applicationUrl, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(5));
227+
228+
// list
229+
var listArgs = new[] { "list-deployments", "--diagnostics" };
230+
Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(listArgs)); ;
231+
232+
// Verify stack exists in list of deployments
233+
var listStdOut = _interactiveService.StdOutReader.ReadAllLines().Select(x => x.Split()[0]).ToList();
234+
Assert.Contains(listStdOut, (deployment) => _stackName.Equals(deployment));
190235

191236
// Arrange input for delete
192237
// Use --silent flag to delete without user prompts

test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -399,37 +399,6 @@ public async Task CreateDotnetPublishZip_UnsupportedFramework_AlreadySetAsSelfCo
399399
Assert.Equal(expectedCommand, _commandLineWrapper.CommandsToExecute.First().Command);
400400
}
401401

402-
[Theory]
403-
[InlineData("arn:aws:elasticbeanstalk:us-west-2::platform/.NET 8 running on 64bit Amazon Linux 2023")]
404-
[InlineData("arn:aws:elasticbeanstalk:us-west-2::platform/.NET 8 running on 64bit Amazon Linux 2023/invalidversion")]
405-
public async Task CreateDotnetPublishZip_InvalidPlatformArn(string platformArn)
406-
{
407-
var projectPath = SystemIOUtilities.ResolvePath(Path.Combine("ConsoleAppTask"));
408-
var project = await _projectDefinitionParser.Parse(projectPath);
409-
_recipeDefinition.TargetService = RecipeIdentifier.TARGET_SERVICE_ELASTIC_BEANSTALK;
410-
_recipeDefinition.OptionSettings.Add(
411-
new OptionSettingItem(
412-
"ElasticBeanstalkPlatformArn",
413-
"ElasticBeanstalkPlatformArn",
414-
"Beanstalk Platform",
415-
"The name of the Elastic Beanstalk platform to use with the environment.")
416-
{
417-
DefaultValue = platformArn
418-
});
419-
var recommendation = new Recommendation(_recipeDefinition, project, 100, new Dictionary<string, object>());
420-
421-
await _deploymentBundleHandler.CreateDotnetPublishZip(recommendation);
422-
423-
Assert.False(recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild);
424-
425-
var expectedCommand =
426-
$"dotnet publish \"{project.ProjectPath}\"" +
427-
$" -o \"{_directoryManager.CreatedDirectories.First()}\"" +
428-
" -c Release ";
429-
430-
Assert.Equal(expectedCommand, _commandLineWrapper.CommandsToExecute.First().Command);
431-
}
432-
433402
[Theory]
434403
[InlineData("arn:aws:elasticbeanstalk:us-west-2::platform/.NET Core running on 64bit Amazon Linux 2/2.7.3")]
435404
[InlineData("arn:aws:elasticbeanstalk:us-west-2::platform/.NET 6 running on 64bit Amazon Linux 2023/3.1.3")]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
</Project>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
2+
USER app
3+
WORKDIR /app
4+
EXPOSE 8080
5+
EXPOSE 8081
6+
7+
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
8+
ARG TARGETARCH
9+
WORKDIR /src
10+
COPY ["ConsoleAppArmDeployment.csproj", "ConsoleAppArmDeployment/"]
11+
RUN dotnet restore "ConsoleAppArmDeployment/ConsoleAppArmDeployment.csproj" -a $TARGETARCH
12+
COPY . "ConsoleAppArmDeployment/"
13+
14+
WORKDIR "/src/ConsoleAppArmDeployment"
15+
RUN dotnet build "ConsoleAppArmDeployment.csproj" -c Release -o /app/build -a $TARGETARCH
16+
17+
FROM build AS publish
18+
RUN dotnet publish "ConsoleAppArmDeployment.csproj" -c Release -o /app/publish -a $TARGETARCH
19+
20+
FROM base AS final
21+
WORKDIR /app
22+
COPY --from=publish /app/publish .
23+
ENTRYPOINT ["dotnet", "ConsoleAppArmDeployment.dll"]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// See https://aka.ms/new-console-template for more information
2+
3+
while (true)
4+
{
5+
Console.WriteLine("Hello World!");
6+
await Task.Delay(500);
7+
}

0 commit comments

Comments
 (0)