Skip to content

Commit

Permalink
feat: Add GetCapabilities API to check for NodeJS and Docker installa…
Browse files Browse the repository at this point in the history
…tions
  • Loading branch information
philasmar committed Aug 16, 2021
1 parent 765bc87 commit 73d6fe3
Show file tree
Hide file tree
Showing 18 changed files with 286 additions and 99 deletions.
4 changes: 1 addition & 3 deletions src/AWS.Deploy.CLI/Commands/CommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,6 @@ private Command BuildDeployCommand()
_commandLineWrapper.RegisterAWSContext(awsCredentials, awsRegion);
_awsClientFactory.RegisterAWSContext(awsCredentials, awsRegion);

var systemCapabilities = _systemCapabilityEvaluator.Evaluate();

var projectDefinition = await _projectParserUtility.Parse(input.ProjectPath ?? "");

var callerIdentity = await _awsResourceQueryer.GetCallerIdentity();
Expand All @@ -185,7 +183,6 @@ private Command BuildDeployCommand()
awsRegion,
callerIdentity.Account)
{
SystemCapabilities = systemCapabilities,
AWSProfileName = input.Profile ?? userDeploymentSettings?.AWSProfile ?? null
};

Expand All @@ -207,6 +204,7 @@ private Command BuildDeployCommand()
_localUserSettingsEngine,
_consoleUtilities,
_customRecipeLocator,
_systemCapabilityEvaluator,
session);

var deploymentProjectPath = input.DeploymentProject ?? string.Empty;
Expand Down
34 changes: 11 additions & 23 deletions src/AWS.Deploy.CLI/Commands/DeployCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class DeployCommand
private readonly ILocalUserSettingsEngine _localUserSettingsEngine;
private readonly IConsoleUtilities _consoleUtilities;
private readonly ICustomRecipeLocator _customRecipeLocator;

private readonly ISystemCapabilityEvaluator _systemCapabilityEvaluator;
private readonly OrchestratorSession _session;

