From 628fb391ba7570b37b4032ea8fe25206fc0ee43f Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Sun, 13 Oct 2024 14:48:36 -0400 Subject: [PATCH] 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);