From cbd161afc8af10be23d85da0a4a6547b57e2de35 Mon Sep 17 00:00:00 2001 From: Malhar Khimsaria Date: Fri, 11 Feb 2022 04:45:14 -0500 Subject: [PATCH] fix: Correctly set deployment ResourceType during re-deployments via server mode --- .../Controllers/DeploymentController.cs | 1 + .../CLITests.cs | 44 ++++ .../ServerModeTests.cs | 134 ++++++++++++ .../TestContextFixture.cs | 119 +++++++++++ .../TestContextFixtureCollection.cs | 12 ++ .../Helpers/ElasticBeanstalkHelper.cs | 200 ++++++++++++++++++ .../Helpers/IAMHelper.cs | 99 +++++++++ .../ServerModeTests.cs | 5 +- 8 files changed, 610 insertions(+), 4 deletions(-) create mode 100644 test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/CLITests.cs create mode 100644 test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/ServerModeTests.cs create mode 100644 test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/TestContextFixture.cs create mode 100644 test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/TestContextFixtureCollection.cs create mode 100644 test/AWS.Deploy.CLI.IntegrationTests/Helpers/ElasticBeanstalkHelper.cs create mode 100644 test/AWS.Deploy.CLI.IntegrationTests/Helpers/IAMHelper.cs diff --git a/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs b/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs index 47c2977b8..5699c0b00 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs @@ -397,6 +397,7 @@ public async Task SetDeploymentTarget(string sessionId, [FromBody state.ApplicationDetails.Name = existingDeployment.Name; state.ApplicationDetails.UniqueIdentifier = existingDeployment.UniqueIdentifier; state.ApplicationDetails.RecipeId = existingDeployment.RecipeId; + state.ApplicationDetails.ResourceType = existingDeployment.ResourceType; await orchestrator.ApplyAllReplacementTokens(state.SelectedRecommendation, existingDeployment.Name); } diff --git a/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/CLITests.cs b/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/CLITests.cs new file mode 100644 index 000000000..defe30858 --- /dev/null +++ b/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/CLITests.cs @@ -0,0 +1,44 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using AWS.Deploy.CLI.IntegrationTests.Extensions; +using Xunit; + +namespace AWS.Deploy.CLI.IntegrationTests.BeanstalkBackwardsCompatibilityTests +{ + [Collection(nameof(TestContextFixture))] + public class CLITests + { + private readonly TestContextFixture _fixture; + + public CLITests(TestContextFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task DeployToExistingBeanstalkEnvironment() + { + var projectPath = _fixture.TestAppManager.GetProjectPath(Path.Combine("testapps", "WebAppNoDockerFile", "WebAppNoDockerFile.csproj")); + var deployArgs = new[] { "deploy", "--project-path", projectPath, "--application-name", _fixture.EnvironmentName, "--diagnostics", "--silent" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _fixture.App.Run(deployArgs)); + + var environmentDescription = await _fixture.AWSResourceQueryer.DescribeElasticBeanstalkEnvironment(_fixture.EnvironmentName); + + // URL could take few more minutes to come live, therefore, we want to wait and keep trying for a specified timeout + await _fixture.HttpHelper.WaitUntilSuccessStatusCode(environmentDescription.CNAME, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(5)); + + var successMessagePrefix = $"The Elastic Beanstalk Environment {_fixture.EnvironmentName} has been successfully updated"; + var deployStdOutput = _fixture.InteractiveService.StdOutReader.ReadAllLines(); + var successMessage = deployStdOutput.First(line => line.Trim().StartsWith(successMessagePrefix)); + Assert.False(string.IsNullOrEmpty(successMessage)); + + var expectedVersionLabel = successMessage.Split(" ").Last(); + Assert.True(await _fixture.EBHelper.VerifyEnvironmentVersionLabel(_fixture.EnvironmentName, expectedVersionLabel)); + } + } +} diff --git a/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/ServerModeTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/ServerModeTests.cs new file mode 100644 index 000000000..ffc028dc6 --- /dev/null +++ b/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/ServerModeTests.cs @@ -0,0 +1,134 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Amazon.Runtime; +using AWS.Deploy.CLI.Commands; +using AWS.Deploy.CLI.IntegrationTests.Helpers; +using AWS.Deploy.ServerMode.Client; +using Xunit; + +namespace AWS.Deploy.CLI.IntegrationTests.BeanstalkBackwardsCompatibilityTests +{ + [Collection(nameof(TestContextFixture))] + public class ServerModeTests + { + private readonly TestContextFixture _fixture; + private const string BEANSTALK_ENVIRONMENT_RECIPE_ID = "AspNetAppExistingBeanstalkEnvironment"; + + public ServerModeTests(TestContextFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task DeployToExistingBeanstalkEnvironment() + { + var projectPath = _fixture.TestAppManager.GetProjectPath(Path.Combine("testapps", "WebAppNoDockerFile", "WebAppNoDockerFile.csproj")); + var portNumber = 4001; + using var httpClient = ServerModeHttpClientFactory.ConstructHttpClient(ResolveCredentials); + + var serverCommand = new ServerModeCommand(_fixture.ToolInteractiveService, portNumber, null, true); + var cancelSource = new CancellationTokenSource(); + + var serverTask = serverCommand.ExecuteAsync(cancelSource.Token); + try + { + var baseUrl = $"http://localhost:{portNumber}/"; + var restClient = new RestAPIClient(baseUrl, httpClient); + + await WaitTillServerModeReady(restClient); + + var startSessionOutput = await restClient.StartDeploymentSessionAsync(new StartDeploymentSessionInput + { + AwsRegion = "us-west-2", + ProjectPath = projectPath + }); + + var sessionId = startSessionOutput.SessionId; + Assert.NotNull(sessionId); + + var existingDeployments = await restClient.GetExistingDeploymentsAsync(sessionId); + var existingDeployment = existingDeployments.ExistingDeployments.First(x => string.Equals(_fixture.EnvironmentName, x.Name)); + + Assert.Equal(_fixture.EnvironmentName, existingDeployment.Name); + Assert.Equal(BEANSTALK_ENVIRONMENT_RECIPE_ID, existingDeployment.RecipeId); + Assert.Equal(_fixture.EnvironmentId, existingDeployment.ExistingDeploymentId); + Assert.Equal(DeploymentTypes.BeanstalkEnvironment, existingDeployment.DeploymentType); + + var signalRClient = new DeploymentCommunicationClient(baseUrl); + await signalRClient.JoinSession(sessionId); + + var logOutput = new StringBuilder(); + signalRClient.ReceiveLogAllLogAction = (line) => + { + logOutput.AppendLine(line); + }; + + await restClient.SetDeploymentTargetAsync(sessionId, new SetDeploymentTargetInput + { + ExistingDeploymentId = _fixture.EnvironmentId + }); + + await restClient.StartDeploymentAsync(sessionId); + + await WaitForDeployment(restClient, sessionId); + + Assert.True(logOutput.Length > 0); + var successMessagePrefix = $"The Elastic Beanstalk Environment {_fixture.EnvironmentName} has been successfully updated"; + var deployStdOutput = logOutput.ToString().Split(Environment.NewLine); + var successMessage = deployStdOutput.First(line => line.Trim().StartsWith(successMessagePrefix)); + Assert.False(string.IsNullOrEmpty(successMessage)); + + var expectedVersionLabel = successMessage.Split(" ").Last(); + Assert.True(await _fixture.EBHelper.VerifyEnvironmentVersionLabel(_fixture.EnvironmentName, expectedVersionLabel)); + } + finally + { + cancelSource.Cancel(); + } + } + + private async Task WaitForDeployment(RestAPIClient restApiClient, string sessionId) + { + // Do an initial delay to avoid a race condition of the status being checked before the deployment has kicked off. + await Task.Delay(TimeSpan.FromSeconds(3)); + + await WaitUntilHelper.WaitUntil(async () => + { + DeploymentStatus status = (await restApiClient.GetDeploymentStatusAsync(sessionId)).Status; ; + return status != DeploymentStatus.Executing; + }, TimeSpan.FromSeconds(1), TimeSpan.FromMinutes(15)); + + return (await restApiClient.GetDeploymentStatusAsync(sessionId)).Status; + } + + private async Task WaitTillServerModeReady(RestAPIClient restApiClient) + { + await WaitUntilHelper.WaitUntil(async () => + { + SystemStatus status = SystemStatus.Error; + try + { + status = (await restApiClient.HealthAsync()).Status; + } + catch (Exception) + { + } + + return status == SystemStatus.Ready; + }, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(10)); + } + + private Task ResolveCredentials() + { + var testCredentials = FallbackCredentialsFactory.GetCredentials(); + return Task.FromResult(testCredentials); + } + } +} diff --git a/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/TestContextFixture.cs b/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/TestContextFixture.cs new file mode 100644 index 000000000..65bd8f809 --- /dev/null +++ b/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/TestContextFixture.cs @@ -0,0 +1,119 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Amazon.ElasticBeanstalk; +using Amazon.IdentityManagement; +using AWS.Deploy.CLI.Common.UnitTests.IO; +using AWS.Deploy.CLI.Extensions; +using AWS.Deploy.CLI.IntegrationTests.Extensions; +using AWS.Deploy.CLI.IntegrationTests.Helpers; +using AWS.Deploy.CLI.IntegrationTests.Services; +using AWS.Deploy.Common.IO; +using AWS.Deploy.Orchestration.Data; +using AWS.Deploy.Orchestration.Utilities; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace AWS.Deploy.CLI.IntegrationTests.BeanstalkBackwardsCompatibilityTests +{ + public class TestContextFixture : IAsyncLifetime + { + public readonly App App; + public readonly HttpHelper HttpHelper; + public readonly IAWSResourceQueryer AWSResourceQueryer; + public readonly TestAppManager TestAppManager; + public readonly IDirectoryManager DirectoryManager; + public readonly ICommandLineWrapper CommandLineWrapper; + public readonly IZipFileManager ZipFileManager; + public readonly IToolInteractiveService ToolInteractiveService; + public readonly InMemoryInteractiveService InteractiveService; + public readonly ElasticBeanstalkHelper EBHelper; + public readonly IAMHelper IAMHelper; + + public readonly string ApplicationName; + public readonly string EnvironmentName; + public readonly string VersionLabel; + public string EnvironmentId; + + public TestContextFixture() + { + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddCustomServices(); + serviceCollection.AddTestServices(); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + + App = serviceProvider.GetService(); + Assert.NotNull(App); + + InteractiveService = serviceProvider.GetService(); + Assert.NotNull(InteractiveService); + + ToolInteractiveService = serviceProvider.GetService(); + + AWSResourceQueryer = serviceProvider.GetService(); + Assert.NotNull(AWSResourceQueryer); + + CommandLineWrapper = serviceProvider.GetService(); + Assert.NotNull(CommandLineWrapper); + + ZipFileManager = serviceProvider.GetService(); + Assert.NotNull(ZipFileManager); + + DirectoryManager = serviceProvider.GetService(); + Assert.NotNull(DirectoryManager); + + HttpHelper = new HttpHelper(InteractiveService); + TestAppManager = new TestAppManager(); + + var suffix = Guid.NewGuid().ToString().Split('-').Last(); + ApplicationName = $"application{suffix}"; + EnvironmentName = $"environment{suffix}"; + VersionLabel = $"v-{suffix}"; + + EBHelper = new ElasticBeanstalkHelper(new AmazonElasticBeanstalkClient(), AWSResourceQueryer, ToolInteractiveService); + IAMHelper = new IAMHelper(new AmazonIdentityManagementServiceClient(), AWSResourceQueryer, ToolInteractiveService); + } + + public async Task InitializeAsync() + { + await IAMHelper.CreateRoleForBeanstalkEnvionmentDeployment("aws-elasticbeanstalk-ec2-role"); + + var projectPath = TestAppManager.GetProjectPath(Path.Combine("testapps", "WebAppNoDockerFile", "WebAppNoDockerFile.csproj")); + var publishDirectoryInfo = DirectoryManager.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + var zipFilePath = $"{publishDirectoryInfo.FullName}.zip"; + + var publishCommand = + $"dotnet publish \"{projectPath}\"" + + $" -o \"{publishDirectoryInfo}\"" + + $" -c release"; + + var result = await CommandLineWrapper.TryRunWithResult(publishCommand, streamOutputToInteractiveService: true); + Assert.Equal(0, result.ExitCode); + + await ZipFileManager.CreateFromDirectory(publishDirectoryInfo.FullName, zipFilePath); + + await EBHelper.CreateApplicationAsync(ApplicationName); + await EBHelper.CreateApplicationVersionAsync(ApplicationName, VersionLabel, zipFilePath); + var success = await EBHelper.CreateEnvironmentAsync(ApplicationName, EnvironmentName, VersionLabel); + Assert.True(success); + + var environmentDescription = await AWSResourceQueryer.DescribeElasticBeanstalkEnvironment(EnvironmentName); + EnvironmentId = environmentDescription.EnvironmentId; + + // URL could take few more minutes to come live, therefore, we want to wait and keep trying for a specified timeout + await HttpHelper.WaitUntilSuccessStatusCode(environmentDescription.CNAME, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(5)); + } + + public async Task DisposeAsync() + { + var success = await EBHelper.DeleteApplication(ApplicationName, EnvironmentName); + Assert.True(success); + } + } +} diff --git a/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/TestContextFixtureCollection.cs b/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/TestContextFixtureCollection.cs new file mode 100644 index 000000000..b408e1968 --- /dev/null +++ b/test/AWS.Deploy.CLI.IntegrationTests/BeanstalkBackwardsCompatibilityTests/TestContextFixtureCollection.cs @@ -0,0 +1,12 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Xunit; + +namespace AWS.Deploy.CLI.IntegrationTests.BeanstalkBackwardsCompatibilityTests +{ + [CollectionDefinition(nameof(TestContextFixture), DisableParallelization = true)] + public class TestContextFixtureCollection : ICollectionFixture + { + } +} diff --git a/test/AWS.Deploy.CLI.IntegrationTests/Helpers/ElasticBeanstalkHelper.cs b/test/AWS.Deploy.CLI.IntegrationTests/Helpers/ElasticBeanstalkHelper.cs new file mode 100644 index 000000000..40f6e6b8d --- /dev/null +++ b/test/AWS.Deploy.CLI.IntegrationTests/Helpers/ElasticBeanstalkHelper.cs @@ -0,0 +1,200 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Amazon.ElasticBeanstalk; +using Amazon.ElasticBeanstalk.Model; +using Amazon.S3; +using Amazon.S3.Transfer; +using AWS.Deploy.Common.IO; +using AWS.Deploy.Orchestration.Data; + +namespace AWS.Deploy.CLI.IntegrationTests.Helpers +{ + public class ElasticBeanstalkHelper + { + private readonly IAmazonElasticBeanstalk _client; + private readonly IAWSResourceQueryer _awsResourceQueryer; + private readonly IToolInteractiveService _interactiveService; + + public ElasticBeanstalkHelper(IAmazonElasticBeanstalk client, IAWSResourceQueryer awsResourceQueryer, IToolInteractiveService toolInteractiveService) + { + _client = client; + _awsResourceQueryer = awsResourceQueryer; + _interactiveService = toolInteractiveService; + } + + public async Task CreateApplicationAsync(string applicationName) + { + _interactiveService.WriteLine($"Create Elastic Beanstalk application: {applicationName}"); + await _client.CreateApplicationAsync(new CreateApplicationRequest + { + ApplicationName = applicationName, + Description = "aws-dotnet-deploy-integ-test" + }); + } + + public async Task CreateApplicationVersionAsync(string applicationName, string versionLabel, string deploymentPackage) + { + _interactiveService.WriteLine($"Creating new application version for {applicationName}: {versionLabel}"); + + var bucketName = (await _client.CreateStorageLocationAsync()).S3Bucket; + + var key = string.Format("{0}/AWSDeploymentArchive_{0}_{1}{2}", + applicationName.Replace(' ', '-'), + versionLabel.Replace(' ', '-'), + new FileManager().GetExtension(deploymentPackage)); + + await UploadToS3Async(bucketName, key, deploymentPackage); + + await _client.CreateApplicationVersionAsync(new CreateApplicationVersionRequest + { + ApplicationName = applicationName, + VersionLabel = versionLabel, + SourceBundle = new S3Location { S3Bucket = bucketName, S3Key = key } + }); + } + + public async Task CreateEnvironmentAsync(string applicationName, string environmentName, string versionLabel) + { + _interactiveService.WriteLine($"Creating new Elastic Beanstalk environment {environmentName} with versionLabel {versionLabel}"); + + var startingEventDate = DateTime.Now; + + await _client.CreateEnvironmentAsync(new CreateEnvironmentRequest + { + ApplicationName = applicationName, + EnvironmentName = environmentName, + VersionLabel = versionLabel, + PlatformArn = (await _awsResourceQueryer.GetLatestElasticBeanstalkPlatformArn()).PlatformArn, + OptionSettings = new List + { + new ConfigurationOptionSetting("aws:autoscaling:launchconfiguration", "IamInstanceProfile", "aws-elasticbeanstalk-ec2-role"), + new ConfigurationOptionSetting("aws:elasticbeanstalk:healthreporting:system", "SystemType", "basic") + } + }); + + return await WaitForEnvironmentCreateCompletion(applicationName, environmentName, startingEventDate); + } + + public async Task DeleteApplication(string applicationName, string environmentName) + { + _interactiveService.WriteLine($"Deleting Elastic Beanstalk application: {applicationName}"); + _interactiveService.WriteLine($"Deleting Elastic Beanstalk environment: {environmentName}"); + await _client.DeleteApplicationAsync(new DeleteApplicationRequest + { + ApplicationName = applicationName, + TerminateEnvByForce = true + }); + + return await WaitForEnvironmentDeletion(environmentName); + } + + public async Task VerifyEnvironmentVersionLabel(string environmentName, string expectedVersionLabel) + { + var envDescription = await _awsResourceQueryer.DescribeElasticBeanstalkEnvironment(environmentName); + _interactiveService.WriteLine($"The Elastic Beanstalk environment is pointing to \"{envDescription.VersionLabel}\" version label"); + return string.Equals(envDescription.VersionLabel, expectedVersionLabel); + } + + private async Task WaitForEnvironmentCreateCompletion(string applicationName, string environmentName, DateTime startingEventDate) + { + _interactiveService.WriteLine("Waiting for environment update to complete"); + + var success = true; + var environment = new EnvironmentDescription(); + var lastEventDate = startingEventDate; + var requestEvents = new DescribeEventsRequest + { + ApplicationName = applicationName, + EnvironmentName = environmentName + }; + var requestEnvironment = new DescribeEnvironmentsRequest + { + ApplicationName = applicationName, + EnvironmentNames = new List { environmentName } + }; + + do + { + Thread.Sleep(5000); + + var responseEnvironments = await _client.DescribeEnvironmentsAsync(requestEnvironment); + environment = responseEnvironments.Environments[0]; + + requestEvents.StartTimeUtc = lastEventDate; + var responseEvents = await _client.DescribeEventsAsync(requestEvents); + if (responseEvents.Events.Any()) + { + for (var i = responseEvents.Events.Count - 1; i >= 0; i--) + { + var evnt = responseEvents.Events[i]; + if (evnt.EventDate <= lastEventDate) + continue; + + _interactiveService.WriteLine(evnt.EventDate.ToLocalTime() + " " + evnt.Severity + " " + evnt.Message); + if (evnt.Severity == EventSeverity.ERROR || evnt.Severity == EventSeverity.FATAL) + { + success = false; + } + } + + lastEventDate = responseEvents.Events[0].EventDate; + } + } while (environment.Status == EnvironmentStatus.Launching || environment.Status == EnvironmentStatus.Updating); + + return success; + } + + private async Task WaitForEnvironmentDeletion(string environmentName) + { + var attemptCount = 0; + const int maxAttempts = 7; + + while (attemptCount < maxAttempts) + { + attemptCount += 1; + var response = await _client.DescribeEnvironmentsAsync(new DescribeEnvironmentsRequest + { + EnvironmentNames = new List { environmentName } + }); + + if (!response.Environments.Any() || response.Environments.Single().Status == EnvironmentStatus.Terminated) + return true; + + await Task.Delay(GetWaitTime(attemptCount)); + } + + return false; + } + + private TimeSpan GetWaitTime(int attemptCount) + { + var waitTime = Math.Pow(2, attemptCount) * 5; + return TimeSpan.FromSeconds(waitTime); + } + + private async Task UploadToS3Async(string bucketName, string key, string filePath) + { + _interactiveService.WriteLine("Uploading application deployment package to S3..."); + + using (var stream = File.OpenRead(filePath)) + { + var request = new TransferUtilityUploadRequest() + { + BucketName = bucketName, + Key = key, + InputStream = stream + }; + + var s3Client = new AmazonS3Client(); + await new TransferUtility(s3Client).UploadAsync(request); + } + } + } +} diff --git a/test/AWS.Deploy.CLI.IntegrationTests/Helpers/IAMHelper.cs b/test/AWS.Deploy.CLI.IntegrationTests/Helpers/IAMHelper.cs new file mode 100644 index 000000000..37301310f --- /dev/null +++ b/test/AWS.Deploy.CLI.IntegrationTests/Helpers/IAMHelper.cs @@ -0,0 +1,99 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Linq; +using System.Threading.Tasks; +using Amazon.IdentityManagement; +using Amazon.IdentityManagement.Model; +using AWS.Deploy.Orchestration.Data; + +namespace AWS.Deploy.CLI.IntegrationTests.Helpers +{ + public class IAMHelper + { + private readonly IAmazonIdentityManagementService _client; + private readonly IToolInteractiveService _interactiveService; + private readonly IAWSResourceQueryer _awsResourceQueryer; + + public IAMHelper(IAmazonIdentityManagementService client, IAWSResourceQueryer awsResourceQueryer, IToolInteractiveService toolInteractiveService) + { + _client = client; + _awsResourceQueryer = awsResourceQueryer; + _interactiveService = toolInteractiveService; + } + + public async Task CreateRoleForBeanstalkEnvionmentDeployment(string roleName) + { + _interactiveService.WriteLine($"Creating role {roleName} for deployment to Elastic Beanstalk environemnt"); + var existingRoles = await _awsResourceQueryer.ListOfIAMRoles("ec2.amazonaws.com"); + var role = existingRoles.FirstOrDefault(x => string.Equals(roleName, x.RoleName)); + if (role != null) + { + _interactiveService.WriteLine($" The role {roleName} already exists"); + } + else + { + var assumeRolepolicyDocument = + @"{ + 'Version':'2008-10-17', + 'Statement':[ + { + 'Effect':'Allow', + 'Principal':{ + 'Service':'ec2.amazonaws.com' + }, + 'Action':'sts:AssumeRole' + } + ] + }"; + + await _client.CreateRoleAsync(new CreateRoleRequest + { + RoleName = roleName, + AssumeRolePolicyDocument = assumeRolepolicyDocument.Replace("'", "\"") + }); + } + + InstanceProfile instanceProfile = null; + try + { + instanceProfile = (await _client.GetInstanceProfileAsync(new GetInstanceProfileRequest() + { + InstanceProfileName = roleName + })).InstanceProfile; + } + catch (NoSuchEntityException) { } + + // Check to see if an instance profile exists for this role and if not create it. + if (instanceProfile == null) + { + _interactiveService.WriteLine($"Creating new IAM Instance Profile {roleName}"); + await _client.CreateInstanceProfileAsync(new CreateInstanceProfileRequest() + { + InstanceProfileName = roleName + }); + + _interactiveService.WriteLine($"Attaching IAM role {roleName} to Instance Profile {roleName}"); + await _client.AddRoleToInstanceProfileAsync(new AddRoleToInstanceProfileRequest() + { + RoleName = roleName, + InstanceProfileName = roleName + }); + } + // If it already exists see if this role is already assigned and if not assign it. + else + { + _interactiveService.WriteLine($"IAM Instance Profile {roleName} already exists"); + if (instanceProfile.Roles.FirstOrDefault(x => x.RoleName == roleName) == null) + { + _interactiveService.WriteLine($"Attaching IAM role {roleName} to Instance Profile {roleName}"); + await _client.AddRoleToInstanceProfileAsync(new AddRoleToInstanceProfileRequest() + { + RoleName = roleName, + InstanceProfileName = roleName + }); + } + } + } + } +} diff --git a/test/AWS.Deploy.CLI.IntegrationTests/ServerModeTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/ServerModeTests.cs index c2fecbce7..d8fb5b260 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/ServerModeTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/ServerModeTests.cs @@ -4,16 +4,12 @@ using System; using System.IO; using System.Linq; -using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using Amazon.CloudFormation; -using Amazon.CloudFormation.Model; -using Amazon.ECS; using Amazon.Runtime; -using Amazon.Runtime.CredentialManagement; using AWS.Deploy.CLI.Commands; using AWS.Deploy.CLI.Common.UnitTests.IO; using AWS.Deploy.CLI.Extensions; @@ -235,6 +231,7 @@ public async Task WebFargateDeploymentNoConfigChanges() Assert.Equal(fargateRecommendation.ShortDescription, existingDeployment.ShortDescription); Assert.Equal(fargateRecommendation.Description, existingDeployment.Description); Assert.Equal(fargateRecommendation.TargetService, existingDeployment.TargetService); + Assert.Equal(DeploymentTypes.CloudFormationStack, existingDeployment.DeploymentType); } finally {