Skip to content

Commit

Permalink
Merge pull request #296 from aws/dev
Browse files Browse the repository at this point in the history
chore: release 0.16
  • Loading branch information
philasmar authored Aug 16, 2021
2 parents 0b355e6 + 73d6fe3 commit 2ac2770
Show file tree
Hide file tree
Showing 64 changed files with 1,442 additions and 544 deletions.
166 changes: 111 additions & 55 deletions src/AWS.Deploy.CLI/Commands/CommandFactory.cs

Large diffs are not rendered by default.

79 changes: 63 additions & 16 deletions src/AWS.Deploy.CLI/Commands/DeleteDeploymentCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
using Amazon.CloudFormation.Model;
using AWS.Deploy.CLI.CloudFormation;
using AWS.Deploy.Common;
using AWS.Deploy.Recipes.CDK.Common;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Orchestration;
using AWS.Deploy.Orchestration.LocalUserSettings;

namespace AWS.Deploy.CLI.Commands
{
Expand All @@ -23,13 +25,23 @@ public class DeleteDeploymentCommand
private readonly IToolInteractiveService _interactiveService;
private readonly IAmazonCloudFormation _cloudFormationClient;
private readonly IConsoleUtilities _consoleUtilities;

public DeleteDeploymentCommand(IAWSClientFactory awsClientFactory, IToolInteractiveService interactiveService, IConsoleUtilities consoleUtilities)
private readonly ILocalUserSettingsEngine _localUserSettingsEngine;
private readonly OrchestratorSession? _session;
private const int MAX_RETRIES = 4;

public DeleteDeploymentCommand(
IAWSClientFactory awsClientFactory,
IToolInteractiveService interactiveService,
IConsoleUtilities consoleUtilities,
ILocalUserSettingsEngine localUserSettingsEngine,
OrchestratorSession? session)
{
_awsClientFactory = awsClientFactory;
_interactiveService = interactiveService;
_consoleUtilities = consoleUtilities;
_cloudFormationClient = _awsClientFactory.GetAWSClient<IAmazonCloudFormation>();
_localUserSettingsEngine = localUserSettingsEngine;
_session = session;
}

/// <summary>
Expand Down Expand Up @@ -66,12 +78,14 @@ await _cloudFormationClient.DeleteStackAsync(new DeleteStackRequest
var _ = monitor.StartAsync();

await WaitForStackDelete(stackName);

if (_session != null)
{
await _localUserSettingsEngine.DeleteLastDeployedStack(stackName, _session.ProjectDefinition.ProjectName, _session.AWSAccountId, _session.AWSRegion);
}

_interactiveService.WriteLine($"{stackName}: deleted");
}
catch (AmazonCloudFormationException)
{
throw new FailedToDeleteException($"Failed to delete {stackName} stack.");
}
finally
{
// Stop monitoring CloudFormation stack status once the deletion operation finishes
Expand Down Expand Up @@ -136,19 +150,52 @@ private async Task WaitForStackDelete(string stackName)

private async Task<Stack?> GetStackAsync(string stackName)
{
try
var retryCount = 0;
var shouldRetry = false;

Stack? stack = null;
do
{
var response = await _cloudFormationClient.DescribeStacksAsync(new DescribeStacksRequest
var waitTime = GetWaitTime(retryCount);
try
{
StackName = stackName
});
await Task.Delay(waitTime);

return response.Stacks.Count == 0 ? null : response.Stacks[0];
}
catch (AmazonCloudFormationException exception) when (exception.ErrorCode.Equals("ValidationError") && exception.Message.Equals($"Stack with id {stackName} does not exist"))
{
return null;
var response = await _cloudFormationClient.DescribeStacksAsync(new DescribeStacksRequest
{
StackName = stackName
});

stack = response.Stacks.Count == 0 ? null : response.Stacks[0];
shouldRetry = false;
}
catch (AmazonCloudFormationException exception) when (exception.ErrorCode.Equals("ValidationError") && exception.Message.Equals($"Stack with id {stackName} does not exist"))
{
shouldRetry = false;
}
catch (AmazonCloudFormationException exception) when (exception.ErrorCode.Equals("ThrottlingException"))
{
_interactiveService.WriteDebugLine(exception.PrettyPrint());
shouldRetry = true;
}
} while (shouldRetry && retryCount++ < MAX_RETRIES);

return stack;
}

/// <summary>
/// Returns the next wait interval, in milliseconds, using an exponential backoff algorithm
/// Read more here https://docs.aws.amazon.com/general/latest/gr/api-retries.html
/// </summary>
/// <param name="retryCount"></param>
/// <returns></returns>
private static TimeSpan GetWaitTime(int retryCount) {
if (retryCount == 0) {
return TimeSpan.Zero;
}

var waitTime = Math.Pow(2, retryCount) * 5;
return TimeSpan.FromSeconds(waitTime);
}
}
}
61 changes: 19 additions & 42 deletions src/AWS.Deploy.CLI/Commands/DeployCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
using AWS.Deploy.Orchestration.Data;
using AWS.Deploy.Orchestration.Utilities;
using AWS.Deploy.Orchestration.DisplayedResources;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Orchestration.LocalUserSettings;

