diff --git a/.github/workflows/codebuild-ci.yml b/.github/workflows/codebuild-ci.yml new file mode 100644 index 000000000..298563c5e --- /dev/null +++ b/.github/workflows/codebuild-ci.yml @@ -0,0 +1,26 @@ +name: AWS CodeBuild CI + +on: + push: + branches: + - main + - dev + pull_request: + branches: + - main + - dev + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.CI_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.CI_AWS_ACCESS_KEY_SECRET }} + aws-region: us-west-2 + - name: Run CodeBuild + uses: aws-actions/aws-codebuild-run-build@v1.0.3 + with: + project-name: aws-dotnet-deploy-ci diff --git a/src/AWS.Deploy.CLI/AWSUtilities.cs b/src/AWS.Deploy.CLI/AWSUtilities.cs index 66b5c382b..4e7cc7d04 100644 --- a/src/AWS.Deploy.CLI/AWSUtilities.cs +++ b/src/AWS.Deploy.CLI/AWSUtilities.cs @@ -1,6 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -8,7 +9,9 @@ using Amazon.Runtime.CredentialManagement; using Amazon.EC2.Model; using System.IO; +using AWS.Deploy.CLI.Commands.CommandHandlerInput; using AWS.Deploy.CLI.Utilities; +using AWS.Deploy.Common; using AWS.Deploy.Common.IO; namespace AWS.Deploy.CLI @@ -25,7 +28,10 @@ public class AWSUtilities : IAWSUtilities private readonly IConsoleUtilities _consoleUtilities; private readonly IDirectoryManager _directoryManager; - public AWSUtilities(IToolInteractiveService toolInteractiveService, IConsoleUtilities consoleUtilities, IDirectoryManager directoryManager) + public AWSUtilities( + IToolInteractiveService toolInteractiveService, + IConsoleUtilities consoleUtilities, + IDirectoryManager directoryManager) { _toolInteractiveService = toolInteractiveService; _consoleUtilities = consoleUtilities; @@ -36,51 +42,52 @@ public async Task ResolveAWSCredentials(string? profileName, str { async Task Resolve() { - var chain = new CredentialProfileStoreChain(); + var chain = new CredentialProfileStoreChain(); if (!string.IsNullOrEmpty(profileName) && chain.TryGetAWSCredentials(profileName, out var profileCredentials) && // Skip checking CanLoadCredentials for AssumeRoleAWSCredentials because it might require an MFA token and the callback hasn't been setup yet. (profileCredentials is AssumeRoleAWSCredentials || await CanLoadCredentials(profileCredentials))) - { + { _toolInteractiveService.WriteLine($"Configuring AWS Credentials from Profile {profileName}."); return profileCredentials; } - if (!string.IsNullOrEmpty(lastUsedProfileName) && + if (!string.IsNullOrEmpty(lastUsedProfileName) && chain.TryGetAWSCredentials(lastUsedProfileName, out var lastUsedCredentials) && await CanLoadCredentials(lastUsedCredentials)) - { - _toolInteractiveService.WriteLine($"Configuring AWS Credentials with previous configured profile value {lastUsedProfileName}."); + { + _toolInteractiveService.WriteLine($"Configuring AWS Credentials with previous configured profile value {lastUsedProfileName}."); return lastUsedCredentials; - } + } - try - { + try + { var fallbackCredentials = FallbackCredentialsFactory.GetCredentials(); if (await CanLoadCredentials(fallbackCredentials)) - { - _toolInteractiveService.WriteLine("Configuring AWS Credentials using AWS SDK credential search."); + { + _toolInteractiveService.WriteLine("Configuring AWS Credentials using AWS SDK credential search."); return fallbackCredentials; + } + } + catch (AmazonServiceException ex) + { + // FallbackCredentialsFactory throws an exception if no credentials are found. Burying exception because if no credentials are found + // we want to continue and ask the user to select a profile. + _toolInteractiveService.WriteDebugLine(ex.PrettyPrint()); } - } - catch (AmazonServiceException) - { - // FallbackCredentialsFactory throws an exception if no credentials are found. Burying exception because if no credentials are found - // we want to continue and ask the user to select a profile. - } - var sharedCredentials = new SharedCredentialsFile(); - if (sharedCredentials.ListProfileNames().Count == 0) - { - throw new NoAWSCredentialsFoundException("Unable to resolve AWS credentials to access AWS."); - } + var sharedCredentials = new SharedCredentialsFile(); + if (sharedCredentials.ListProfileNames().Count == 0) + { + throw new NoAWSCredentialsFoundException("Unable to resolve AWS credentials to access AWS."); + } - var selectedProfileName = _consoleUtilities.AskUserToChoose(sharedCredentials.ListProfileNames(), "Select AWS Credentials Profile", null); + var selectedProfileName = _consoleUtilities.AskUserToChoose(sharedCredentials.ListProfileNames(), "Select AWS Credentials Profile", null); if (chain.TryGetAWSCredentials(selectedProfileName, out var selectedProfileCredentials) && (await CanLoadCredentials(selectedProfileCredentials))) - { + { return selectedProfileCredentials; } @@ -100,16 +107,14 @@ await CanLoadCredentials(lastUsedCredentials)) private async Task CanLoadCredentials(AWSCredentials credentials) { - if (null == credentials) - return false; - try { await credentials.GetCredentialsAsync(); return true; } - catch + catch (Exception ex) { + _toolInteractiveService.WriteDebugLine(ex.PrettyPrint()); return false; } } diff --git a/src/AWS.Deploy.CLI/CloudFormation/StackEventMonitor.cs b/src/AWS.Deploy.CLI/CloudFormation/StackEventMonitor.cs index b4b130402..12dccfc50 100644 --- a/src/AWS.Deploy.CLI/CloudFormation/StackEventMonitor.cs +++ b/src/AWS.Deploy.CLI/CloudFormation/StackEventMonitor.cs @@ -31,11 +31,13 @@ internal class StackEventMonitor private readonly IAmazonCloudFormation _cloudFormationClient; private readonly HashSet _processedEventIds = new HashSet(); private readonly IConsoleUtilities _consoleUtilities; + private readonly IToolInteractiveService _interactiveService; - public StackEventMonitor(string stackName, IAWSClientFactory awsClientFactory, IConsoleUtilities consoleUtilities) + public StackEventMonitor(string stackName, IAWSClientFactory awsClientFactory, IConsoleUtilities consoleUtilities, IToolInteractiveService interactiveService) { _stackName = stackName; _consoleUtilities = consoleUtilities; + _interactiveService = interactiveService; _cloudFormationClient = awsClientFactory.GetAWSClient(); } @@ -112,10 +114,12 @@ private async Task ReadNewEventsAsync() catch (AmazonCloudFormationException exception) when (exception.ErrorCode.Equals("ValidationError") && exception.Message.Equals($"Stack [{_stackName}] does not exist")) { // Stack is deleted, there could be some missed events between the last poll timestamp and DELETE_COMPLETE + _interactiveService.WriteDebugLine(exception.PrettyPrint()); } - catch (AmazonCloudFormationException) + catch (AmazonCloudFormationException exception) { // Other AmazonCloudFormationException + _interactiveService.WriteDebugLine(exception.PrettyPrint()); } foreach (var stackEvent in stackEvents.OrderBy(e => e.Timestamp)) diff --git a/src/AWS.Deploy.CLI/Commands/DeleteDeploymentCommand.cs b/src/AWS.Deploy.CLI/Commands/DeleteDeploymentCommand.cs index f35db27ba..baf5af13a 100644 --- a/src/AWS.Deploy.CLI/Commands/DeleteDeploymentCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/DeleteDeploymentCommand.cs @@ -64,7 +64,7 @@ public async Task ExecuteAsync(string stackName) } _interactiveService.WriteLine($"{stackName}: deleting..."); - var monitor = new StackEventMonitor(stackName, _awsClientFactory, _consoleUtilities); + var monitor = new StackEventMonitor(stackName, _awsClientFactory, _consoleUtilities, _interactiveService); try { @@ -171,6 +171,7 @@ private async Task WaitForStackDelete(string stackName) } catch (AmazonCloudFormationException exception) when (exception.ErrorCode.Equals("ValidationError") && exception.Message.Equals($"Stack with id {stackName} does not exist")) { + _interactiveService.WriteDebugLine(exception.PrettyPrint()); shouldRetry = false; } catch (AmazonCloudFormationException exception) when (exception.ErrorCode.Equals("Throttling")) diff --git a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs index 2b5390ed4..5d3f1d569 100644 --- a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs @@ -164,7 +164,7 @@ private void DisplayOutputResources(List displayedResourc // Get Cloudformation stack name. var cloudApplicationName = GetCloudApplicationName(stackName, userDeploymentSettings, compatibleApplications); - + // Find existing application with the same CloudFormation stack name. var deployedApplication = allDeployedApplications.FirstOrDefault(x => string.Equals(x.Name, cloudApplicationName)); @@ -354,8 +354,9 @@ private void ConfigureDeploymentFromConfigFile(Recommendation recommendation, Us throw new InvalidOverrideValueException($"Invalid value {optionSettingValue} for option setting item {optionSettingJsonPath}"); } } - catch (Exception) + catch (Exception exception) { + _toolInteractiveService.WriteDebugLine(exception.PrettyPrint()); throw new InvalidOverrideValueException($"Invalid value {optionSettingValue} for option setting item {optionSettingJsonPath}"); } @@ -483,7 +484,10 @@ private string AskUserForCloudApplicationName(ProjectDefinition project, List { await ShutDownHost(host, cancellationToken); }; } - catch (Exception) + catch (Exception exception) { + _interactiveService.WriteDebugLine(exception.PrettyPrint()); return; } diff --git a/src/AWS.Deploy.CLI/Utilities/CommandLineWrapper.cs b/src/AWS.Deploy.CLI/Utilities/CommandLineWrapper.cs index c55882ab1..e309eb5d7 100644 --- a/src/AWS.Deploy.CLI/Utilities/CommandLineWrapper.cs +++ b/src/AWS.Deploy.CLI/Utilities/CommandLineWrapper.cs @@ -114,7 +114,12 @@ public async Task Run( while (true) { if (process.HasExited) + { + // In some cases, process might have exited but OutputDataReceived or ErrorDataReceived could still be writing + // asynchronously, adding a delay should cover most of the cases. + await Task.Delay(TimeSpan.FromSeconds(1), cancelToken); break; + } await Task.Delay(TimeSpan.FromMilliseconds(50), cancelToken); } diff --git a/src/AWS.Deploy.Orchestration/CdkProjectHandler.cs b/src/AWS.Deploy.Orchestration/CdkProjectHandler.cs index 81c4db0ac..6c6196f69 100644 --- a/src/AWS.Deploy.Orchestration/CdkProjectHandler.cs +++ b/src/AWS.Deploy.Orchestration/CdkProjectHandler.cs @@ -16,7 +16,7 @@ public interface ICdkProjectHandler Task ConfigureCdkProject(OrchestratorSession session, CloudApplication cloudApplication, Recommendation recommendation); string CreateCdkProject(Recommendation recommendation, OrchestratorSession session, string? saveDirectoryPath = null); Task DeployCdkProject(OrchestratorSession session, string cdkProjectPath, Recommendation recommendation); - void DeleteTemporaryCdkProject(string cdkProjectPath); + void DeleteTemporaryCdkProject(OrchestratorSession session, string cdkProjectPath); } public class CdkProjectHandler : ICdkProjectHandler @@ -83,6 +83,7 @@ await _commandLineWrapper.Run($"npx cdk bootstrap aws://{session.AWSAccountId}/{ workingDirectory: cdkProjectPath, environmentVariables: environmentVariables, needAwsCredentials: true, + redirectIO: true, streamOutputToInteractiveService: true); if (cdkDeploy.ExitCode != 0) @@ -118,7 +119,7 @@ public string CreateCdkProject(Recommendation recommendation, OrchestratorSessio return saveCdkDirectoryPath; } - public void DeleteTemporaryCdkProject(string cdkProjectPath) + public void DeleteTemporaryCdkProject(OrchestratorSession session, string cdkProjectPath) { var parentPath = Path.GetFullPath(Constants.CDK.ProjectsDirectory); cdkProjectPath = Path.GetFullPath(cdkProjectPath); @@ -130,8 +131,9 @@ public void DeleteTemporaryCdkProject(string cdkProjectPath) { _directoryManager.Delete(cdkProjectPath, true); } - catch (Exception) + catch (Exception exception) { + _interactiveService.LogDebugLine(exception.PrettyPrint()); _interactiveService.LogErrorMessageLine($"We were unable to delete the temporary project that was created for this deployment. Please manually delete it at this location: {cdkProjectPath}"); } } diff --git a/src/AWS.Deploy.Orchestration/CustomRecipeLocator.cs b/src/AWS.Deploy.Orchestration/CustomRecipeLocator.cs index 36dd57175..7d348364d 100644 --- a/src/AWS.Deploy.Orchestration/CustomRecipeLocator.cs +++ b/src/AWS.Deploy.Orchestration/CustomRecipeLocator.cs @@ -31,7 +31,7 @@ public class CustomRecipeLocator : ICustomRecipeLocator private readonly ICommandLineWrapper _commandLineWrapper; private readonly IDeploymentManifestEngine _deploymentManifestEngine; private readonly IDirectoryManager _directoryManager; - + public CustomRecipeLocator(IDeploymentManifestEngine deploymentManifestEngine, IOrchestratorInteractiveService orchestratorInteractiveService, ICommandLineWrapper commandLineWrapper, IDirectoryManager directoryManager) { @@ -109,7 +109,7 @@ private async Task> LocateAlternateRecipePaths(string targetApplica rootDirectoryPath = solutionDirectoryPath; } - return GetRecipePathsFromRootDirectory(rootDirectoryPath); + return GetRecipePathsFromRootDirectory(rootDirectoryPath); } /// @@ -141,10 +141,15 @@ private List GetRecipePathsFromRootDirectory(string? rootDirectoryPath) private async Task GetSourceControlRootDirectory(string currentDirectoryPath) { var possibleRootDirectoryPath = currentDirectoryPath; - while (currentDirectoryPath != null && await IsDirectoryUnderSourceControl(currentDirectoryPath)) + while (await IsDirectoryUnderSourceControl(currentDirectoryPath)) { possibleRootDirectoryPath = currentDirectoryPath; - currentDirectoryPath = _directoryManager.GetDirectoryInfo(currentDirectoryPath).Parent.FullName; + var currentDirectoryInfo = _directoryManager.GetDirectoryInfo(currentDirectoryPath); + if (currentDirectoryInfo.Parent == null) + { + break; + } + currentDirectoryPath = currentDirectoryInfo.Parent.FullName; } return possibleRootDirectoryPath; } diff --git a/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs b/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs index eb96e213e..de1dc8324 100644 --- a/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs +++ b/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs @@ -62,7 +62,7 @@ public async Task BuildDockerImage(CloudApplication cloudApplication, Re recommendation.DeploymentBundle.DockerExecutionDirectory = dockerExecutionDirectory; - var result = await _commandLineWrapper.TryRunWithResult(dockerBuildCommand, dockerExecutionDirectory, redirectIO: false); + var result = await _commandLineWrapper.TryRunWithResult(dockerBuildCommand, dockerExecutionDirectory, streamOutputToInteractiveService: true); if (result.ExitCode != 0) { throw new DockerBuildFailedException(result.StandardError ?? ""); @@ -117,7 +117,7 @@ public async Task CreateDotnetPublishZip(Recommendation recommendation) publishCommand += " --self-contained true"; } - var result = await _commandLineWrapper.TryRunWithResult(publishCommand, redirectIO: false); + var result = await _commandLineWrapper.TryRunWithResult(publishCommand, streamOutputToInteractiveService: true); if (result.ExitCode != 0) { throw new DotnetPublishFailedException(result.StandardError ?? ""); @@ -202,7 +202,7 @@ private async Task InitiateDockerLogin() var decodedTokens = authToken.Split(':'); var dockerLoginCommand = $"docker login --username {decodedTokens[0]} --password {decodedTokens[1]} {authorizationTokens[0].ProxyEndpoint}"; - var result = await _commandLineWrapper.TryRunWithResult(dockerLoginCommand); + var result = await _commandLineWrapper.TryRunWithResult(dockerLoginCommand, streamOutputToInteractiveService: true); if (result.ExitCode != 0) throw new DockerLoginFailedException("Failed to login to Docker"); @@ -225,7 +225,7 @@ private async Task SetupECRRepository(string ecrRepositoryName) private async Task TagDockerImage(string sourceTagName, string targetTagName) { var dockerTagCommand = $"docker tag {sourceTagName} {targetTagName}"; - var result = await _commandLineWrapper.TryRunWithResult(dockerTagCommand); + var result = await _commandLineWrapper.TryRunWithResult(dockerTagCommand, streamOutputToInteractiveService: true); if (result.ExitCode != 0) throw new DockerTagFailedException("Failed to tag Docker image"); @@ -234,7 +234,7 @@ private async Task TagDockerImage(string sourceTagName, string targetTagName) private async Task PushDockerImage(string targetTagName) { var dockerPushCommand = $"docker push {targetTagName}"; - var result = await _commandLineWrapper.TryRunWithResult(dockerPushCommand, redirectIO: false); + var result = await _commandLineWrapper.TryRunWithResult(dockerPushCommand, streamOutputToInteractiveService: true); if (result.ExitCode != 0) throw new DockerPushFailedException("Failed to push Docker Image"); diff --git a/src/AWS.Deploy.Orchestration/Orchestrator.cs b/src/AWS.Deploy.Orchestration/Orchestrator.cs index fbc7b71dd..5faadb96a 100644 --- a/src/AWS.Deploy.Orchestration/Orchestrator.cs +++ b/src/AWS.Deploy.Orchestration/Orchestrator.cs @@ -171,7 +171,7 @@ public async Task DeployRecommendation(CloudApplication cloudApplication, Recomm } finally { - _cdkProjectHandler.DeleteTemporaryCdkProject(cdkProject); + _cdkProjectHandler.DeleteTemporaryCdkProject(_session, cdkProject); } break; default: @@ -235,16 +235,18 @@ public async Task CreateDotnetPublishDeploymentBundle(Recommendation recom { await _deploymentBundleHandler.CreateDotnetPublishZip(recommendation); } - catch (DotnetPublishFailedException ex) + catch (DotnetPublishFailedException exception) { _interactiveService.LogErrorMessageLine("We were unable to package the application using 'dotnet publish' due to the following error:"); - _interactiveService.LogErrorMessageLine(ex.Message); + _interactiveService.LogErrorMessageLine(exception.Message); + _interactiveService.LogDebugLine(exception.PrettyPrint()); return false; } - catch (FailedToCreateZipFileException) + catch (FailedToCreateZipFileException exception) { _interactiveService.LogErrorMessageLine("We were unable to create a zip archive of the packaged application."); _interactiveService.LogErrorMessageLine("Normally this indicates a problem running the \"zip\" utility. Make sure that application is installed and available in your PATH."); + _interactiveService.LogDebugLine(exception.PrettyPrint()); return false; } diff --git a/src/AWS.Deploy.Orchestration/SystemCapabilities.cs b/src/AWS.Deploy.Orchestration/SystemCapabilities.cs index 1d0463d67..b4a7da946 100644 --- a/src/AWS.Deploy.Orchestration/SystemCapabilities.cs +++ b/src/AWS.Deploy.Orchestration/SystemCapabilities.cs @@ -1,15 +1,17 @@ +using System; + namespace AWS.Deploy.Orchestration { public class SystemCapabilities { - public bool NodeJsMinVersionInstalled { get; set; } + public Version? NodeJsVersion { get; set; } public DockerInfo DockerInfo { get; set; } public SystemCapabilities( - bool nodeJsMinVersionInstalled, + Version? nodeJsVersion, DockerInfo dockerInfo) { - NodeJsMinVersionInstalled = nodeJsMinVersionInstalled; + NodeJsVersion = nodeJsVersion; DockerInfo = dockerInfo; } } diff --git a/src/AWS.Deploy.Orchestration/SystemCapabilityEvaluator.cs b/src/AWS.Deploy.Orchestration/SystemCapabilityEvaluator.cs index 3cca05bbf..e13baf533 100644 --- a/src/AWS.Deploy.Orchestration/SystemCapabilityEvaluator.cs +++ b/src/AWS.Deploy.Orchestration/SystemCapabilityEvaluator.cs @@ -19,6 +19,7 @@ public interface ISystemCapabilityEvaluator public class SystemCapabilityEvaluator : ISystemCapabilityEvaluator { private readonly ICommandLineWrapper _commandLineWrapper; + private static readonly Version MinimumNodeJSVersion = new Version(10,13,0); public SystemCapabilityEvaluator(ICommandLineWrapper commandLineWrapper) { @@ -28,7 +29,7 @@ public SystemCapabilityEvaluator(ICommandLineWrapper commandLineWrapper) public async Task Evaluate() { var dockerTask = HasDockerInstalledAndRunning(); - var nodeTask = HasMinVersionNodeJs(); + var nodeTask = GetNodeJsVersion(); var capabilities = new SystemCapabilities(await nodeTask, await dockerTask); @@ -62,7 +63,7 @@ await _commandLineWrapper.Run( /// From https://docs.aws.amazon.com/cdk/latest/guide/work-with.html#work-with-prerequisites, /// min version is 10.3 /// - private async Task HasMinVersionNodeJs() + private async Task GetNodeJsVersion() { // run node --version to get the version var result = await _commandLineWrapper.TryRunWithResult("node --version"); @@ -73,9 +74,9 @@ private async Task HasMinVersionNodeJs() versionString = versionString.Substring(1, versionString.Length - 1); if (!result.Success || !Version.TryParse(versionString, out var version)) - return false; + return null; - return version.Major > 10 || version.Major == 10 && version.Minor >= 3; + return version; } /// @@ -85,13 +86,22 @@ public async Task> EvaluateSystemCapabilities(Recommendat { var capabilities = new List(); var systemCapabilities = await Evaluate(); - if (selectedRecommendation.Recipe.DeploymentType == DeploymentTypes.CdkProject && - !systemCapabilities.NodeJsMinVersionInstalled) + if (selectedRecommendation.Recipe.DeploymentType == DeploymentTypes.CdkProject) { - capabilities.Add(new SystemCapability("NodeJS", false, false) { - InstallationUrl = "https://nodejs.org/en/download/", - Message = "The selected deployment uses the AWS CDK, which requires version of Node.js higher than your current installation. The latest LTS version of Node.js is recommended and can be installed from https://nodejs.org/en/download/. Specifically, AWS CDK requires 10.3+ to work properly." - }); + if (systemCapabilities.NodeJsVersion == null) + { + capabilities.Add(new SystemCapability("NodeJS", false, false) { + InstallationUrl = "https://nodejs.org/en/download/", + Message = "The selected deployment uses the AWS CDK, which requires Node.js. The latest LTS version of Node.js is recommended and can be installed from https://nodejs.org/en/download/. Specifically, AWS CDK requires 10.13.0+ to work properly." + }); + } + else if (systemCapabilities.NodeJsVersion < MinimumNodeJSVersion) + { + capabilities.Add(new SystemCapability("NodeJS", false, false) { + InstallationUrl = "https://nodejs.org/en/download/", + Message = $"The selected deployment uses the AWS CDK, which requires version of Node.js higher than your current installation ({systemCapabilities.NodeJsVersion}). The latest LTS version of Node.js is recommended and can be installed from https://nodejs.org/en/download/. Specifically, AWS CDK requires 10.3+ to work properly." + }); + } } if (selectedRecommendation.Recipe.DeploymentBundle == DeploymentBundleTypes.Container) diff --git a/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs b/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs index d1b68d213..7976bd52e 100644 --- a/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs +++ b/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs @@ -124,16 +124,18 @@ public async Task> GetCompatibleApplications(List x.LastUpdatedTime) - .ToList(); ; + .ToList(); } } } diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/DeploymentManifestFile/DeploymentManifestFileTests.cs b/test/AWS.Deploy.CLI.Common.UnitTests/DeploymentManifestFile/DeploymentManifestFileTests.cs index 2dacfa7c0..6287658c5 100644 --- a/test/AWS.Deploy.CLI.Common.UnitTests/DeploymentManifestFile/DeploymentManifestFileTests.cs +++ b/test/AWS.Deploy.CLI.Common.UnitTests/DeploymentManifestFile/DeploymentManifestFileTests.cs @@ -105,7 +105,7 @@ public async Task GetRecipeDefinitionPaths() // Assert Assert.True(_fileManager.Exists(deploymentManifestFilePath)); - recipeDefinitionPaths.Count.ShouldEqual(3); + recipeDefinitionPaths.Count.ShouldEqual(3, $"Custom recipes found: {Environment.NewLine} {string.Join(Environment.NewLine, recipeDefinitionPaths)}"); recipeDefinitionPaths.ShouldContain(saveCdkDirectoryFullPath); recipeDefinitionPaths.ShouldContain(saveCdkDirectoryFullPath2); recipeDefinitionPaths.ShouldContain(saveCdkDirectoryFullPath3); diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/Services/InMemoryInteractiveService.cs b/test/AWS.Deploy.CLI.Common.UnitTests/Services/InMemoryInteractiveService.cs index 078629b60..4a384f232 100644 --- a/test/AWS.Deploy.CLI.Common.UnitTests/Services/InMemoryInteractiveService.cs +++ b/test/AWS.Deploy.CLI.Common.UnitTests/Services/InMemoryInteractiveService.cs @@ -4,18 +4,18 @@ using System; using System.Diagnostics; using System.IO; +using AWS.Deploy.Orchestration; namespace AWS.Deploy.CLI.IntegrationTests.Services { - public class InMemoryInteractiveService : IToolInteractiveService + public class InMemoryInteractiveService : IToolInteractiveService, IOrchestratorInteractiveService { + private static readonly object s_readToEndLocker = new object(); + private readonly object _writeLocker = new object(); + private readonly object _readLocker = new object(); private long _stdOutWriterPosition; - private long _stdErrorWriterPosition; - private long _stdDebugWriterPosition; private long _stdInReaderPosition; private readonly StreamWriter _stdOutWriter; - private readonly StreamWriter _stdErrorWriter; - private readonly StreamWriter _stdDebugWriter; private readonly StreamReader _stdInReader; /// @@ -29,30 +29,12 @@ public class InMemoryInteractiveService : IToolInteractiveService /// public StreamReader StdOutReader { get; } - /// - /// Allows consumers to read string which is written via - /// - public StreamReader StdErrorReader { get; } - - /// - /// Allows consumers to read string which is written via - /// - public StreamReader StdDebugReader { get; } - public InMemoryInteractiveService() { var stdOut = new MemoryStream(); _stdOutWriter = new StreamWriter(stdOut); StdOutReader = new StreamReader(stdOut); - var stdError = new MemoryStream(); - _stdErrorWriter = new StreamWriter(stdError); - StdErrorReader = new StreamReader(stdError); - - var stdDebug = new MemoryStream(); - _stdDebugWriter = new StreamWriter(stdDebug); - StdDebugReader = new StreamReader(stdDebug); - var stdIn = new MemoryStream(); _stdInReader = new StreamReader(stdIn); StdInWriter = new StreamWriter(stdIn); @@ -60,115 +42,90 @@ public InMemoryInteractiveService() public void Write(string message) { - Console.Write(message); - Debug.Write(message); + lock (_writeLocker) + { + Debug.Write(message); - // Save BaseStream position, it must be only modified by the consumer of StdOutReader - // After writing to the BaseStream, we will reset it to the original position. - var stdOutReaderPosition = StdOutReader.BaseStream.Position; + // Save BaseStream position, it must be only modified by the consumer of StdOutReader + // After writing to the BaseStream, we will reset it to the original position. + var stdOutReaderPosition = StdOutReader.BaseStream.Position; - // Reset the BaseStream to the last save position to continue writing from where we left. - _stdOutWriter.BaseStream.Position = _stdOutWriterPosition; - _stdOutWriter.Write(message); - _stdOutWriter.Flush(); + // Reset the BaseStream to the last save position to continue writing from where we left. + _stdOutWriter.BaseStream.Position = _stdOutWriterPosition; + _stdOutWriter.Write(message); + _stdOutWriter.Flush(); - // Save the BaseStream position for future writes. - _stdOutWriterPosition = _stdOutWriter.BaseStream.Position; + // Save the BaseStream position for future writes. + _stdOutWriterPosition = _stdOutWriter.BaseStream.Position; - // Reset the BaseStream position to the original position - StdOutReader.BaseStream.Position = stdOutReaderPosition; + // Reset the BaseStream position to the original position + StdOutReader.BaseStream.Position = stdOutReaderPosition; + } } public void WriteLine(string message) { - Console.WriteLine(message); - Debug.WriteLine(message); + lock (_writeLocker) + { + Debug.WriteLine(message); - // Save BaseStream position, it must be only modified by the consumer of StdOutReader - // After writing to the BaseStream, we will reset it to the original position. - var stdOutReaderPosition = StdOutReader.BaseStream.Position; + // Save BaseStream position, it must be only modified by the consumer of StdOutReader + // After writing to the BaseStream, we will reset it to the original position. + var stdOutReaderPosition = StdOutReader.BaseStream.Position; - // Reset the BaseStream to the last save position to continue writing from where we left. - _stdOutWriter.BaseStream.Position = _stdOutWriterPosition; - _stdOutWriter.WriteLine(message); - _stdOutWriter.Flush(); + // Reset the BaseStream to the last save position to continue writing from where we left. + _stdOutWriter.BaseStream.Position = _stdOutWriterPosition; + _stdOutWriter.WriteLine(message); + _stdOutWriter.Flush(); - // Save the BaseStream position for future writes. - _stdOutWriterPosition = _stdOutWriter.BaseStream.Position; + // Save the BaseStream position for future writes. + _stdOutWriterPosition = _stdOutWriter.BaseStream.Position; - // Reset the BaseStream position to the original position - StdOutReader.BaseStream.Position = stdOutReaderPosition; + // Reset the BaseStream position to the original position + StdOutReader.BaseStream.Position = stdOutReaderPosition; + } } public void WriteDebugLine(string message) { - Console.WriteLine(message); - Debug.WriteLine(message); - - // Save BaseStream position, it must be only modified by the consumer of StdDebugReader - // After writing to the BaseStream, we will reset it to the original position. - var stdDebugReaderPosition = StdDebugReader.BaseStream.Position; - - // Reset the BaseStream to the last save position to continue writing from where we left. - _stdDebugWriter.BaseStream.Position = _stdDebugWriterPosition; - _stdDebugWriter.WriteLine(message); - _stdDebugWriter.Flush(); - - // Save the BaseStream position for future writes. - _stdDebugWriterPosition = _stdDebugWriter.BaseStream.Position; - - // Reset the BaseStream position to the original position - StdDebugReader.BaseStream.Position = stdDebugReaderPosition; + WriteLine(message); } public void WriteErrorLine(string message) { - Console.WriteLine(message); - Debug.WriteLine(message); - - // Save BaseStream position, it must be only modified by the consumer of StdErrorReader - // After writing to the BaseStream, we will reset it to the original position. - var stdErrorReaderPosition = StdErrorReader.BaseStream.Position; - - // Reset the BaseStream to the last save position to continue writing from where we left. - _stdErrorWriter.BaseStream.Position = _stdErrorWriterPosition; - _stdErrorWriter.WriteLine(message); - _stdErrorWriter.Flush(); - - // Save the BaseStream position for future writes. - _stdErrorWriterPosition = _stdErrorWriter.BaseStream.Position; - - // Reset the BaseStream position to the original position - StdErrorReader.BaseStream.Position = stdErrorReaderPosition; + WriteLine(message); } public string ReadLine() { - var stdInWriterPosition = StdInWriter.BaseStream.Position; + lock (_readLocker) + { + var stdInWriterPosition = StdInWriter.BaseStream.Position; - // Reset the BaseStream to the last save position to continue writing from where we left. - _stdInReader.BaseStream.Position = _stdInReaderPosition; + // Reset the BaseStream to the last save position to continue writing from where we left. + _stdInReader.BaseStream.Position = _stdInReaderPosition; - var readLine = _stdInReader.ReadLine(); + var readLine = _stdInReader.ReadLine(); - if (readLine == null) - { - throw new InvalidOperationException(); - } + if (readLine == null) + { + throw new InvalidOperationException(); + } - // Save the BaseStream position for future reads. - _stdInReaderPosition = _stdInReader.BaseStream.Position; + // Save the BaseStream position for future reads. + _stdInReaderPosition = _stdInReader.BaseStream.Position; - // Reset the BaseStream position to the original position - StdInWriter.BaseStream.Position = stdInWriterPosition; + // Reset the BaseStream position to the original position + StdInWriter.BaseStream.Position = stdInWriterPosition; - Console.WriteLine(readLine); - Debug.WriteLine(readLine); + WriteLine(readLine); - return readLine; + return readLine; + } } public bool Diagnostics { get; set; } + public bool DisableInteractive { get; set; } public ConsoleKeyInfo ReadKey(bool intercept) @@ -187,10 +144,44 @@ public ConsoleKeyInfo ReadKey(bool intercept) // Reset the BaseStream position to the original position StdInWriter.BaseStream.Position = stdInWriterPosition; - Console.WriteLine(key); - Debug.WriteLine(key); + WriteLine(key.ToString()); return key; } + + public void ReadStdOutStartToEnd() + { + lock (s_readToEndLocker) + { + // Save BaseStream position, it must be only modified by the consumer of StdOutReader + // After writing to the BaseStream, we will reset it to the original position. + var stdOutReaderPosition = StdOutReader.BaseStream.Position; + + StdOutReader.BaseStream.Position = 0; + + var output = StdOutReader.ReadToEnd(); + + Console.WriteLine(output); + Debug.WriteLine(output); + + // Reset the BaseStream position to the original position + StdOutReader.BaseStream.Position = stdOutReaderPosition; + } + } + + public void LogErrorMessageLine(string message) + { + WriteLine(message); + } + + public void LogMessageLine(string message) + { + WriteLine(message); + } + + public void LogDebugLine(string message) + { + WriteLine(message); + } } } diff --git a/test/AWS.Deploy.CLI.IntegrationTests/BlazorWasmTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/BlazorWasmTests.cs index 089da7ed8..612314737 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/BlazorWasmTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/BlazorWasmTests.cs @@ -30,11 +30,6 @@ public class BlazorWasmTests : IDisposable public BlazorWasmTests() { - _httpHelper = new HttpHelper(); - - _cloudFormationHelper = new CloudFormationHelper(new AmazonCloudFormationClient()); - _cloudFrontHelper = new CloudFrontHelper(new Amazon.CloudFront.AmazonCloudFrontClient()); - var serviceCollection = new ServiceCollection(); serviceCollection.AddCustomServices(); @@ -48,6 +43,12 @@ public BlazorWasmTests() _interactiveService = serviceProvider.GetService(); Assert.NotNull(_interactiveService); + _httpHelper = new HttpHelper(_interactiveService); + + var cloudFormationClient = new AmazonCloudFormationClient(); + _cloudFormationHelper = new CloudFormationHelper(cloudFormationClient); + _cloudFrontHelper = new CloudFrontHelper(new Amazon.CloudFront.AmazonCloudFrontClient()); + _testAppManager = new TestAppManager(); } @@ -65,22 +66,17 @@ public async Task DefaultConfigurations(params string[] components) // Deploy var deployArgs = new[] { "deploy", "--project-path", _testAppManager.GetProjectPath(Path.Combine(components)), "--stack-name", _stackName, "--diagnostics" }; - await _app.Run(deployArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deployArgs)); // Verify application is deployed and running Assert.Equal(StackStatus.CREATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName)); - var deployStdDebug = _interactiveService.StdDebugReader.ReadAllLines(); - - var tempCdkProject = deployStdDebug.FirstOrDefault(line => line.Trim().Contains("The CDK Project is saved at: "))? - .Split(": ")[1] - .Trim(); - - Assert.NotNull(tempCdkProject); - Assert.False(Directory.Exists(tempCdkProject)); - var deployStdOut = _interactiveService.StdOutReader.ReadAllLines(); + var tempCdkProjectLine = deployStdOut.First(line => line.StartsWith("The CDK Project is saved at:")); + var tempCdkProject = tempCdkProjectLine.Split(":")[1].Trim(); + Assert.False(Directory.Exists(tempCdkProject), $"{tempCdkProject} must not exist."); + // Example URL string: BlazorWasm5068e7a879d5ee.EndpointURL = http://blazorwasm5068e7a879d5ee-blazorhostc7106839-a2585dcq9xve.s3-website-us-west-2.amazonaws.com/ var applicationUrl = deployStdOut.First(line => line.Contains("https://") && line.Contains("cloudfront.net/")) .Split("=")[1] @@ -95,8 +91,8 @@ public async Task DefaultConfigurations(params string[] components) Assert.Equal(Amazon.CloudFront.PriceClass.PriceClass_All, distribution.DistributionConfig.PriceClass); // list - var listArgs = new[] { "list-deployments" }; - await _app.Run(listArgs); + var listArgs = new[] { "list-deployments", "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(listArgs));; // Verify stack exists in list of deployments var listDeployStdOut = _interactiveService.StdOutReader.ReadAllLines(); @@ -108,7 +104,7 @@ public async Task DefaultConfigurations(params string[] components) var applyLoggingSettingsFile = Path.Combine(Directory.GetParent(_testAppManager.GetProjectPath(Path.Combine(components))).FullName, "apply-settings.json"); deployArgs = new[] { "deploy", "--project-path", _testAppManager.GetProjectPath(Path.Combine(components)), "--stack-name", _stackName, "--diagnostics", "--apply", applyLoggingSettingsFile }; - await _app.Run(deployArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deployArgs)); // URL could take few more minutes to come live, therefore, we want to wait and keep trying for a specified timeout await _httpHelper.WaitUntilSuccessStatusCode(applicationUrl, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(5)); @@ -119,10 +115,10 @@ public async Task DefaultConfigurations(params string[] components) // Arrange input for delete await _interactiveService.StdInWriter.WriteAsync("y"); // Confirm delete await _interactiveService.StdInWriter.FlushAsync(); - var deleteArgs = new[] { "delete-deployment", _stackName }; + var deleteArgs = new[] { "delete-deployment", _stackName, "--diagnostics" }; // Delete - await _app.Run(deleteArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deleteArgs));; // Verify application is deleted Assert.True(await _cloudFormationHelper.IsStackDeleted(_stackName), $"{_stackName} still exists."); @@ -145,6 +141,8 @@ protected virtual void Dispose(bool disposing) { _cloudFormationHelper.DeleteStack(_stackName).GetAwaiter().GetResult(); } + + _interactiveService.ReadStdOutStartToEnd(); } _isDisposed = true; diff --git a/test/AWS.Deploy.CLI.IntegrationTests/ConfigFileDeployment/ECSFargateDeploymentTest.cs b/test/AWS.Deploy.CLI.IntegrationTests/ConfigFileDeployment/ECSFargateDeploymentTest.cs index 1fe057b67..2b6207f6c 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/ConfigFileDeployment/ECSFargateDeploymentTest.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/ConfigFileDeployment/ECSFargateDeploymentTest.cs @@ -33,11 +33,6 @@ public class ECSFargateDeploymentTest : IDisposable public ECSFargateDeploymentTest() { - _httpHelper = new HttpHelper(); - - var cloudFormationClient = new AmazonCloudFormationClient(); - _cloudFormationHelper = new CloudFormationHelper(cloudFormationClient); - var ecsClient = new AmazonECSClient(); _ecsHelper = new ECSHelper(ecsClient); @@ -54,6 +49,11 @@ public ECSFargateDeploymentTest() _interactiveService = serviceProvider.GetService(); Assert.NotNull(_interactiveService); + var cloudFormationClient = new AmazonCloudFormationClient(); + _cloudFormationHelper = new CloudFormationHelper(cloudFormationClient); + + _httpHelper = new HttpHelper(_interactiveService); + _testAppManager = new TestAppManager(); } @@ -70,8 +70,8 @@ public async Task PerformDeployment() _stackName = userDeploymentSettings.StackName; _clusterName = userDeploymentSettings.LeafOptionSettingItems["ECSCluster.NewClusterName"]; - var deployArgs = new[] { "deploy", "--project-path", projectPath, "--apply", configFilePath, "--silent" }; - await _app.Run(deployArgs); + var deployArgs = new[] { "deploy", "--project-path", projectPath, "--apply", configFilePath, "--silent", "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deployArgs)); // Verify application is deployed and running Assert.Equal(StackStatus.CREATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName)); @@ -90,8 +90,8 @@ public async Task PerformDeployment() await _httpHelper.WaitUntilSuccessStatusCode(applicationUrl, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(5)); // list - var listArgs = new[] { "list-deployments" }; - await _app.Run(listArgs); + var listArgs = new[] { "list-deployments", "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(listArgs));; // Verify stack exists in list of deployments var listDeployStdOut = _interactiveService.StdOutReader.ReadAllLines(); @@ -100,10 +100,10 @@ public async Task PerformDeployment() // Arrange input for delete await _interactiveService.StdInWriter.WriteAsync("y"); // Confirm delete await _interactiveService.StdInWriter.FlushAsync(); - var deleteArgs = new[] { "delete-deployment", _stackName }; + var deleteArgs = new[] { "delete-deployment", _stackName, "--diagnostics" }; // Delete - await _app.Run(deleteArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deleteArgs));; // Verify application is deleted Assert.True(await _cloudFormationHelper.IsStackDeleted(_stackName), $"{_stackName} still exists."); @@ -126,6 +126,8 @@ protected virtual void Dispose(bool disposing) { _cloudFormationHelper.DeleteStack(_stackName).GetAwaiter().GetResult(); } + + _interactiveService.ReadStdOutStartToEnd(); } _isDisposed = true; diff --git a/test/AWS.Deploy.CLI.IntegrationTests/ConfigFileDeployment/ElasticBeanStalkDeploymentTest.cs b/test/AWS.Deploy.CLI.IntegrationTests/ConfigFileDeployment/ElasticBeanStalkDeploymentTest.cs index 20be36f54..056ea3210 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/ConfigFileDeployment/ElasticBeanStalkDeploymentTest.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/ConfigFileDeployment/ElasticBeanStalkDeploymentTest.cs @@ -30,11 +30,6 @@ public class ElasticBeanStalkDeploymentTest : IDisposable public ElasticBeanStalkDeploymentTest() { - _httpHelper = new HttpHelper(); - - var cloudFormationClient = new AmazonCloudFormationClient(); - _cloudFormationHelper = new CloudFormationHelper(cloudFormationClient); - var serviceCollection = new ServiceCollection(); serviceCollection.AddCustomServices(); @@ -48,6 +43,11 @@ public ElasticBeanStalkDeploymentTest() _interactiveService = serviceProvider.GetService(); Assert.NotNull(_interactiveService); + _httpHelper = new HttpHelper(_interactiveService); + + var cloudFormationClient = new AmazonCloudFormationClient(); + _cloudFormationHelper = new CloudFormationHelper(cloudFormationClient); + _testAppManager = new TestAppManager(); } @@ -63,8 +63,8 @@ public async Task PerformDeployment() _stackName = userDeploymentSettings.StackName; - var deployArgs = new[] { "deploy", "--project-path", projectPath, "--apply", configFilePath, "--silent" }; - await _app.Run(deployArgs); + var deployArgs = new[] { "deploy", "--project-path", projectPath, "--apply", configFilePath, "--silent", "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deployArgs)); // Verify application is deployed and running Assert.Equal(StackStatus.CREATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName)); @@ -80,8 +80,8 @@ public async Task PerformDeployment() await _httpHelper.WaitUntilSuccessStatusCode(applicationUrl, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(5)); // list - var listArgs = new[] { "list-deployments" }; - await _app.Run(listArgs); + var listArgs = new[] { "list-deployments", "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(listArgs));; // Verify stack exists in list of deployments var listDeployStdOut = _interactiveService.StdOutReader.ReadAllLines(); @@ -90,13 +90,15 @@ public async Task PerformDeployment() // Arrange input for delete await _interactiveService.StdInWriter.WriteAsync("y"); // Confirm delete await _interactiveService.StdInWriter.FlushAsync(); - var deleteArgs = new[] { "delete-deployment", _stackName }; + var deleteArgs = new[] { "delete-deployment", _stackName, "--diagnostics" }; // Delete - await _app.Run(deleteArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deleteArgs));; // Verify application is deleted Assert.True(await _cloudFormationHelper.IsStackDeleted(_stackName), $"{_stackName} still exists."); + + _interactiveService.ReadStdOutStartToEnd(); } public void Dispose() @@ -116,6 +118,8 @@ protected virtual void Dispose(bool disposing) { _cloudFormationHelper.DeleteStack(_stackName).GetAwaiter().GetResult(); } + + _interactiveService.ReadStdOutStartToEnd(); } _isDisposed = true; diff --git a/test/AWS.Deploy.CLI.IntegrationTests/ConsoleAppTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/ConsoleAppTests.cs index 00ebc9793..718d2eae7 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/ConsoleAppTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/ConsoleAppTests.cs @@ -31,9 +31,6 @@ public class ConsoleAppTests : IDisposable public ConsoleAppTests() { - var cloudFormationClient = new AmazonCloudFormationClient(); - _cloudFormationHelper = new CloudFormationHelper(cloudFormationClient); - var ecsClient = new AmazonECSClient(); _ecsHelper = new ECSHelper(ecsClient); @@ -53,6 +50,9 @@ public ConsoleAppTests() _interactiveService = serviceProvider.GetService(); Assert.NotNull(_interactiveService); + var cloudFormationClient = new AmazonCloudFormationClient(); + _cloudFormationHelper = new CloudFormationHelper(cloudFormationClient); + _testAppManager = new TestAppManager(); } @@ -70,7 +70,7 @@ public async Task DefaultConfigurations(params string[] components) // Deploy var deployArgs = new[] { "deploy", "--project-path", _testAppManager.GetProjectPath(Path.Combine(components)), "--stack-name", _stackName, "--diagnostics" }; - await _app.Run(deployArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deployArgs)); // Verify application is deployed and running Assert.Equal(StackStatus.CREATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName)); @@ -83,30 +83,27 @@ public async Task DefaultConfigurations(params string[] components) var logMessages = await _cloudWatchLogsHelper.GetLogMessages(logGroup); Assert.Contains("Hello World!", logMessages); - var deployStdDebug = _interactiveService.StdDebugReader.ReadAllLines(); + var deployStdOut = _interactiveService.StdOutReader.ReadAllLines(); - var tempCdkProject = deployStdDebug.FirstOrDefault(line => line.Trim().Contains("The CDK Project is saved at: "))? - .Split(": ")[1] - .Trim(); - - Assert.NotNull(tempCdkProject); - Assert.False(Directory.Exists(tempCdkProject)); + var tempCdkProjectLine = deployStdOut.First(line => line.StartsWith("The CDK Project is saved at:")); + var tempCdkProject = tempCdkProjectLine.Split(":")[1].Trim(); + Assert.False(Directory.Exists(tempCdkProject), $"{tempCdkProject} must not exist."); // list - var listArgs = new[] { "list-deployments" }; - await _app.Run(listArgs); + var listArgs = new[] { "list-deployments", "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(listArgs));; // Verify stack exists in list of deployments - var listDeployStdOut = _interactiveService.StdOutReader.ReadAllLines(); - Assert.Contains(listDeployStdOut, (deployment) => _stackName.Equals(deployment)); + var listStdOut = _interactiveService.StdOutReader.ReadAllLines(); + Assert.Contains(listStdOut, (deployment) => _stackName.Equals(deployment)); // Arrange input for delete await _interactiveService.StdInWriter.WriteAsync("y"); // Confirm delete await _interactiveService.StdInWriter.FlushAsync(); - var deleteArgs = new[] { "delete-deployment", _stackName }; + var deleteArgs = new[] { "delete-deployment", _stackName, "--diagnostics" }; // Delete - await _app.Run(deleteArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deleteArgs));; // Verify application is deleted Assert.True(await _cloudFormationHelper.IsStackDeleted(_stackName), $"{_stackName} still exists."); @@ -129,6 +126,8 @@ protected virtual void Dispose(bool disposing) { _cloudFormationHelper.DeleteStack(_stackName).GetAwaiter().GetResult(); } + + _interactiveService.ReadStdOutStartToEnd(); } _isDisposed = true; diff --git a/test/AWS.Deploy.CLI.IntegrationTests/Extensions/ReadAllLinesStreamReaderExtension.cs b/test/AWS.Deploy.CLI.IntegrationTests/Extensions/ReadAllLinesStreamReaderExtension.cs index 41895fd0b..b120c383c 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/Extensions/ReadAllLinesStreamReaderExtension.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/Extensions/ReadAllLinesStreamReaderExtension.cs @@ -15,7 +15,7 @@ public static class ReadAllLinesStreamReaderExtension /// /// Reader that allows line by line reading /// Read lines - public static IEnumerable ReadAllLines(this StreamReader reader) + public static IList ReadAllLines(this StreamReader reader) { var lines = new List(); diff --git a/test/AWS.Deploy.CLI.IntegrationTests/Extensions/TestServiceCollectionExtension.cs b/test/AWS.Deploy.CLI.IntegrationTests/Extensions/TestServiceCollectionExtension.cs index ab84d657b..09d3b58eb 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/Extensions/TestServiceCollectionExtension.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/Extensions/TestServiceCollectionExtension.cs @@ -26,6 +26,7 @@ public static void AddTestServices(this IServiceCollection serviceCollection) { serviceCollection.AddSingleton(); serviceCollection.AddSingleton(serviceProvider => serviceProvider.GetService()); + serviceCollection.AddSingleton(serviceProvider => serviceProvider.GetService()); } } } diff --git a/test/AWS.Deploy.CLI.IntegrationTests/Helpers/CloudFormationHelper.cs b/test/AWS.Deploy.CLI.IntegrationTests/Helpers/CloudFormationHelper.cs index 24e78f3ec..666cbee5a 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/Helpers/CloudFormationHelper.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/Helpers/CloudFormationHelper.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Amazon.CloudFormation; using Amazon.CloudFormation.Model; +using AWS.Deploy.Common; using Xunit; namespace AWS.Deploy.CLI.IntegrationTests.Helpers diff --git a/test/AWS.Deploy.CLI.IntegrationTests/Helpers/HttpHelper.cs b/test/AWS.Deploy.CLI.IntegrationTests/Helpers/HttpHelper.cs index 835589df7..c2ea31bf1 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/Helpers/HttpHelper.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/Helpers/HttpHelper.cs @@ -4,12 +4,21 @@ using System; using System.Net.Http; using System.Threading.Tasks; +using AWS.Deploy.CLI.IntegrationTests.Services; +using AWS.Deploy.Common; using Xunit; namespace AWS.Deploy.CLI.IntegrationTests.Helpers { public class HttpHelper { + private readonly InMemoryInteractiveService _interactiveService; + + public HttpHelper(InMemoryInteractiveService interactiveService) + { + _interactiveService = interactiveService; + } + public async Task WaitUntilSuccessStatusCode(string url, TimeSpan frequency, TimeSpan timeout) { using var client = new HttpClient(); @@ -22,8 +31,9 @@ await WaitUntilHelper.WaitUntil(async () => return httpResponseMessage.IsSuccessStatusCode; }, frequency, timeout); } - catch (TimeoutException) + catch (TimeoutException ex) { + _interactiveService.WriteErrorLine(ex.PrettyPrint()); Assert.True(false, $"{url} URL is not reachable."); } } diff --git a/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/CustomRecipeLocatorTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/CustomRecipeLocatorTests.cs index c031e7362..05bfa660a 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/CustomRecipeLocatorTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/CustomRecipeLocatorTests.cs @@ -1,6 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +using System; using System.IO; using AWS.Deploy.CLI.Utilities; using AWS.Deploy.Common.DeploymentManifest; @@ -10,17 +11,19 @@ using Task = System.Threading.Tasks.Task; using Should; using AWS.Deploy.CLI.Common.UnitTests.IO; +using AWS.Deploy.CLI.IntegrationTests.Services; namespace AWS.Deploy.CLI.IntegrationTests.SaveCdkDeploymentProject { - public class CustomRecipeLocatorTests + public class CustomRecipeLocatorTests : IDisposable { private readonly CommandLineWrapper _commandLineWrapper; + private readonly InMemoryInteractiveService _inMemoryInteractiveService; public CustomRecipeLocatorTests() { - _commandLineWrapper = new CommandLineWrapper(new ConsoleOrchestratorLogger(new ConsoleInteractiveServiceImpl())); - } + _inMemoryInteractiveService = new InMemoryInteractiveService(); + _commandLineWrapper = new CommandLineWrapper(_inMemoryInteractiveService); } [Fact] public async Task LocateCustomRecipePathsWithManifestFile() @@ -41,7 +44,7 @@ public async Task LocateCustomRecipePathsWithManifestFile() // ASSERT File.Exists(Path.Combine(webAppWithDockerFilePath, "aws-deployments.json")).ShouldBeTrue(); - customRecipePaths.Count.ShouldEqual(2); + customRecipePaths.Count.ShouldEqual(2, $"Custom recipes found: {Environment.NewLine} {string.Join(Environment.NewLine, customRecipePaths)}"); customRecipePaths.ShouldContain(Path.Combine(tempDirectoryPath, "MyCdkApp1")); customRecipePaths.ShouldContain(Path.Combine(tempDirectoryPath, "MyCdkApp1")); } @@ -67,7 +70,7 @@ public async Task LocateCustomRecipePathsWithoutManifestFile() // ASSERT File.Exists(Path.Combine(webAppNoDockerFilePath, "aws-deployments.json")).ShouldBeFalse(); - customRecipePaths.Count.ShouldEqual(2); + customRecipePaths.Count.ShouldEqual(2, $"Custom recipes found: {Environment.NewLine} {string.Join(Environment.NewLine, customRecipePaths)}"); customRecipePaths.ShouldContain(Path.Combine(tempDirectoryPath, "MyCdkApp1")); customRecipePaths.ShouldContain(Path.Combine(tempDirectoryPath, "MyCdkApp1")); } @@ -77,10 +80,24 @@ private ICustomRecipeLocator BuildCustomRecipeLocator() var directoryManager = new DirectoryManager(); var fileManager = new FileManager(); var deploymentManifestEngine = new DeploymentManifestEngine(directoryManager, fileManager); - var consoleInteractiveServiceImpl = new ConsoleInteractiveServiceImpl(); - var consoleOrchestratorLogger = new ConsoleOrchestratorLogger(consoleInteractiveServiceImpl); - var commandLineWrapper = new CommandLineWrapper(consoleOrchestratorLogger); - return new CustomRecipeLocator(deploymentManifestEngine, consoleOrchestratorLogger, commandLineWrapper, directoryManager); + var commandLineWrapper = new CommandLineWrapper(_inMemoryInteractiveService); + return new CustomRecipeLocator(deploymentManifestEngine, _inMemoryInteractiveService, commandLineWrapper, directoryManager); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _inMemoryInteractiveService.ReadStdOutStartToEnd(); + } } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~CustomRecipeLocatorTests() => Dispose(false); } } diff --git a/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/RecommendationTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/RecommendationTests.cs index ca42a6b2b..93c4afc96 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/RecommendationTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/RecommendationTests.cs @@ -21,17 +21,20 @@ using AWS.Deploy.Common.Recipes; using Newtonsoft.Json; using AWS.Deploy.CLI.Common.UnitTests.IO; +using AWS.Deploy.CLI.IntegrationTests.Services; using AWS.Deploy.Orchestration.LocalUserSettings; namespace AWS.Deploy.CLI.IntegrationTests.SaveCdkDeploymentProject { - public class RecommendationTests + public class RecommendationTests : IDisposable { private readonly CommandLineWrapper _commandLineWrapper; + private readonly InMemoryInteractiveService _inMemoryInteractiveService; public RecommendationTests() { - _commandLineWrapper = new CommandLineWrapper(new ConsoleOrchestratorLogger(new ConsoleInteractiveServiceImpl())); + _inMemoryInteractiveService = new InMemoryInteractiveService(); + _commandLineWrapper = new CommandLineWrapper(_inMemoryInteractiveService); } [Fact] @@ -201,16 +204,14 @@ private async Task GetOrchestrator(string targetApplicationProject var fileManager = new FileManager(); var deploymentManifestEngine = new DeploymentManifestEngine(directoryManager, fileManager); var localUserSettingsEngine = new LocalUserSettingsEngine(fileManager, directoryManager); - var consoleInteractiveServiceImpl = new ConsoleInteractiveServiceImpl(); - var consoleOrchestratorLogger = new ConsoleOrchestratorLogger(consoleInteractiveServiceImpl); - var commandLineWrapper = new CommandLineWrapper(consoleOrchestratorLogger); - var customRecipeLocator = new CustomRecipeLocator(deploymentManifestEngine, consoleOrchestratorLogger, commandLineWrapper, directoryManager); + var commandLineWrapper = new CommandLineWrapper(_inMemoryInteractiveService); + var customRecipeLocator = new CustomRecipeLocator(deploymentManifestEngine, _inMemoryInteractiveService, commandLineWrapper, directoryManager); var projectDefinition = await new ProjectDefinitionParser(fileManager, directoryManager).Parse(targetApplicationProjectPath); var session = new OrchestratorSession(projectDefinition); return new Orchestrator(session, - consoleOrchestratorLogger, + _inMemoryInteractiveService, new Mock().Object, new Mock().Object, new Mock().Object, @@ -229,5 +230,21 @@ private async Task GetCustomRecipeId(string recipeFilePath) var recipe = JsonConvert.DeserializeObject(recipeBody); return recipe.Id; } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _inMemoryInteractiveService.ReadStdOutStartToEnd(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~RecommendationTests() => Dispose(false); } } diff --git a/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/SaveCdkDeploymentProjectTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/SaveCdkDeploymentProjectTests.cs index 90d8a5917..e8c0b9e37 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/SaveCdkDeploymentProjectTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/SaveCdkDeploymentProjectTests.cs @@ -1,9 +1,11 @@ // 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 AWS.Deploy.CLI.Common.UnitTests.IO; +using AWS.Deploy.CLI.IntegrationTests.Services; using AWS.Deploy.CLI.Utilities; using Xunit; using Task = System.Threading.Tasks.Task; @@ -12,13 +14,15 @@ namespace AWS.Deploy.CLI.IntegrationTests.SaveCdkDeploymentProject { - public class SaveCdkDeploymentProjectTests + public class SaveCdkDeploymentProjectTests : IDisposable { private readonly CommandLineWrapper _commandLineWrapper; + private readonly InMemoryInteractiveService _inMemoryInteractiveService; public SaveCdkDeploymentProjectTests() { - _commandLineWrapper = new CommandLineWrapper(new ConsoleOrchestratorLogger(new ConsoleInteractiveServiceImpl())); + _inMemoryInteractiveService = new InMemoryInteractiveService(); + _commandLineWrapper = new CommandLineWrapper(_inMemoryInteractiveService); } [Fact] @@ -71,5 +75,21 @@ public async Task InvalidNonEmptySaveCdkDirectory() var saveDirectoryPath = Path.Combine(tempDirectoryPath, "MyCdkApp"); await Utilities.CreateCDKDeploymentProject(targetApplicationProjectPath, saveDirectoryPath, false); } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _inMemoryInteractiveService.ReadStdOutStartToEnd(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~SaveCdkDeploymentProjectTests() => Dispose(false); } } diff --git a/test/AWS.Deploy.CLI.IntegrationTests/ServerModeTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/ServerModeTests.cs index 59b0df50f..a415874de 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/ServerModeTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/ServerModeTests.cs @@ -37,9 +37,12 @@ public class ServerModeTests : IDisposable private readonly string _awsRegion; private readonly TestAppManager _testAppManager; + private readonly InMemoryInteractiveService _interactiveService; public ServerModeTests() { + _interactiveService = new InMemoryInteractiveService(); + var cloudFormationClient = new AmazonCloudFormationClient(Amazon.RegionEndpoint.USWest2); _cloudFormationHelper = new CloudFormationHelper(cloudFormationClient); @@ -108,7 +111,6 @@ public async Task GetRecommendationsWithEncryptedCredentials() using var httpClient = ServerModeHttpClientFactory.ConstructHttpClient(ResolveCredentials, aes); - InMemoryInteractiveService interactiveService = new InMemoryInteractiveService(); var keyInfo = new EncryptionKeyInfo { Version = EncryptionKeyInfo.VERSION_1_0, @@ -116,10 +118,10 @@ public async Task GetRecommendationsWithEncryptedCredentials() IV = Convert.ToBase64String(aes.IV) }; var keyInfoStdin = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(keyInfo))); - await interactiveService.StdInWriter.WriteAsync(keyInfoStdin); - await interactiveService.StdInWriter.FlushAsync(); + await _interactiveService.StdInWriter.WriteAsync(keyInfoStdin); + await _interactiveService.StdInWriter.FlushAsync(); - var serverCommand = new ServerModeCommand(interactiveService, portNumber, null, false); + var serverCommand = new ServerModeCommand(_interactiveService, portNumber, null, false); var cancelSource = new CancellationTokenSource(); var serverTask = serverCommand.ExecuteAsync(cancelSource.Token); @@ -141,7 +143,7 @@ public async Task GetRecommendationsWithEncryptedCredentials() Assert.NotEmpty(getRecommendationOutput.Recommendations); Assert.Equal("AspNetAppElasticBeanstalkLinux", getRecommendationOutput.Recommendations.FirstOrDefault().RecipeId); - var listDeployStdOut = interactiveService.StdOutReader.ReadAllLines(); + var listDeployStdOut = _interactiveService.StdOutReader.ReadAllLines(); Assert.Contains("Waiting on symmetric key from stdin", listDeployStdOut); Assert.Contains("Encryption provider enabled", listDeployStdOut); } @@ -270,6 +272,8 @@ protected virtual void Dispose(bool disposing) { _cloudFormationHelper.DeleteStack(_stackName).GetAwaiter().GetResult(); } + + _interactiveService.ReadStdOutStartToEnd(); } } diff --git a/test/AWS.Deploy.CLI.IntegrationTests/Services/InMemoryInteractiveServiceTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/Services/InMemoryInteractiveServiceTests.cs index 88b440191..10a771e6e 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/Services/InMemoryInteractiveServiceTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/Services/InMemoryInteractiveServiceTests.cs @@ -54,17 +54,17 @@ public void WriteErrorLine() service.WriteErrorLine("Error Line 2"); service.WriteErrorLine("Error Line 3"); - Assert.Equal("Error Line 1", service.StdErrorReader.ReadLine()); - Assert.Equal("Error Line 2", service.StdErrorReader.ReadLine()); - Assert.Equal("Error Line 3", service.StdErrorReader.ReadLine()); + Assert.Equal("Error Line 1", service.StdOutReader.ReadLine()); + Assert.Equal("Error Line 2", service.StdOutReader.ReadLine()); + Assert.Equal("Error Line 3", service.StdOutReader.ReadLine()); service.WriteErrorLine("Error Line 4"); service.WriteErrorLine("Error Line 5"); service.WriteErrorLine("Error Line 6"); - Assert.Equal("Error Line 4", service.StdErrorReader.ReadLine()); - Assert.Equal("Error Line 5", service.StdErrorReader.ReadLine()); - Assert.Equal("Error Line 6", service.StdErrorReader.ReadLine()); + Assert.Equal("Error Line 4", service.StdOutReader.ReadLine()); + Assert.Equal("Error Line 5", service.StdOutReader.ReadLine()); + Assert.Equal("Error Line 6", service.StdOutReader.ReadLine()); } [Fact] @@ -76,17 +76,17 @@ public void WriteDebugLine() service.WriteDebugLine("Debug Line 2"); service.WriteDebugLine("Debug Line 3"); - Assert.Equal("Debug Line 1", service.StdDebugReader.ReadLine()); - Assert.Equal("Debug Line 2", service.StdDebugReader.ReadLine()); - Assert.Equal("Debug Line 3", service.StdDebugReader.ReadLine()); + Assert.Equal("Debug Line 1", service.StdOutReader.ReadLine()); + Assert.Equal("Debug Line 2", service.StdOutReader.ReadLine()); + Assert.Equal("Debug Line 3", service.StdOutReader.ReadLine()); service.WriteDebugLine("Debug Line 4"); service.WriteDebugLine("Debug Line 5"); service.WriteDebugLine("Debug Line 6"); - Assert.Equal("Debug Line 4", service.StdDebugReader.ReadLine()); - Assert.Equal("Debug Line 5", service.StdDebugReader.ReadLine()); - Assert.Equal("Debug Line 6", service.StdDebugReader.ReadLine()); + Assert.Equal("Debug Line 4", service.StdOutReader.ReadLine()); + Assert.Equal("Debug Line 5", service.StdOutReader.ReadLine()); + Assert.Equal("Debug Line 6", service.StdOutReader.ReadLine()); } [Fact] diff --git a/test/AWS.Deploy.CLI.IntegrationTests/WebAppNoDockerFileTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/WebAppNoDockerFileTests.cs index edd3da31e..19a3c96e7 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/WebAppNoDockerFileTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/WebAppNoDockerFileTests.cs @@ -29,11 +29,6 @@ public class WebAppNoDockerFileTests : IDisposable public WebAppNoDockerFileTests() { - _httpHelper = new HttpHelper(); - - var cloudFormationClient = new AmazonCloudFormationClient(); - _cloudFormationHelper = new CloudFormationHelper(cloudFormationClient); - var serviceCollection = new ServiceCollection(); serviceCollection.AddCustomServices(); @@ -47,6 +42,11 @@ public WebAppNoDockerFileTests() _interactiveService = serviceProvider.GetService(); Assert.NotNull(_interactiveService); + _httpHelper = new HttpHelper(_interactiveService); + + var cloudFormationClient = new AmazonCloudFormationClient(); + _cloudFormationHelper = new CloudFormationHelper(cloudFormationClient); + _testAppManager = new TestAppManager(); } @@ -63,22 +63,17 @@ public async Task DefaultConfigurations() // Deploy var projectPath = _testAppManager.GetProjectPath(Path.Combine("testapps", "WebAppNoDockerFile", "WebAppNoDockerFile.csproj")); var deployArgs = new[] { "deploy", "--project-path", projectPath, "--stack-name", _stackName, "--diagnostics" }; - await _app.Run(deployArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deployArgs)); // Verify application is deployed and running Assert.Equal(StackStatus.CREATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName)); - var deployStdDebug = _interactiveService.StdDebugReader.ReadAllLines(); - - var tempCdkProject = deployStdDebug.FirstOrDefault(line => line.Trim().Contains("The CDK Project is saved at: "))? - .Split(": ")[1] - .Trim(); - - Assert.NotNull(tempCdkProject); - Assert.False(Directory.Exists(tempCdkProject)); - var deployStdOut = _interactiveService.StdOutReader.ReadAllLines(); + var tempCdkProjectLine = deployStdOut.First(line => line.StartsWith("The CDK Project is saved at:")); + var tempCdkProject = tempCdkProjectLine.Split(":")[1].Trim(); + Assert.False(Directory.Exists(tempCdkProject), $"{tempCdkProject} must not exist."); + // Example: Endpoint: http://52.36.216.238/ var applicationUrl = deployStdOut.First(line => line.Trim().StartsWith($"Endpoint")) .Split(":")[1] @@ -88,20 +83,20 @@ public async Task DefaultConfigurations() await _httpHelper.WaitUntilSuccessStatusCode(applicationUrl, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(5)); // list - var listArgs = new[] { "list-deployments" }; - await _app.Run(listArgs); + var listArgs = new[] { "list-deployments", "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(listArgs));; // Verify stack exists in list of deployments - var listDeployStdOut = _interactiveService.StdOutReader.ReadAllLines(); - Assert.Contains(listDeployStdOut, (deployment) => _stackName.Equals(deployment)); + var listStdOut = _interactiveService.StdOutReader.ReadAllLines(); + Assert.Contains(listStdOut, (deployment) => _stackName.Equals(deployment)); // Arrange input for delete await _interactiveService.StdInWriter.WriteAsync("y"); // Confirm delete await _interactiveService.StdInWriter.FlushAsync(); - var deleteArgs = new[] { "delete-deployment", _stackName }; + var deleteArgs = new[] { "delete-deployment", _stackName, "--diagnostics" }; // Delete - await _app.Run(deleteArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deleteArgs));; // Verify application is deleted Assert.True(await _cloudFormationHelper.IsStackDeleted(_stackName), $"{_stackName} still exists."); @@ -124,6 +119,8 @@ protected virtual void Dispose(bool disposing) { _cloudFormationHelper.DeleteStack(_stackName).GetAwaiter().GetResult(); } + + _interactiveService.ReadStdOutStartToEnd(); } _isDisposed = true; diff --git a/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs index 2c30e37c1..7867d733a 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs @@ -31,11 +31,6 @@ public class WebAppWithDockerFileTests : IDisposable public WebAppWithDockerFileTests() { - _httpHelper = new HttpHelper(); - - var cloudFormationClient = new AmazonCloudFormationClient(); - _cloudFormationHelper = new CloudFormationHelper(cloudFormationClient); - var ecsClient = new AmazonECSClient(); _ecsHelper = new ECSHelper(ecsClient); @@ -52,6 +47,11 @@ public WebAppWithDockerFileTests() _interactiveService = serviceProvider.GetService(); Assert.NotNull(_interactiveService); + _httpHelper = new HttpHelper(_interactiveService); + + var cloudFormationClient = new AmazonCloudFormationClient(); + _cloudFormationHelper = new CloudFormationHelper(cloudFormationClient); + _testAppManager = new TestAppManager(); } @@ -68,7 +68,7 @@ public async Task DefaultConfigurations() // Deploy var projectPath = _testAppManager.GetProjectPath(Path.Combine("testapps", "WebAppWithDockerFile", "WebAppWithDockerFile.csproj")); var deployArgs = new[] { "deploy", "--project-path", projectPath, "--stack-name", _stackName, "--diagnostics" }; - await _app.Run(deployArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deployArgs)); // Verify application is deployed and running Assert.Equal(StackStatus.CREATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName)); @@ -76,17 +76,12 @@ public async Task DefaultConfigurations() var cluster = await _ecsHelper.GetCluster(_stackName); Assert.Equal("ACTIVE", cluster.Status); - var deployStdDebug = _interactiveService.StdDebugReader.ReadAllLines(); - - var tempCdkProject = deployStdDebug.FirstOrDefault(line => line.Trim().Contains("The CDK Project is saved at: "))? - .Split(": ")[1] - .Trim(); - - Assert.NotNull(tempCdkProject); - Assert.False(Directory.Exists(tempCdkProject)); - var deployStdOut = _interactiveService.StdOutReader.ReadAllLines(); + var tempCdkProjectLine = deployStdOut.First(line => line.StartsWith("The CDK Project is saved at:")); + var tempCdkProject = tempCdkProjectLine.Split(":")[1].Trim(); + Assert.False(Directory.Exists(tempCdkProject), $"{tempCdkProject} must not exist."); + var applicationUrl = deployStdOut.First(line => line.Trim().StartsWith("Endpoint:")) .Split(" ")[1] .Trim(); @@ -95,8 +90,8 @@ public async Task DefaultConfigurations() await _httpHelper.WaitUntilSuccessStatusCode(applicationUrl, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(5)); // list - var listArgs = new[] { "list-deployments" }; - await _app.Run(listArgs); + var listArgs = new[] { "list-deployments", "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(listArgs));; // Verify stack exists in list of deployments var listDeployStdOut = _interactiveService.StdOutReader.ReadAllLines(); @@ -108,18 +103,17 @@ public async Task DefaultConfigurations() // Perform re-deployment deployArgs = new[] { "deploy", "--project-path", projectPath, "--stack-name", _stackName, "--diagnostics" }; - var returnCode = await _app.Run(deployArgs); - Assert.Equal(CommandReturnCodes.SUCCESS, returnCode); + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deployArgs)); Assert.Equal(StackStatus.UPDATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName)); Assert.Equal("ACTIVE", cluster.Status); // Arrange input for delete await _interactiveService.StdInWriter.WriteAsync("y"); // Confirm delete await _interactiveService.StdInWriter.FlushAsync(); - var deleteArgs = new[] { "delete-deployment", _stackName }; + var deleteArgs = new[] { "delete-deployment", _stackName, "--diagnostics" }; // Delete - await _app.Run(deleteArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deleteArgs));; // Verify application is deleted Assert.True(await _cloudFormationHelper.IsStackDeleted(_stackName), $"{_stackName} still exists."); @@ -137,8 +131,8 @@ public async Task AppRunnerDeployment() // Deploy var projectPath = _testAppManager.GetProjectPath(Path.Combine("testapps", "WebAppWithDockerFile", "WebAppWithDockerFile.csproj")); - var deployArgs = new[] { "deploy", "--project-path", projectPath, "--stack-name", _stackName }; - await _app.Run(deployArgs); + var deployArgs = new[] { "deploy", "--project-path", projectPath, "--stack-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)); @@ -155,8 +149,8 @@ public async Task AppRunnerDeployment() await _httpHelper.WaitUntilSuccessStatusCode(applicationUrl, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(5)); // list - var listArgs = new[] { "list-deployments" }; - await _app.Run(listArgs); + var listArgs = new[] { "list-deployments", "--diagnostics" }; + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(listArgs));; // Verify stack exists in list of deployments var listDeployStdOut = _interactiveService.StdOutReader.ReadAllLines(); @@ -165,10 +159,10 @@ public async Task AppRunnerDeployment() // Arrange input for delete await _interactiveService.StdInWriter.WriteAsync("y"); // Confirm delete await _interactiveService.StdInWriter.FlushAsync(); - var deleteArgs = new[] { "delete-deployment", _stackName }; + var deleteArgs = new[] { "delete-deployment", _stackName, "--diagnostics" }; // Delete - await _app.Run(deleteArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, await _app.Run(deleteArgs));; // Verify application is deleted Assert.True(await _cloudFormationHelper.IsStackDeleted(_stackName)); @@ -191,6 +185,8 @@ protected virtual void Dispose(bool disposing) { _cloudFormationHelper.DeleteStack(_stackName).GetAwaiter().GetResult(); } + + _interactiveService.ReadStdOutStartToEnd(); } _isDisposed = true; diff --git a/test/AWS.Deploy.CLI.UnitTests/ServerModeAuthTests.cs b/test/AWS.Deploy.CLI.UnitTests/ServerModeAuthTests.cs index e5337e9ca..9dabbdc63 100644 --- a/test/AWS.Deploy.CLI.UnitTests/ServerModeAuthTests.cs +++ b/test/AWS.Deploy.CLI.UnitTests/ServerModeAuthTests.cs @@ -255,8 +255,6 @@ public async Task AuthEncryptionWithInvalidVersion() await interactiveService.StdInWriter.FlushAsync(); var serverCommand = new ServerModeCommand(interactiveService, portNumber, null, false); - - var cancelSource = new CancellationTokenSource(); Exception actualException = null; try diff --git a/version.json b/version.json index 3ab6951ce..16e61c6b1 100644 --- a/version.json +++ b/version.json @@ -1,13 +1,14 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.19", + "version": "0.20", "publicReleaseRefSpec": [ ".*" ], "cloudBuild": { - "setAllVariables": true, + "setAllVariables": false, "buildNumber": { - "enabled": true - } + "enabled": false + }, + "setVersionVariables": false } }