public DeployCommand(
Expand All @@ -59,6 +59,7 @@ public DeployCommand(
ILocalUserSettingsEngine localUserSettingsEngine,
IConsoleUtilities consoleUtilities,
ICustomRecipeLocator customRecipeLocator,
ISystemCapabilityEvaluator systemCapabilityEvaluator,
OrchestratorSession session)
{
_toolInteractiveService = toolInteractiveService;
Expand All @@ -77,14 +78,15 @@ public DeployCommand(
_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 @@ -189,32 +191,18 @@ private void DisplayOutputResources(List<DisplayedResourceItem> displayedResourc
/// <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
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
35 changes: 35 additions & 0 deletions src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,41 @@ public async Task<IActionResult> SetDeploymentTarget(string sessionId, [FromBody
return Ok();
}

/// <summary>
/// Checks the missing System Capabilities for a given session.
/// </summary>
[HttpPost("session/<sessionId>/compatiblity")]
[SwaggerOperation(OperationId = "GetCompatibility")]
[SwaggerResponse(200, type: typeof(GetCompatibilityOutput))]
[Authorize]
public async Task<IActionResult> GetCompatibility(string sessionId)
{
var state = _stateServer.Get(sessionId);
if (state == null)
{
return NotFound($"Session ID {sessionId} not found.");
}

if (state.SelectedRecommendation == null)
{
return NotFound($"A deployment target is not set for Session ID {sessionId}.");
}

var output = new GetCompatibilityOutput();
var serviceProvider = CreateSessionServiceProvider(state);
var systemCapabilityEvaluator = serviceProvider.GetRequiredService<ISystemCapabilityEvaluator>();

var capabilities = await systemCapabilityEvaluator.EvaluateSystemCapabilities(state.SelectedRecommendation);

output.Capabilities = capabilities.Select(x => new SystemCapabilitySummary(x.Name, x.Installed, x.Available)
{
InstallationUrl = x.InstallationUrl,
Message = x.Message
}).ToList();

return Ok(output);
}

/// <summary>
/// Begin execution of the deployment.
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions src/AWS.Deploy.CLI/ServerMode/Models/GetCompatibilityOutput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using System.Collections.Generic;

namespace AWS.Deploy.CLI.ServerMode.Models
{
public class GetCompatibilityOutput
{
public IEnumerable<SystemCapabilitySummary> Capabilities { get; set; } = new List<SystemCapabilitySummary>();
}
}
21 changes: 21 additions & 0 deletions src/AWS.Deploy.CLI/ServerMode/Models/SystemCapabilitySummary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

namespace AWS.Deploy.CLI.ServerMode.Models
{
public class SystemCapabilitySummary
{
public string Name { get; set; }
public bool Installed { get; set; }
public bool Available { get; set; }
public string? Message { get; set; }
public string? InstallationUrl { get; set; }

public SystemCapabilitySummary(string name, bool installed, bool available)
{
Name = name;
Installed = installed;
Available = available;
}
}
}
30 changes: 3 additions & 27 deletions src/AWS.Deploy.Common/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,36 +47,12 @@ public InvalidProjectDefinitionException(string message, Exception? innerExcepti
}

/// <summary>
/// Throw if the user attempts to deploy a <see cref="RecipeDefinition"/>
/// that uses <see cref="DeploymentTypes.CdkProject"/>
/// but NodeJs/NPM could not be detected.
/// Thrown if there is a missing System Capability.
/// </summary>
[AWSDeploymentExpectedException]
public class MissingNodeJsException : Exception
public class MissingSystemCapabilityException : Exception
{
public MissingNodeJsException(string message, Exception? innerException = null) : base(message, innerException) { }
}

/// <summary>
/// Throw if the user attempts to deploy a <see cref="RecipeDefinition"/>
/// that requires <see cref="DeploymentBundleTypes.Container"/>
/// but Docker could not be detected.
/// </summary>
[AWSDeploymentExpectedException]
public class MissingDockerException : Exception
{
public MissingDockerException(string message, Exception? innerException = null) : base(message, innerException) { }
}

/// <summary>
/// Throw if the user attempts to deploy a <see cref="RecipeDefinition"/>
/// that requires <see cref="DeploymentBundleTypes.Container"/>
/// but Docker is not running in linux mode.
/// </summary>
[AWSDeploymentExpectedException]
public class DockerContainerTypeException : Exception
{
public DockerContainerTypeException(string message, Exception? innerException = null) : base(message, innerException) { }
public MissingSystemCapabilityException(string message, Exception? innerException = null) : base(message, innerException) { }
}

/// <summary>
Expand Down
10 changes: 10 additions & 0 deletions src/AWS.Deploy.Orchestration/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,21 @@ public class InvalidLocalUserSettingsFileException : Exception
public InvalidLocalUserSettingsFileException(string message, Exception? innerException = null) : base(message, innerException) { }
}

/// <summary>
/// Exception thrown if a failure occured while trying to update the Local User Settings file.
/// </summary>
[AWSDeploymentExpectedException]
public class FailedToUpdateLocalUserSettingsFileException : Exception
{
public FailedToUpdateLocalUserSettingsFileException(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) { }
}
}
7 changes: 0 additions & 7 deletions src/AWS.Deploy.Orchestration/OrchestratorSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ public class OrchestratorSession : IDeployToolValidationContext
public string? AWSProfileName { get; set; }
public AWSCredentials? AWSCredentials { get; set; }
public string? AWSRegion { get; set; }
/// <remarks>
/// Calculating the current <see cref="SystemCapabilities"/> can take several seconds
/// and is not needed immediately so it is run as a background Task.
/// <para />
/// It's safe to repeatedly await this property; evaluation will only be done once.
/// </remarks>
public Task<SystemCapabilities>? SystemCapabilities { get; set; }
public string? AWSAccountId { get; set; }

public OrchestratorSession(
Expand Down
26 changes: 26 additions & 0 deletions src/AWS.Deploy.Orchestration/SystemCapabilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,30 @@ public DockerInfo(
DockerContainerType = dockerContainerType;
}
}

public class SystemCapability
{
public string Name { get; set; }
public bool Installed { get; set; }
public bool Available { get; set; }
public string? Message { get; set; }
public string? InstallationUrl { get; set; }

public SystemCapability(string name, bool installed, bool available)
{
Name = name;
Installed = installed;
Available = available;
}

public string GetMessage()
{
if (!string.IsNullOrEmpty(Message))
return Message;

var availabilityMessage = Available ? "and available" : "but not available";
var installationMessage = Installed ? $"installed {availabilityMessage}" : "not installed";
return $"The system capability '{Name}' is {installationMessage}";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@
// SPDX-License-Identifier: Apache-2.0

using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using AWS.Deploy.Orchestration;
using AWS.Deploy.Orchestration.CDK;
using AWS.Deploy.Common;
using AWS.Deploy.Common.Recipes;
using AWS.Deploy.Orchestration.Utilities;

namespace AWS.Deploy.CLI
namespace AWS.Deploy.Orchestration
{
public interface ISystemCapabilityEvaluator
{
Task<SystemCapabilities> Evaluate();
Task<List<SystemCapability>> EvaluateSystemCapabilities(Recommendation selectedRecommendation);
}

internal class SystemCapabilityEvaluator : ISystemCapabilityEvaluator
public class SystemCapabilityEvaluator : ISystemCapabilityEvaluator
{
private readonly ICommandLineWrapper _commandLineWrapper;

Expand Down Expand Up @@ -77,5 +77,43 @@ private async Task<bool> HasMinVersionNodeJs()

return version.Major > 10 || version.Major == 10 && version.Minor >= 3;
}

/// <summary>
/// Checks if the system meets all the necessary requirements for deployment.
/// </summary>
public async Task<List<SystemCapability>> EvaluateSystemCapabilities(Recommendation selectedRecommendation)
{
var capabilities = new List<SystemCapability>();
var systemCapabilities = await Evaluate();
if (selectedRecommendation.Recipe.DeploymentType == DeploymentTypes.CdkProject &&
!systemCapabilities.NodeJsMinVersionInstalled)
{
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 (selectedRecommendation.Recipe.DeploymentBundle == DeploymentBundleTypes.Container)
{
if (!systemCapabilities.DockerInfo.DockerInstalled)
{
capabilities.Add(new SystemCapability("Docker", false, false)
{
InstallationUrl = "https://docs.docker.com/engine/install/",
Message = "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/"
});
}
else if (!systemCapabilities.DockerInfo.DockerContainerType.Equals("linux", StringComparison.OrdinalIgnoreCase))
{
capabilities.Add(new SystemCapability("Docker", true, false)
{
Message = "The deployment tool requires Docker to be running in linux mode. Please switch Docker to linux mode to continue."
});
}
}

return capabilities;
}
}
}
Loading

0 comments on commit 73d6fe3

Please sign in to comment.