namespace AWS.Deploy.CLI.Commands
{
Expand All @@ -35,9 +37,10 @@ public class DeployCommand
private readonly ITypeHintCommandFactory _typeHintCommandFactory;
private readonly IDisplayedResourcesHandler _displayedResourcesHandler;
private readonly ICloudApplicationNameGenerator _cloudApplicationNameGenerator;
private readonly ILocalUserSettingsEngine _localUserSettingsEngine;
private readonly IConsoleUtilities _consoleUtilities;
private readonly ICustomRecipeLocator _customRecipeLocator;

private readonly ISystemCapabilityEvaluator _systemCapabilityEvaluator;
private readonly OrchestratorSession _session;

public DeployCommand(
Expand All @@ -53,8 +56,10 @@ public DeployCommand(
ITypeHintCommandFactory typeHintCommandFactory,
IDisplayedResourcesHandler displayedResourcesHandler,
ICloudApplicationNameGenerator cloudApplicationNameGenerator,
ILocalUserSettingsEngine localUserSettingsEngine,
IConsoleUtilities consoleUtilities,
ICustomRecipeLocator customRecipeLocator,
ISystemCapabilityEvaluator systemCapabilityEvaluator,
OrchestratorSession session)
{
_toolInteractiveService = toolInteractiveService;
Expand All @@ -68,18 +73,20 @@ public DeployCommand(
_typeHintCommandFactory = typeHintCommandFactory;
_displayedResourcesHandler = displayedResourcesHandler;
_cloudApplicationNameGenerator = cloudApplicationNameGenerator;
_localUserSettingsEngine = localUserSettingsEngine;
_consoleUtilities = consoleUtilities;
_session = session;
_cdkManager = cdkManager;
_customRecipeLocator = customRecipeLocator;
_systemCapabilityEvaluator = systemCapabilityEvaluator;
}

public async Task ExecuteAsync(string stackName, string deploymentProjectPath, UserDeploymentSettings? userDeploymentSettings = null)
{
var (orchestrator, selectedRecommendation, cloudApplication) = await InitializeDeployment(stackName, userDeploymentSettings, deploymentProjectPath);

// Verify Docker installation and minimum NodeJS version.
await EvaluateSystemCapabilities(_session, selectedRecommendation);
await EvaluateSystemCapabilities(selectedRecommendation);

// Configure option settings.
await ConfigureDeployment(cloudApplication, orchestrator, selectedRecommendation, userDeploymentSettings);
Expand Down Expand Up @@ -131,6 +138,7 @@ private void DisplayOutputResources(List<DisplayedResourceItem> displayedResourc
_cdkManager,
_awsResourceQueryer,
_deploymentBundleHandler,
_localUserSettingsEngine,
_dockerEngine,
_customRecipeLocator,
new List<string> { RecipeLocator.FindRecipeDefinitionsPath() });
Expand All @@ -142,7 +150,7 @@ private void DisplayOutputResources(List<DisplayedResourceItem> displayedResourc
var allDeployedApplications = await _deployedApplicationQueryer.GetExistingDeployedApplications();

// Filter compatible applications that can be re-deployed using the current set of recommendations.
var compatibleApplications = GetCompatibleApplications(allDeployedApplications, recommendations);
var compatibleApplications = await _deployedApplicationQueryer.GetCompatibleApplications(recommendations, allDeployedApplications, _session);

// Get Cloudformation stack name.
var cloudApplicationName = GetCloudApplicationName(stackName, userDeploymentSettings, compatibleApplications);
Expand Down Expand Up @@ -180,52 +188,21 @@ private void DisplayOutputResources(List<DisplayedResourceItem> displayedResourc
return (orchestrator, selectedRecommendation, cloudApplication);
}

/// <summary>
/// Filters the applications that can be re-deployed using the current set of available recommendations.
/// </summary>
/// <param name="allDeployedApplications"></param>
/// <param name="recommendations"></param>
/// <returns>A list of <see cref="CloudApplication"/> that are compatible for a re-deployment</returns>
private List<CloudApplication> GetCompatibleApplications(List<CloudApplication> allDeployedApplications, List<Recommendation> recommendations)
{
var compatibleApplications = new List<CloudApplication>();
foreach (var app in allDeployedApplications)
{
if (recommendations.Any(rec => string.Equals(rec.Recipe.Id, app.RecipeId, StringComparison.Ordinal)))
compatibleApplications.Add(app);
}
return compatibleApplications;
}

/// <summary>
/// Checks if the system meets all the necessary requirements for deployment.
/// </summary>
/// <param name="session">Holds metadata about the deployment project and the AWS account used for deployment.<see cref="OrchestratorSession"/></param>
/// <param name="selectedRecommendation">The selected recommendation settings used for deployment.<see cref="Recommendation"/></param>
public async Task EvaluateSystemCapabilities(OrchestratorSession session, Recommendation selectedRecommendation)
public async Task EvaluateSystemCapabilities(Recommendation selectedRecommendation)
{
if (_session.SystemCapabilities == null)
throw new SystemCapabilitiesNotProvidedException("The system capabilities were not provided.");

var systemCapabilities = await _session.SystemCapabilities;
if (selectedRecommendation.Recipe.DeploymentType == DeploymentTypes.CdkProject &&
!systemCapabilities.NodeJsMinVersionInstalled)
var systemCapabilities = await _systemCapabilityEvaluator.EvaluateSystemCapabilities(selectedRecommendation);
var missingCapabilitiesMessage = "";
foreach (var capability in systemCapabilities)
{
throw new MissingNodeJsException("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.");
missingCapabilitiesMessage = $"{missingCapabilitiesMessage}{capability.GetMessage()}{Environment.NewLine}";
}

if (selectedRecommendation.Recipe.DeploymentBundle == DeploymentBundleTypes.Container)
{
if (!systemCapabilities.DockerInfo.DockerInstalled)
{
throw new MissingDockerException("The selected deployment option requires Docker, which was not detected. Please install and start the appropriate version of Docker for you OS: https://docs.docker.com/engine/install/");
}

if (!systemCapabilities.DockerInfo.DockerContainerType.Equals("linux", StringComparison.OrdinalIgnoreCase))
{
throw new DockerContainerTypeException("The deployment tool requires Docker to be running in linux mode. Please switch Docker to linux mode to continue.");
}
}
if (systemCapabilities.Any())
throw new MissingSystemCapabilityException(missingCapabilitiesMessage);
}

/// <summary>
Expand Down Expand Up @@ -574,7 +551,7 @@ private async Task CreateDeploymentBundle(Orchestrator orchestrator, Recommendat
}

_toolInteractiveService.WriteLine(string.Empty);
var answer = _consoleUtilities.AskYesNoQuestion("Do you want to go back and modify the current configuration?", "true");
var answer = _consoleUtilities.AskYesNoQuestion("Do you want to go back and modify the current configuration?", "false");
if (answer == YesNo.Yes)
{
var dockerExecutionDirectory =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public async Task ExecuteAsync(string saveCdkDirectoryPath, string projectDispla
}
}

await _cdkProjectHandler.CreateCdkProjectForDeployment(selectedRecommendation, _session, saveCdkDirectoryPath);
_cdkProjectHandler.CreateCdkProjectForDeployment(selectedRecommendation, _session, saveCdkDirectoryPath);
await GenerateDeploymentRecipeSnapShot(selectedRecommendation, saveCdkDirectoryPath, projectDisplayName);

var saveCdkDirectoryFullPath = _directoryManager.GetDirectoryInfo(saveCdkDirectoryPath).FullName;
Expand Down Expand Up @@ -239,7 +239,7 @@ private async Task GenerateDeploymentRecipeSnapShot(Recommendation recommendatio
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new SerializeRecipeContractResolver()
ContractResolver = new SerializeModelContractResolver()
});
await _fileManager.WriteAllTextAsync(recipeSnapshotFilePath, recipeSnapshotBody);
}
Expand Down
9 changes: 0 additions & 9 deletions src/AWS.Deploy.CLI/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,6 @@ public class FailedToFindDeployableTargetException : Exception
public FailedToFindDeployableTargetException(string message, Exception? innerException = null) : base(message, innerException) { }
}

/// <summary>
/// Throw if docker info failed to return output.
/// </summary>
[AWSDeploymentExpectedException]
public class DockerInfoException : Exception
{
public DockerInfoException(string message, Exception? innerException = null) : base(message, innerException) { }
}

/// <summary>
/// Throw if prompting the user for a name returns a null value.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using AWS.Deploy.Orchestration.CDK;
using AWS.Deploy.Orchestration.Data;
using AWS.Deploy.Orchestration.DisplayedResources;
using AWS.Deploy.Orchestration.LocalUserSettings;
using AWS.Deploy.Orchestration.Utilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand Down Expand Up @@ -54,6 +55,7 @@ public static void AddCustomServices(this IServiceCollection serviceCollection,
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IZipFileManager), typeof(ZipFileManager), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDeploymentManifestEngine), typeof(DeploymentManifestEngine), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ICustomRecipeLocator), typeof(CustomRecipeLocator), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ILocalUserSettingsEngine), typeof(LocalUserSettingsEngine), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ICommandFactory), typeof(CommandFactory), lifetime));

var packageJsonTemplate = typeof(PackageJsonGenerator).Assembly.ReadEmbeddedFile(PackageJsonGenerator.TemplateIdentifier);
Expand Down
Loading

0 comments on commit 2ac2770

Please sign in to comment.