diff --git a/docs/OptionSettingsItems-input-validation.md b/docs/OptionSettingsItems-input-validation.md
index 50fb2294e..dad54ec0e 100644
--- a/docs/OptionSettingsItems-input-validation.md
+++ b/docs/OptionSettingsItems-input-validation.md
@@ -141,18 +141,24 @@ In the `RangeValidator` example above, a recipe author can customize `Validation
### Dependencies
-Because Validators will be deserialized as part of a `RecipeDefinition` they need to have parameterless constructors and therefore can't use Constructor Injection.
+Validators may require other services during validation. For example, an option that selects a file path may need an `IFileManager` to validate that it exists.
-Validators are currently envisoned to be relatively simple to the point where they shouldn't need any dependencies. If dependencies in are needed in the future, we can explore adding an `Initialize` method that uses the ServiceLocation (anti-)pattern:
+When deserializing and initializing validators from the `RecipeDefinition` we shall inject any required services into their constructor via an `IServiceProvider` created from the collection of the Deploy Tool's custom services.
```csharp
-public interface IOptionSettingItemValidator
+public class FileExistsValidator : IOptionSettingItemValidator
{
- ///
- /// One possibile solution if we need to create a Validator that needs
- /// dependencies.
- ///
- void Initialize(IServiceLocator serviceLocator);
+ private readonly IFileManager _fileManager;
+
+ public FileExistsValidator(IFileManager fileManager)
+ {
+ _fileManager = fileManager;
+ }
+
+ public ValidationResult Validate(object input)
+ {
+ // Validate that the provided file path is valid
+ }
}
```
diff --git a/src/AWS.Deploy.CLI/Commands/CommandFactory.cs b/src/AWS.Deploy.CLI/Commands/CommandFactory.cs
index bf8f449ab..c85ab5bac 100644
--- a/src/AWS.Deploy.CLI/Commands/CommandFactory.cs
+++ b/src/AWS.Deploy.CLI/Commands/CommandFactory.cs
@@ -25,6 +25,7 @@
using AWS.Deploy.Orchestration.LocalUserSettings;
using AWS.Deploy.Orchestration.ServiceHandlers;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
namespace AWS.Deploy.CLI.Commands
{
@@ -74,6 +75,7 @@ public class CommandFactory : ICommandFactory
private readonly ICDKVersionDetector _cdkVersionDetector;
private readonly IAWSServiceHandler _awsServiceHandler;
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IValidatorFactory _validatorFactory;
public CommandFactory(
IServiceProvider serviceProvider,
@@ -101,7 +103,8 @@ public CommandFactory(
ILocalUserSettingsEngine localUserSettingsEngine,
ICDKVersionDetector cdkVersionDetector,
IAWSServiceHandler awsServiceHandler,
- IOptionSettingHandler optionSettingHandler)
+ IOptionSettingHandler optionSettingHandler,
+ IValidatorFactory validatorFactory)
{
_serviceProvider = serviceProvider;
_toolInteractiveService = toolInteractiveService;
@@ -129,6 +132,7 @@ public CommandFactory(
_cdkVersionDetector = cdkVersionDetector;
_awsServiceHandler = awsServiceHandler;
_optionSettingHandler = optionSettingHandler;
+ _validatorFactory = validatorFactory;
}
public Command BuildRootCommand()
@@ -230,7 +234,8 @@ private Command BuildDeployCommand()
_directoryManager,
_fileManager,
_awsServiceHandler,
- _optionSettingHandler);
+ _optionSettingHandler,
+ _validatorFactory);
var deploymentProjectPath = input.DeploymentProject ?? string.Empty;
if (!string.IsNullOrEmpty(deploymentProjectPath))
diff --git a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs
index 878f4c276..3acb38a3c 100644
--- a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs
+++ b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs
@@ -52,6 +52,7 @@ public class DeployCommand
private readonly ICDKVersionDetector _cdkVersionDetector;
private readonly IAWSServiceHandler _awsServiceHandler;
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IValidatorFactory _validatorFactory;
public DeployCommand(
IServiceProvider serviceProvider,
@@ -76,7 +77,8 @@ public DeployCommand(
IDirectoryManager directoryManager,
IFileManager fileManager,
IAWSServiceHandler awsServiceHandler,
- IOptionSettingHandler optionSettingHandler)
+ IOptionSettingHandler optionSettingHandler,
+ IValidatorFactory validatorFactory)
{
_serviceProvider = serviceProvider;
_toolInteractiveService = toolInteractiveService;
@@ -101,6 +103,7 @@ public DeployCommand(
_systemCapabilityEvaluator = systemCapabilityEvaluator;
_awsServiceHandler = awsServiceHandler;
_optionSettingHandler = optionSettingHandler;
+ _validatorFactory = validatorFactory;
}
public async Task ExecuteAsync(string applicationName, string deploymentProjectPath, UserDeploymentSettings? userDeploymentSettings = null)
@@ -146,14 +149,12 @@ private void DisplayOutputResources(List displayedResourc
/// If a new Cloudformation stack name is selected, then a fresh deployment is initiated with the user-selected deployment recipe.
/// If an existing deployment target is selected, then a re-deployment is initiated with the same deployment recipe.
///
- /// The cloud application name provided via the --application-name CLI argument
+ /// The cloud application name provided via the --application-name CLI argument
/// The deserialized object from the user provided config file.
/// The absolute or relative path of the CDK project that will be used for deployment
/// A tuple consisting of the Orchestrator object, Selected Recommendation, Cloud Application metadata.
- public async Task<(Orchestrator, Recommendation, CloudApplication)> InitializeDeployment(string applicationName, UserDeploymentSettings? userDeploymentSettings, string deploymentProjectPath)
+ public async Task<(Orchestrator, Recommendation, CloudApplication)> InitializeDeployment(string cloudApplicationName, UserDeploymentSettings? userDeploymentSettings, string deploymentProjectPath)
{
- string cloudApplicationName;
-
var orchestrator = new Orchestrator(
_session,
_orchestratorInteractiveService,
@@ -180,8 +181,9 @@ private void DisplayOutputResources(List displayedResourc
// Filter compatible applications that can be re-deployed using the current set of recommendations.
var compatibleApplications = await _deployedApplicationQueryer.GetCompatibleApplications(recommendations, allDeployedApplications, _session);
- // Try finding the CloudApplication name via the --application-name CLI argument or user provided config settings.
- cloudApplicationName = GetCloudApplicationNameFromDeploymentSettings(applicationName, userDeploymentSettings);
+ if (string.IsNullOrEmpty(cloudApplicationName))
+ // Try finding the CloudApplication name via the user provided config settings.
+ cloudApplicationName = userDeploymentSettings?.ApplicationName ?? string.Empty;
// Prompt the user with a choice to re-deploy to existing targets or deploy to a new cloud application.
if (string.IsNullOrEmpty(cloudApplicationName))
@@ -222,13 +224,20 @@ private void DisplayOutputResources(List displayedResourc
// The ECR repository name is already configurable as part of the recipe option settings.
if (selectedRecommendation.Recipe.DeploymentType == DeploymentTypes.ElasticContainerRegistryImage)
{
- cloudApplicationName = _cloudApplicationNameGenerator.GenerateValidName(_session.ProjectDefinition, compatibleApplications);
+ cloudApplicationName = _cloudApplicationNameGenerator.GenerateValidName(_session.ProjectDefinition, compatibleApplications, selectedRecommendation.Recipe.DeploymentType);
}
else
{
cloudApplicationName = AskForNewCloudApplicationName(selectedRecommendation.Recipe.DeploymentType, compatibleApplications);
}
}
+ // cloudApplication name was already provided via CLI args or the deployment config file
+ else
+ {
+ var validationResult = _cloudApplicationNameGenerator.IsValidName(cloudApplicationName, allDeployedApplications, selectedRecommendation.Recipe.DeploymentType);
+ if (!validationResult.IsValid)
+ throw new InvalidCloudApplicationNameException(DeployToolErrorCode.InvalidCloudApplicationName, validationResult.ErrorMessage);
+ }
}
await orchestrator.ApplyAllReplacementTokens(selectedRecommendation, cloudApplicationName);
@@ -451,16 +460,13 @@ private void ConfigureDeploymentFromConfigFile(Recommendation recommendation, Us
throw new InvalidOverrideValueException(DeployToolErrorCode.InvalidValueForOptionSettingItem, $"Invalid value {optionSettingValue} for option setting item {optionSettingJsonPath}");
}
- _optionSettingHandler.SetOptionSettingValue(optionSetting, settingValue);
-
- SetDeploymentBundleOptionSetting(recommendation, optionSetting.Id, settingValue);
+ _optionSettingHandler.SetOptionSettingValue(recommendation, optionSetting, settingValue);
}
}
var validatorFailedResults =
- recommendation.Recipe
- .BuildValidators()
- .Select(validator => validator.Validate(recommendation, _session, _optionSettingHandler))
+ _validatorFactory.BuildValidators(recommendation.Recipe)
+ .Select(validator => validator.Validate(recommendation, _session))
.Where(x => !x.IsValid)
.ToList();
@@ -479,57 +485,6 @@ private void ConfigureDeploymentFromConfigFile(Recommendation recommendation, Us
throw new InvalidUserDeploymentSettingsException(DeployToolErrorCode.DeploymentConfigurationNeedsAdjusting, errorMessage.Trim());
}
- private void SetDeploymentBundleOptionSetting(Recommendation recommendation, string optionSettingId, object settingValue)
- {
- switch (optionSettingId)
- {
- case "DockerExecutionDirectory":
- ActivatorUtilities.CreateInstance(_serviceProvider).OverrideValue(recommendation, settingValue.ToString() ?? "");
- break;
- case "DockerBuildArgs":
- ActivatorUtilities.CreateInstance(_serviceProvider).OverrideValue(recommendation, settingValue.ToString() ?? "");
- break;
- case "DotnetBuildConfiguration":
- ActivatorUtilities.CreateInstance(_serviceProvider).Overridevalue(recommendation, settingValue.ToString() ?? "");
- break;
- case "DotnetPublishArgs":
- ActivatorUtilities.CreateInstance(_serviceProvider).OverrideValue(recommendation, settingValue.ToString() ?? "");
- break;
- case "SelfContainedBuild":
- ActivatorUtilities.CreateInstance(_serviceProvider).OverrideValue(recommendation, (bool)settingValue);
- break;
- default:
- return;
- }
- }
-
- // This method tries to find the cloud application name via the user provided CLI arguments or deployment config file.
- // If a name is not present at either of the places then return string.empty
- private string GetCloudApplicationNameFromDeploymentSettings(string? applicationName, UserDeploymentSettings? userDeploymentSettings)
- {
- // validate and return the applicationName provided by the --application-name cli argument if present.
- if (!string.IsNullOrEmpty(applicationName))
- {
- if (_cloudApplicationNameGenerator.IsValidName(applicationName))
- return applicationName;
-
- PrintInvalidApplicationNameMessage(applicationName);
- throw new InvalidCliArgumentException(DeployToolErrorCode.InvalidCliArguments, "Found invalid CLI arguments");
- }
-
- // validate and return the applicationName from the deployment settings if present.
- if (!string.IsNullOrEmpty(userDeploymentSettings?.ApplicationName))
- {
- if (_cloudApplicationNameGenerator.IsValidName(userDeploymentSettings.ApplicationName))
- return userDeploymentSettings.ApplicationName;
-
- PrintInvalidApplicationNameMessage(userDeploymentSettings.ApplicationName);
- throw new InvalidUserDeploymentSettingsException(DeployToolErrorCode.UserDeploymentInvalidStackName, "Please provide a valid cloud application name and try again.");
- }
-
- return string.Empty;
- }
-
// This method prompts the user to select a CloudApplication name for existing deployments or create a new one.
// If a user chooses to create a new CloudApplication, then this method returns string.Empty
private string AskForCloudApplicationNameFromDeployedApplications(List deployedApplications)
@@ -573,14 +528,14 @@ private string AskForNewCloudApplicationName(DeploymentTypes deploymentType, Lis
try
{
- defaultName = _cloudApplicationNameGenerator.GenerateValidName(_session.ProjectDefinition, deployedApplications);
+ defaultName = _cloudApplicationNameGenerator.GenerateValidName(_session.ProjectDefinition, deployedApplications, deploymentType);
}
catch (Exception exception)
{
_toolInteractiveService.WriteDebugLine(exception.PrettyPrint());
}
- var cloudApplicationName = "";
+ var cloudApplicationName = string.Empty;
while (true)
{
@@ -610,12 +565,14 @@ private string AskForNewCloudApplicationName(DeploymentTypes deploymentType, Lis
allowEmpty: false,
defaultAskValuePrompt: inputPrompt);
- if (string.IsNullOrEmpty(cloudApplicationName) || !_cloudApplicationNameGenerator.IsValidName(cloudApplicationName))
- PrintInvalidApplicationNameMessage(cloudApplicationName);
- else if (deployedApplications.Any(x => x.Name.Equals(cloudApplicationName)))
- PrintApplicationNameAlreadyExistsMessage();
- else
+ var validationResult = _cloudApplicationNameGenerator.IsValidName(cloudApplicationName, deployedApplications, deploymentType);
+ if (validationResult.IsValid)
+ {
return cloudApplicationName;
+ }
+
+ _toolInteractiveService.WriteLine();
+ _toolInteractiveService.WriteErrorLine(validationResult.ErrorMessage);
}
}
@@ -653,20 +610,6 @@ private Recommendation GetSelectedRecommendation(UserDeploymentSettings? userDep
return selectedRecommendation;
}
- private void PrintInvalidApplicationNameMessage(string name)
- {
- _toolInteractiveService.WriteLine();
- _toolInteractiveService.WriteErrorLine(_cloudApplicationNameGenerator.InvalidNameMessage(name));
- }
-
- private void PrintApplicationNameAlreadyExistsMessage()
- {
- _toolInteractiveService.WriteLine();
- _toolInteractiveService.WriteErrorLine(
- "Invalid application name. There already exists a CloudFormation stack with the name you provided. " +
- "Please choose another application name.");
- }
-
private bool ConfirmDeployment(Recommendation recommendation)
{
var message = recommendation.Recipe.DeploymentConfirmation?.DefaultMessage;
@@ -768,9 +711,8 @@ private async Task ConfigureDeploymentFromCli(Recommendation recommendation, IEn
if (string.IsNullOrEmpty(input))
{
var validatorFailedResults =
- recommendation.Recipe
- .BuildValidators()
- .Select(validator => validator.Validate(recommendation, _session, _optionSettingHandler))
+ _validatorFactory.BuildValidators(recommendation.Recipe)
+ .Select(validator => validator.Validate(recommendation, _session))
.Where(x => !x.IsValid)
.ToList();
@@ -887,7 +829,7 @@ private async Task ConfigureDeploymentFromCli(Recommendation recommendation, Opt
{
try
{
- _optionSettingHandler.SetOptionSettingValue(setting, settingValue);
+ _optionSettingHandler.SetOptionSettingValue(recommendation, setting, settingValue);
}
catch (ValidationFailedException ex)
{
diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/DockerBuildArgsCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/DockerBuildArgsCommand.cs
index d68e15799..7d293f3e7 100644
--- a/src/AWS.Deploy.CLI/Commands/TypeHints/DockerBuildArgsCommand.cs
+++ b/src/AWS.Deploy.CLI/Commands/TypeHints/DockerBuildArgsCommand.cs
@@ -2,10 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
using System.Collections.Generic;
-using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AWS.Deploy.Common;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Common.TypeHintData;
namespace AWS.Deploy.CLI.Commands.TypeHints
@@ -55,20 +55,16 @@ public void OverrideValue(Recommendation recommendation, string dockerBuildArgs)
private string ValidateBuildArgs(string buildArgs)
{
- var argsList = buildArgs.Split(",");
- if (argsList.Length == 0)
- return "";
+ var validationResult = new DockerBuildArgsValidator().Validate(buildArgs);
- foreach (var arg in argsList)
+ if (validationResult.IsValid)
{
- var keyValue = arg.Split("=");
- if (keyValue.Length == 2)
- return "";
- else
- return "The Docker Build Args must have the following pattern 'arg1=val1,arg2=val2'.";
+ return string.Empty;
+ }
+ else
+ {
+ return validationResult.ValidationFailedMessage ?? "Invalid value for additional Docker build options.";
}
-
- return "";
}
}
}
diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/DockerExecutionDirectoryCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/DockerExecutionDirectoryCommand.cs
index 6940f5e4b..390aaa5e1 100644
--- a/src/AWS.Deploy.CLI/Commands/TypeHints/DockerExecutionDirectoryCommand.cs
+++ b/src/AWS.Deploy.CLI/Commands/TypeHints/DockerExecutionDirectoryCommand.cs
@@ -7,6 +7,7 @@
using AWS.Deploy.Common;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Common.TypeHintData;
namespace AWS.Deploy.CLI.Commands.TypeHints
@@ -56,10 +57,16 @@ public void OverrideValue(Recommendation recommendation, string executionDirecto
private string ValidateExecutionDirectory(string executionDirectory)
{
- if (!string.IsNullOrEmpty(executionDirectory) && !_directoryManager.Exists(executionDirectory))
- return "The directory specified for Docker execution does not exist.";
+ var validationResult = new DirectoryExistsValidator(_directoryManager).Validate(executionDirectory);
+
+ if (validationResult.IsValid)
+ {
+ return string.Empty;
+ }
else
- return "";
+ { // Override the generic ValidationResultMessage with one about the the Docker execution directory
+ return "The directory specified for Docker execution does not exist.";
+ }
}
}
}
diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetPublishArgsCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetPublishArgsCommand.cs
index eec8a3fe3..043e2b419 100644
--- a/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetPublishArgsCommand.cs
+++ b/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetPublishArgsCommand.cs
@@ -1,11 +1,11 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AWS.Deploy.Common;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Common.TypeHintData;
namespace AWS.Deploy.CLI.Commands.TypeHints
@@ -56,18 +56,16 @@ public void OverrideValue(Recommendation recommendation, string publishArgs)
private string ValidateDotnetPublishArgs(string publishArgs)
{
- var resultString = string.Empty;
+ var validationResult = new DotnetPublishArgsValidator().Validate(publishArgs);
- if (publishArgs.Contains("-o ") || publishArgs.Contains("--output "))
- resultString += "You must not include -o/--output as an additional argument as it is used internally." + Environment.NewLine;
- if (publishArgs.Contains("-c ") || publishArgs.Contains("--configuration "))
- resultString += "You must not include -c/--configuration as an additional argument. You can set the build configuration in the advanced settings." + Environment.NewLine;
- if (publishArgs.Contains("--self-contained") || publishArgs.Contains("--no-self-contained"))
- resultString += "You must not include --self-contained/--no-self-contained as an additional argument. You can set the self-contained property in the advanced settings." + Environment.NewLine;
-
- if (!string.IsNullOrEmpty(resultString))
- return "Invalid valid value for Dotnet Publish Arguments." + Environment.NewLine + resultString.Trim();
- return "";
+ if (validationResult.IsValid)
+ {
+ return string.Empty;
+ }
+ else
+ {
+ return validationResult.ValidationFailedMessage ?? "Invalid value for Dotnet Publish Arguments.";
+ }
}
}
}
diff --git a/src/AWS.Deploy.CLI/Exceptions.cs b/src/AWS.Deploy.CLI/Exceptions.cs
index f70fafc75..853dad147 100644
--- a/src/AWS.Deploy.CLI/Exceptions.cs
+++ b/src/AWS.Deploy.CLI/Exceptions.cs
@@ -84,4 +84,12 @@ public class FailedToGetCredentialsForProfile : DeployToolException
{
public FailedToGetCredentialsForProfile(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }
}
+
+ ///
+ /// Throw if cloud application name is invalid.
+ ///
+ public class InvalidCloudApplicationNameException : DeployToolException
+ {
+ public InvalidCloudApplicationNameException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }
+ }
}
diff --git a/src/AWS.Deploy.CLI/Extensions/CustomServiceCollectionExtension.cs b/src/AWS.Deploy.CLI/Extensions/CustomServiceCollectionExtension.cs
index 52791f41b..770ed5940 100644
--- a/src/AWS.Deploy.CLI/Extensions/CustomServiceCollectionExtension.cs
+++ b/src/AWS.Deploy.CLI/Extensions/CustomServiceCollectionExtension.cs
@@ -9,6 +9,7 @@
using AWS.Deploy.Common.Extensions;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
using AWS.Deploy.Orchestration.CDK;
using AWS.Deploy.Orchestration.Data;
@@ -64,6 +65,7 @@ public static void AddCustomServices(this IServiceCollection serviceCollection,
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IS3Handler), typeof(AWSS3Handler), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IElasticBeanstalkHandler), typeof(AWSElasticBeanstalkHandler), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IOptionSettingHandler), typeof(OptionSettingHandler), lifetime));
+ serviceCollection.TryAdd(new ServiceDescriptor(typeof(IValidatorFactory), typeof(ValidatorFactory), lifetime));
var packageJsonTemplate = typeof(PackageJsonGenerator).Assembly.ReadEmbeddedFile(PackageJsonGenerator.TemplateIdentifier);
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IPackageJsonGenerator), (serviceProvider) => new PackageJsonGenerator(packageJsonTemplate), lifetime));
diff --git a/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs b/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs
index 86f6b8028..4d2553978 100644
--- a/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs
+++ b/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs
@@ -146,6 +146,7 @@ public async Task GetRecommendations(string sessionId)
baseRecipeId: recommendation.Recipe.BaseRecipeId,
recipeId: recommendation.Recipe.Id,
name: recommendation.Name,
+ settingsCategories: CategorySummary.FromCategories(recommendation.GetConfigurableOptionSettingCategories()),
isPersistedDeploymentProject: recommendation.Recipe.PersistedDeploymentProject,
shortDescription: recommendation.ShortDescription,
description: recommendation.Description,
@@ -198,6 +199,7 @@ private List ListOptionSettingSummary(IOptionSettingHa
{
var settingSummary = new OptionSettingItemSummary(setting.Id, setting.Name, setting.Description, setting.Type.ToString())
{
+ Category = setting.Category,
TypeHint = setting.TypeHint?.ToString(),
TypeHintData = setting.TypeHintData,
Value = optionSettingHandler.GetOptionSettingValue(recommendation, setting),
@@ -249,7 +251,7 @@ public IActionResult ApplyConfigSettings(string sessionId, [FromBody] ApplyConfi
try
{
var setting = optionSettingHandler.GetOptionSetting(state.SelectedRecommendation, updatedSetting.Key);
- optionSettingHandler.SetOptionSettingValue(setting, updatedSetting.Value);
+ optionSettingHandler.SetOptionSettingValue(state.SelectedRecommendation, setting, updatedSetting.Value);
}
catch (Exception ex)
{
@@ -340,6 +342,7 @@ public async Task GetExistingDeployments(string sessionId)
baseRecipeId: recommendation.Recipe.BaseRecipeId,
recipeId: deployment.RecipeId,
recipeName: recommendation.Name,
+ settingsCategories: CategorySummary.FromCategories(recommendation.GetConfigurableOptionSettingCategories()),
isPersistedDeploymentProject: recommendation.Recipe.PersistedDeploymentProject,
shortDescription: recommendation.ShortDescription,
description: recommendation.Description,
@@ -383,14 +386,13 @@ public async Task SetDeploymentTarget(string sessionId, [FromBody
return NotFound($"Recommendation {input.NewDeploymentRecipeId} not found.");
}
+ // We only validate the name when the recipe deployment type is not ElasticContainerRegistryImage.
+ // This is because pushing images to ECR does not need a cloud application name.
if (state.SelectedRecommendation.Recipe.DeploymentType != Common.Recipes.DeploymentTypes.ElasticContainerRegistryImage)
{
- // We only validate the name when the recipe deployment type is not ElasticContainerRegistryImage.
- // This is because pushing images to ECR does not need a cloud application name.
- if (!cloudApplicationNameGenerator.IsValidName(newDeploymentName))
- {
- return ValidationProblem(cloudApplicationNameGenerator.InvalidNameMessage(newDeploymentName));
- }
+ var validationResult = cloudApplicationNameGenerator.IsValidName(newDeploymentName, state.ExistingDeployments ?? new List(), state.SelectedRecommendation.Recipe.DeploymentType);
+ if (!validationResult.IsValid)
+ return ValidationProblem(validationResult.ErrorMessage);
}
state.ApplicationDetails.Name = newDeploymentName;
@@ -462,11 +464,7 @@ public async Task GetCompatibility(string sessionId)
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();
+ output.Capabilities = capabilities.Select(x => new SystemCapabilitySummary(x.Name, x.Message, x.InstallationUrl));
return Ok(output);
}
@@ -583,7 +581,7 @@ public IActionResult GetDeploymentStatus(string sessionId)
var innerException = state.DeploymentTask.Exception.InnerException;
if (innerException is DeployToolException deployToolException)
{
- output.Exception = new DeployToolExceptionSummary(deployToolException.ErrorCode.ToString(), deployToolException.Message);
+ output.Exception = new DeployToolExceptionSummary(deployToolException.ErrorCode.ToString(), deployToolException.Message, deployToolException.ProcessExitCode);
}
else
{
diff --git a/src/AWS.Deploy.CLI/ServerMode/ExtensionMethods.cs b/src/AWS.Deploy.CLI/ServerMode/ExtensionMethods.cs
index 95eb368ea..88afe9c88 100644
--- a/src/AWS.Deploy.CLI/ServerMode/ExtensionMethods.cs
+++ b/src/AWS.Deploy.CLI/ServerMode/ExtensionMethods.cs
@@ -59,7 +59,8 @@ public static void ConfigureExceptionHandler(this IApplicationBuilder app)
exceptionString = JsonSerializer.Serialize(
new DeployToolExceptionSummary(
deployToolException.ErrorCode.ToString(),
- deployToolException.Message));
+ deployToolException.Message,
+ deployToolException.ProcessExitCode));
}
else
{
diff --git a/src/AWS.Deploy.CLI/ServerMode/Models/CategorySummary.cs b/src/AWS.Deploy.CLI/ServerMode/Models/CategorySummary.cs
new file mode 100644
index 000000000..890af0024
--- /dev/null
+++ b/src/AWS.Deploy.CLI/ServerMode/Models/CategorySummary.cs
@@ -0,0 +1,47 @@
+// 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;
+
+namespace AWS.Deploy.CLI.ServerMode.Models
+{
+ ///
+ /// A category defined in the recipe that settings will be mapped to via the Id property.
+ ///
+ public class CategorySummary
+ {
+ ///
+ /// The id of the category that will be specified on top level settings.
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// The display name of the category shown to users in UI screens.
+ ///
+ public string DisplayName { get; set; }
+
+ ///
+ /// The order used to sort categories in UI screens. Categories will be shown in sorted descending order.
+ ///
+ public int Order { get; set; }
+
+ public CategorySummary(string id, string displayName, int order)
+ {
+ Id = id;
+ DisplayName = displayName;
+ Order = order;
+ }
+
+ ///
+ /// Transform recipe category types into the this ServerMode model type.
+ ///
+ ///
+ ///
+ public static List FromCategories(List categories)
+ {
+ return categories.Select(x => new CategorySummary(id: x.Id, displayName: x.DisplayName, order: x.Order)).ToList();
+ }
+ }
+}
diff --git a/src/AWS.Deploy.CLI/ServerMode/Models/DeployToolExceptionSummary.cs b/src/AWS.Deploy.CLI/ServerMode/Models/DeployToolExceptionSummary.cs
index 93e641ca2..b5c1bc58f 100644
--- a/src/AWS.Deploy.CLI/ServerMode/Models/DeployToolExceptionSummary.cs
+++ b/src/AWS.Deploy.CLI/ServerMode/Models/DeployToolExceptionSummary.cs
@@ -7,11 +7,13 @@ public class DeployToolExceptionSummary
{
public string ErrorCode { get; set; }
public string Message { get; set; }
+ public int? ProcessExitCode { get; set; }
- public DeployToolExceptionSummary(string errorCode, string message)
+ public DeployToolExceptionSummary(string errorCode, string message, int? processExitCode = null)
{
ErrorCode = errorCode;
Message = message;
+ ProcessExitCode = processExitCode;
}
}
}
diff --git a/src/AWS.Deploy.CLI/ServerMode/Models/ExistingDeploymentSummary.cs b/src/AWS.Deploy.CLI/ServerMode/Models/ExistingDeploymentSummary.cs
index 1158ef352..8364d82cb 100644
--- a/src/AWS.Deploy.CLI/ServerMode/Models/ExistingDeploymentSummary.cs
+++ b/src/AWS.Deploy.CLI/ServerMode/Models/ExistingDeploymentSummary.cs
@@ -39,6 +39,7 @@ public class ExistingDeploymentSummary
public bool UpdatedByCurrentUser { get; set; }
public DeploymentTypes DeploymentType { get; set; }
+ public List SettingsCategories { get; set; }
public string ExistingDeploymentId { get; set; }
@@ -47,6 +48,7 @@ public ExistingDeploymentSummary(
string? baseRecipeId,
string recipeId,
string recipeName,
+ List settingsCategories,
bool isPersistedDeploymentProject,
string shortDescription,
string description,
@@ -61,6 +63,7 @@ string uniqueIdentifier
BaseRecipeId = baseRecipeId;
RecipeId = recipeId;
RecipeName = recipeName;
+ SettingsCategories = settingsCategories;
IsPersistedDeploymentProject = isPersistedDeploymentProject;
ShortDescription = shortDescription;
Description = description;
diff --git a/src/AWS.Deploy.CLI/ServerMode/Models/OptionSettingItemSummary.cs b/src/AWS.Deploy.CLI/ServerMode/Models/OptionSettingItemSummary.cs
index d20264033..51138340e 100644
--- a/src/AWS.Deploy.CLI/ServerMode/Models/OptionSettingItemSummary.cs
+++ b/src/AWS.Deploy.CLI/ServerMode/Models/OptionSettingItemSummary.cs
@@ -12,6 +12,8 @@ public class OptionSettingItemSummary
public string Name { get; set; }
+ public string? Category { get; set; }
+
public string Description { get; set; }
public object? Value { get; set; }
diff --git a/src/AWS.Deploy.CLI/ServerMode/Models/RecommendationSummary.cs b/src/AWS.Deploy.CLI/ServerMode/Models/RecommendationSummary.cs
index 8d48464ed..28345cc1e 100644
--- a/src/AWS.Deploy.CLI/ServerMode/Models/RecommendationSummary.cs
+++ b/src/AWS.Deploy.CLI/ServerMode/Models/RecommendationSummary.cs
@@ -24,11 +24,13 @@ public class RecommendationSummary
public string Description { get; set; }
public string TargetService { get; set; }
public DeploymentTypes DeploymentType { get; set; }
+ public List SettingsCategories { get; set; }
public RecommendationSummary(
string? baseRecipeId,
string recipeId,
string name,
+ List settingsCategories,
bool isPersistedDeploymentProject,
string shortDescription,
string description,
@@ -39,6 +41,7 @@ Common.Recipes.DeploymentTypes deploymentType
BaseRecipeId = baseRecipeId;
RecipeId = recipeId;
Name = name;
+ SettingsCategories = settingsCategories;
IsPersistedDeploymentProject = isPersistedDeploymentProject;
ShortDescription = shortDescription;
Description = description;
diff --git a/src/AWS.Deploy.CLI/ServerMode/Models/SystemCapabilitySummary.cs b/src/AWS.Deploy.CLI/ServerMode/Models/SystemCapabilitySummary.cs
index 393ae86b6..a8a0fb55a 100644
--- a/src/AWS.Deploy.CLI/ServerMode/Models/SystemCapabilitySummary.cs
+++ b/src/AWS.Deploy.CLI/ServerMode/Models/SystemCapabilitySummary.cs
@@ -6,16 +6,14 @@ 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 Message { get; set; }
public string? InstallationUrl { get; set; }
- public SystemCapabilitySummary(string name, bool installed, bool available)
+ public SystemCapabilitySummary(string name, string message, string? installationUrl = null)
{
Name = name;
- Installed = installed;
- Available = available;
+ Message = message;
+ InstallationUrl = installationUrl;
}
}
}
diff --git a/src/AWS.Deploy.Common/CloudApplication.cs b/src/AWS.Deploy.Common/CloudApplication.cs
index d3ffee9b8..5cc42e6de 100644
--- a/src/AWS.Deploy.Common/CloudApplication.cs
+++ b/src/AWS.Deploy.Common/CloudApplication.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using AWS.Deploy.Common.Recipes;
namespace AWS.Deploy.Common
{
@@ -19,6 +20,14 @@ public class CloudApplication
{ CloudApplicationResourceType.ElasticContainerRegistryImage, "ECR Repository" }
};
+ private readonly Dictionary _deploymentTypeMapping =
+ new()
+ {
+ { CloudApplicationResourceType.CloudFormationStack, DeploymentTypes.CdkProject},
+ { CloudApplicationResourceType.BeanstalkEnvironment, DeploymentTypes.BeanstalkEnvironment },
+ { CloudApplicationResourceType.ElasticContainerRegistryImage, DeploymentTypes.ElasticContainerRegistryImage }
+ };
+
///
/// Name of the CloudApplication resource
///
@@ -38,8 +47,7 @@ public class CloudApplication
public string RecipeId { get; set; }
///
- /// indicates the type of the AWS resource which serves as the deployment target.
- /// Current supported values are None, CloudFormationStack and BeanstalkEnvironment.
+ /// Indicates the type of the AWS resource which serves as the deployment target.
///
public CloudApplicationResourceType ResourceType { get; set; }
@@ -63,6 +71,11 @@ public class CloudApplication
///
public override string ToString() => Name;
+ ///
+ /// Gets the deployment type of the recommendation that was used to deploy the cloud application.
+ ///
+ public DeploymentTypes DeploymentType => _deploymentTypeMapping[ResourceType];
+
public CloudApplication(string name, string uniqueIdentifier, CloudApplicationResourceType resourceType, string recipeId, DateTime? lastUpdatedTime = null)
{
Name = name;
diff --git a/src/AWS.Deploy.Common/Exceptions.cs b/src/AWS.Deploy.Common/Exceptions.cs
index b5df6d0ad..3c301e218 100644
--- a/src/AWS.Deploy.Common/Exceptions.cs
+++ b/src/AWS.Deploy.Common/Exceptions.cs
@@ -9,10 +9,12 @@ namespace AWS.Deploy.Common
public abstract class DeployToolException : Exception
{
public DeployToolErrorCode ErrorCode { get; set; }
+ public int? ProcessExitCode { get; set; }
- public DeployToolException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(message, innerException)
+ public DeployToolException(DeployToolErrorCode errorCode, string message, Exception? innerException = null, int? processExitCode = null) : base(message, innerException)
{
ErrorCode = errorCode;
+ ProcessExitCode = processExitCode;
}
}
@@ -110,7 +112,9 @@ public enum DeployToolErrorCode
FailedToRunCDKDiff = 10009000,
FailedToCreateCDKProject = 10009100,
ResourceQuery = 10009200,
- FailedToRetrieveStackId = 10009300
+ FailedToRetrieveStackId = 10009300,
+ FailedToGetECRAuthorizationToken = 10009400,
+ InvalidCloudApplicationName = 10009500
}
public class ProjectFileNotFoundException : DeployToolException
@@ -183,7 +187,7 @@ public ParsingExistingCloudApplicationMetadataException(DeployToolErrorCode erro
///
public class FailedToCreateDeploymentBundleException : DeployToolException
{
- public FailedToCreateDeploymentBundleException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }
+ public FailedToCreateDeploymentBundleException(DeployToolErrorCode errorCode, string message, int? processExitCode, Exception? innerException = null) : base(errorCode, message, innerException, processExitCode) { }
}
///
diff --git a/src/AWS.Deploy.Common/IO/DirectoryManager.cs b/src/AWS.Deploy.Common/IO/DirectoryManager.cs
index 05a2ad96f..e6ae1765d 100644
--- a/src/AWS.Deploy.Common/IO/DirectoryManager.cs
+++ b/src/AWS.Deploy.Common/IO/DirectoryManager.cs
@@ -13,7 +13,30 @@ public interface IDirectoryManager
{
DirectoryInfo CreateDirectory(string path);
DirectoryInfo GetDirectoryInfo(string path);
+
+ ///
+ /// Determines whether the given path refers to an existing directory on disk.
+ /// This can either be an absolute path or relative to the current working directory.
+ ///
+ /// The path to test
+ ///
+ /// true if path refers to an existing directory;
+ /// false if the directory does not exist or an error occurs when trying to determine if the specified directory exists
+ ///
bool Exists(string path);
+
+ ///
+ /// Determines whether the given path refers to an existing directory on disk.
+ /// This can either be an absolute path or relative to the given directory.
+ ///
+ /// The path to test
+ /// Directory to consider the path as relative to
+ ///
+ /// true if path refers to an existing directory;
+ /// false if the directory does not exist or an error occurs when trying to determine if the specified directory exists
+ ///
+ bool Exists(string path, string relativeTo);
+
string[] GetFiles(string path, string? searchPattern = null, SearchOption searchOption = SearchOption.TopDirectoryOnly);
string[] GetDirectories(string path, string? searchPattern = null, SearchOption searchOption = SearchOption.TopDirectoryOnly);
bool IsEmpty(string path);
@@ -38,6 +61,18 @@ public class DirectoryManager : IDirectoryManager
public bool Exists(string path) => IsDirectoryValid(path);
+ public bool Exists(string path, string relativeTo)
+ {
+ if (Path.IsPathRooted(path))
+ {
+ return Exists(path);
+ }
+ else
+ {
+ return Exists(Path.Combine(relativeTo, path));
+ }
+ }
+
public string[] GetFiles(string path, string? searchPattern = null, SearchOption searchOption = SearchOption.TopDirectoryOnly)
=> Directory.GetFiles(path, searchPattern ?? "*", searchOption);
diff --git a/src/AWS.Deploy.Common/Recipes/Category.cs b/src/AWS.Deploy.Common/Recipes/Category.cs
new file mode 100644
index 000000000..5e176fbac
--- /dev/null
+++ b/src/AWS.Deploy.Common/Recipes/Category.cs
@@ -0,0 +1,40 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace AWS.Deploy.Common.Recipes
+{
+ ///
+ /// A category defined in the recipe that settings will be mapped to via the Id property.
+ ///
+ public class Category
+ {
+ public static readonly Category General = new Category("General", "General", 0);
+ public static readonly Category DeploymentBundle = new Category("DeploymentBuildSettings", "Project Build", 1000);
+
+ ///
+ /// The id of the category that will be specified on top level settings.
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// The display name of the category shown to users in UI screens.
+ ///
+ public string DisplayName { get; set; }
+
+ ///
+ /// The order used to sort categories in UI screens. Categories will be shown in sorted descending order.
+ ///
+ public int Order { get; set; }
+
+ public Category(string id, string displayName, int order)
+ {
+ Id = id;
+ DisplayName = displayName;
+ Order = order;
+ }
+ }
+}
diff --git a/src/AWS.Deploy.Common/Recipes/IOptionSettingHandler.cs b/src/AWS.Deploy.Common/Recipes/IOptionSettingHandler.cs
index 2861c315c..aefcf65f6 100644
--- a/src/AWS.Deploy.Common/Recipes/IOptionSettingHandler.cs
+++ b/src/AWS.Deploy.Common/Recipes/IOptionSettingHandler.cs
@@ -13,7 +13,7 @@ public interface IOptionSettingHandler
/// Due to different validations that could be put in place, access to other services may be needed.
/// This method is meant to control access to those services and determine the value to be set.
///
- void SetOptionSettingValue(OptionSettingItem optionSettingItem, object value);
+ void SetOptionSettingValue(Recommendation recommendation, OptionSettingItem optionSettingItem, object value);
///
/// This method retrieves the related to a specific .
diff --git a/src/AWS.Deploy.Common/Recipes/OptionSettingItem.ValueOverride.cs b/src/AWS.Deploy.Common/Recipes/OptionSettingItem.ValueOverride.cs
index 009aa3cdf..d2115c5b4 100644
--- a/src/AWS.Deploy.Common/Recipes/OptionSettingItem.ValueOverride.cs
+++ b/src/AWS.Deploy.Common/Recipes/OptionSettingItem.ValueOverride.cs
@@ -92,11 +92,11 @@ public object GetValue(IDictionary replacementTokens, IDictionar
/// Thrown if one or more determine
/// is not valid.
///
- public void SetValue(IOptionSettingHandler optionSettingHandler, object valueOverride)
+ public void SetValue(IOptionSettingHandler optionSettingHandler, object valueOverride, IOptionSettingItemValidator[] validators, Recommendation recommendation)
{
var isValid = true;
var validationFailedMessage = string.Empty;
- foreach (var validator in this.BuildValidators())
+ foreach (var validator in validators)
{
var result = validator.Validate(valueOverride);
if (!result.IsValid)
@@ -148,7 +148,7 @@ public void SetValue(IOptionSettingHandler optionSettingHandler, object valueOve
{
if (deserialized.TryGetValue(childOptionSetting.Id, out var childValueOverride))
{
- optionSettingHandler.SetOptionSettingValue(childOptionSetting, childValueOverride);
+ optionSettingHandler.SetOptionSettingValue(recommendation, childOptionSetting, childValueOverride);
}
}
}
diff --git a/src/AWS.Deploy.Common/Recipes/OptionSettingItem.cs b/src/AWS.Deploy.Common/Recipes/OptionSettingItem.cs
index d3dba87af..9f7fcd13e 100644
--- a/src/AWS.Deploy.Common/Recipes/OptionSettingItem.cs
+++ b/src/AWS.Deploy.Common/Recipes/OptionSettingItem.cs
@@ -38,9 +38,11 @@ public interface IOptionSettingItem
///
/// Set the value of an while validating the provided input.
///
- ///
- ///
- void SetValue(IOptionSettingHandler optionSettingHandler, object value);
+ /// Handler use to set any child option settings
+ /// Value to set
+ /// Validators for this item
+ /// Selected recommendation
+ void SetValue(IOptionSettingHandler optionSettingHandler, object value, IOptionSettingItemValidator[] validators, Recommendation recommendation);
}
///
@@ -64,6 +66,11 @@ public partial class OptionSettingItem : IOptionSettingItem
///
public string Name { get; set; }
+ ///
+ /// The category for the setting. This value must match an id field in the list of categories.
+ ///
+ public string? Category { get; set; }
+
///
/// The description of what the setting is used for.
///
diff --git a/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs b/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs
index 4c4babd81..f09525993 100644
--- a/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs
+++ b/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs
@@ -93,6 +93,11 @@ public class RecipeDefinition
///
public List RecommendationRules { get; set; } = new ();
+ ///
+ /// The list of categories for the recipes.
+ ///
+ public List Categories { get; set; } = new ();
+
///
/// The settings that can be configured by the user before deploying.
///
diff --git a/src/AWS.Deploy.Common/Recipes/Validation/IRecipeValidator.cs b/src/AWS.Deploy.Common/Recipes/Validation/IRecipeValidator.cs
index c7676e73a..8eb7a3c31 100644
--- a/src/AWS.Deploy.Common/Recipes/Validation/IRecipeValidator.cs
+++ b/src/AWS.Deploy.Common/Recipes/Validation/IRecipeValidator.cs
@@ -10,6 +10,6 @@ namespace AWS.Deploy.Common.Recipes.Validation
///
public interface IRecipeValidator
{
- ValidationResult Validate(Recommendation recommendation, IDeployToolValidationContext deployValidationContext, IOptionSettingHandler optionSettingHandler);
+ ValidationResult Validate(Recommendation recommendation, IDeployToolValidationContext deployValidationContext);
}
}
diff --git a/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidatorList.cs b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidatorList.cs
index acb293c24..366f0ca72 100644
--- a/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidatorList.cs
+++ b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidatorList.cs
@@ -16,6 +16,18 @@ public enum OptionSettingItemValidatorList
///
/// Must be paired with
///
- Required
+ Required,
+ ///
+ /// Must be paired with
+ ///
+ DirectoryExists,
+ ///
+ /// Must be paired with
+ ///
+ DockerBuildArgs,
+ ///
+ /// Must be paried with
+ ///
+ DotnetPublishArgs
}
}
diff --git a/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/DirectoryExistsValidator.cs b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/DirectoryExistsValidator.cs
new file mode 100644
index 000000000..c74523004
--- /dev/null
+++ b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/DirectoryExistsValidator.cs
@@ -0,0 +1,36 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using AWS.Deploy.Common.IO;
+
+namespace AWS.Deploy.Common.Recipes.Validation
+{
+ ///
+ /// Validator that validates if a given directory exists
+ ///
+ public class DirectoryExistsValidator : IOptionSettingItemValidator
+ {
+ private readonly IDirectoryManager _directoryManager;
+
+ public DirectoryExistsValidator(IDirectoryManager directoryManager)
+ {
+ _directoryManager = directoryManager;
+ }
+
+ ///
+ /// Validates that the given directory exists.
+ /// This can be either an absolute path, or a path relative to the project directory.
+ ///
+ /// Path to validate
+ /// Valid if the directory exists, invalid otherwise
+ public ValidationResult Validate(object input)
+ {
+ var executionDirectory = (string)input;
+
+ if (!string.IsNullOrEmpty(executionDirectory) && !_directoryManager.Exists(executionDirectory))
+ return ValidationResult.Failed("The specified directory does not exist.");
+ else
+ return ValidationResult.Valid();
+ }
+ }
+}
diff --git a/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/DockerBuildArgsValidator.cs b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/DockerBuildArgsValidator.cs
new file mode 100644
index 000000000..371b26e61
--- /dev/null
+++ b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/DockerBuildArgsValidator.cs
@@ -0,0 +1,42 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+
+namespace AWS.Deploy.Common.Recipes.Validation
+{
+ ///
+ /// Validator for Docker build-time variables, passed via --build-arg
+ ///
+ public class DockerBuildArgsValidator : IOptionSettingItemValidator
+ {
+ ///
+ /// Validates that additional Docker build options don't collide
+ /// with those set by the deploy tool
+ ///
+ /// Proposed Docker build args
+ /// Valid if the options do not contain those set by the deploy tool, invalid otherwise
+ public ValidationResult Validate(object input)
+ {
+ var buildArgs = Convert.ToString(input);
+ var errorMessage = string.Empty;
+
+ if (string.IsNullOrEmpty(buildArgs))
+ {
+ return ValidationResult.Valid();
+ }
+
+ if (buildArgs.Contains("-t ") || buildArgs.Contains("--tag "))
+ errorMessage += "You must not include -t/--tag as an additional argument as it is used internally. " +
+ "You may set the Image Tag property in the advanced settings for some recipes." + Environment.NewLine;
+
+ if (buildArgs.Contains("-f ") || buildArgs.Contains("--file "))
+ errorMessage += "You must not include -f/--file as an additional argument as it is used internally." + Environment.NewLine;
+
+ if (!string.IsNullOrEmpty(errorMessage))
+ return ValidationResult.Failed("Invalid value for additional Docker build options." + Environment.NewLine + errorMessage.Trim());
+
+ return ValidationResult.Valid();
+ }
+ }
+}
diff --git a/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/DotnetPublishArgsValidator.cs b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/DotnetPublishArgsValidator.cs
new file mode 100644
index 000000000..9c1f898de
--- /dev/null
+++ b/src/AWS.Deploy.Common/Recipes/Validation/OptionSettingItemValidators/DotnetPublishArgsValidator.cs
@@ -0,0 +1,43 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+
+namespace AWS.Deploy.Common.Recipes.Validation
+{
+ ///
+ /// Validator for additional arguments to 'dotnet publish'
+ ///
+ public class DotnetPublishArgsValidator : IOptionSettingItemValidator
+ {
+ ///
+ /// Validates that additional 'dotnet publish' arguments do not collide with those used by the deploy tool
+ ///
+ /// Additional publish arguments
+ /// Valid if the arguments don't interfere with the deploy tool, invalid otherwise
+ public ValidationResult Validate(object input)
+ {
+ var publishArgs = Convert.ToString(input);
+ var errorMessage = string.Empty;
+
+ if (string.IsNullOrEmpty(publishArgs))
+ {
+ return ValidationResult.Valid();
+ }
+
+ if (publishArgs.Contains("-o ") || publishArgs.Contains("--output "))
+ errorMessage += "You must not include -o/--output as an additional argument as it is used internally." + Environment.NewLine;
+
+ if (publishArgs.Contains("-c ") || publishArgs.Contains("--configuration "))
+ errorMessage += "You must not include -c/--configuration as an additional argument. You can set the build configuration in the advanced settings." + Environment.NewLine;
+
+ if (publishArgs.Contains("--self-contained") || publishArgs.Contains("--no-self-contained"))
+ errorMessage += "You must not include --self-contained/--no-self-contained as an additional argument. You can set the self-contained property in the advanced settings." + Environment.NewLine;
+
+ if (!string.IsNullOrEmpty(errorMessage))
+ return ValidationResult.Failed("Invalid value for Dotnet Publish Arguments." + Environment.NewLine + errorMessage.Trim());
+
+ return ValidationResult.Valid();
+ }
+ }
+}
diff --git a/src/AWS.Deploy.Common/Recipes/Validation/RecipeValidators/FargateTaskCpuMemorySizeValidator.cs b/src/AWS.Deploy.Common/Recipes/Validation/RecipeValidators/FargateTaskCpuMemorySizeValidator.cs
index 9d0128a6f..097291d78 100644
--- a/src/AWS.Deploy.Common/Recipes/Validation/RecipeValidators/FargateTaskCpuMemorySizeValidator.cs
+++ b/src/AWS.Deploy.Common/Recipes/Validation/RecipeValidators/FargateTaskCpuMemorySizeValidator.cs
@@ -20,6 +20,13 @@ namespace AWS.Deploy.Common.Recipes.Validation
///
public class FargateTaskCpuMemorySizeValidator : IRecipeValidator
{
+ private readonly IOptionSettingHandler _optionSettingHandler;
+
+ public FargateTaskCpuMemorySizeValidator(IOptionSettingHandler optionSettingHandler)
+ {
+ _optionSettingHandler = optionSettingHandler;
+ }
+
private readonly Dictionary _cpuMemoryMap = new()
{
{ "256", new[] { "512", "1024", "2048" } },
@@ -51,15 +58,15 @@ private static IEnumerable BuildMemoryArray(int start, int end, int incr
public string? InvalidCpuValueValidationFailedMessage { get; set; }
///
- public ValidationResult Validate(Recommendation recommendation, IDeployToolValidationContext deployValidationContext, IOptionSettingHandler optionSettingHandler)
+ public ValidationResult Validate(Recommendation recommendation, IDeployToolValidationContext deployValidationContext)
{
string cpu;
string memory;
try
{
- cpu = optionSettingHandler.GetOptionSettingValue(recommendation, optionSettingHandler.GetOptionSetting(recommendation, CpuOptionSettingsId));
- memory = optionSettingHandler.GetOptionSettingValue(recommendation, optionSettingHandler.GetOptionSetting(recommendation, MemoryOptionSettingsId));
+ cpu = _optionSettingHandler.GetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, CpuOptionSettingsId));
+ memory = _optionSettingHandler.GetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, MemoryOptionSettingsId));
}
catch (OptionSettingItemDoesNotExistException)
{
diff --git a/src/AWS.Deploy.Common/Recipes/Validation/RecipeValidators/MinMaxConstraintValidator.cs b/src/AWS.Deploy.Common/Recipes/Validation/RecipeValidators/MinMaxConstraintValidator.cs
index a4fd6a80a..53659f9ad 100644
--- a/src/AWS.Deploy.Common/Recipes/Validation/RecipeValidators/MinMaxConstraintValidator.cs
+++ b/src/AWS.Deploy.Common/Recipes/Validation/RecipeValidators/MinMaxConstraintValidator.cs
@@ -10,19 +10,26 @@ namespace AWS.Deploy.Common.Recipes.Validation
///
public class MinMaxConstraintValidator : IRecipeValidator
{
+ private readonly IOptionSettingHandler _optionSettingHandler;
+
+ public MinMaxConstraintValidator(IOptionSettingHandler optionSettingHandler)
+ {
+ _optionSettingHandler = optionSettingHandler;
+ }
+
public string MinValueOptionSettingsId { get; set; } = string.Empty;
public string MaxValueOptionSettingsId { get; set; } = string.Empty;
public string ValidationFailedMessage { get; set; } = "The value specified for {{MinValueOptionSettingsId}} must be less than or equal to the value specified for {{MaxValueOptionSettingsId}}";
- public ValidationResult Validate(Recommendation recommendation, IDeployToolValidationContext deployValidationContext, IOptionSettingHandler optionSettingHandler)
+ public ValidationResult Validate(Recommendation recommendation, IDeployToolValidationContext deployValidationContext)
{
double minVal;
double maxValue;
try
{
- minVal = optionSettingHandler.GetOptionSettingValue(recommendation, optionSettingHandler.GetOptionSetting(recommendation, MinValueOptionSettingsId));
- maxValue = optionSettingHandler.GetOptionSettingValue(recommendation, optionSettingHandler.GetOptionSetting(recommendation, MaxValueOptionSettingsId));
+ minVal = _optionSettingHandler.GetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, MinValueOptionSettingsId));
+ maxValue = _optionSettingHandler.GetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, MaxValueOptionSettingsId));
}
catch (OptionSettingItemDoesNotExistException)
{
diff --git a/src/AWS.Deploy.Common/Recipes/Validation/ValidatorFactory.cs b/src/AWS.Deploy.Common/Recipes/Validation/ValidatorFactory.cs
index 2fc086fe3..8b645836e 100644
--- a/src/AWS.Deploy.Common/Recipes/Validation/ValidatorFactory.cs
+++ b/src/AWS.Deploy.Common/Recipes/Validation/ValidatorFactory.cs
@@ -4,20 +4,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Microsoft.Extensions.DependencyInjection;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
namespace AWS.Deploy.Common.Recipes.Validation
{
+ ///
+ /// Factory that builds the validators for a given option or recipe
+ ///
+ public interface IValidatorFactory
+ {
+ ///
+ /// Builds the validators that apply to the given option
+ ///
+ /// Option to validate
+ /// Array of validators for the given option
+ IOptionSettingItemValidator[] BuildValidators(OptionSettingItem optionSettingItem);
+
+ ///
+ /// Builds the validators that apply to the given recipe
+ ///
+ /// Recipe to validate
+ /// Array of validators for the given recipe
+ IRecipeValidator[] BuildValidators(RecipeDefinition recipeDefinition);
+ }
+
///
/// Builds and instances.
///
- public static class ValidatorFactory
+ public class ValidatorFactory : IValidatorFactory
{
+ private readonly IServiceProvider _serviceProvider;
+
+ public ValidatorFactory(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
private static readonly Dictionary _optionSettingItemValidatorTypeMapping = new()
{
{ OptionSettingItemValidatorList.Range, typeof(RangeValidator) },
{ OptionSettingItemValidatorList.Regex, typeof(RegexValidator) },
- { OptionSettingItemValidatorList.Required, typeof(RequiredValidator) }
+ { OptionSettingItemValidatorList.Required, typeof(RequiredValidator) },
+ { OptionSettingItemValidatorList.DirectoryExists, typeof(DirectoryExistsValidator) },
+ { OptionSettingItemValidatorList.DockerBuildArgs, typeof(DockerBuildArgsValidator) },
+ { OptionSettingItemValidatorList.DotnetPublishArgs, typeof(DotnetPublishArgsValidator) },
};
private static readonly Dictionary _recipeValidatorTypeMapping = new()
@@ -26,7 +59,7 @@ public static class ValidatorFactory
{ RecipeValidatorList.MinMaxConstraint, typeof(MinMaxConstraintValidator) }
};
- public static IOptionSettingItemValidator[] BuildValidators(this OptionSettingItem optionSettingItem)
+ public IOptionSettingItemValidator[] BuildValidators(OptionSettingItem optionSettingItem)
{
return optionSettingItem.Validators
.Select(v => Activate(v.ValidatorType, v.Configuration, _optionSettingItemValidatorTypeMapping))
@@ -34,7 +67,7 @@ public static IOptionSettingItemValidator[] BuildValidators(this OptionSettingIt
.ToArray();
}
- public static IRecipeValidator[] BuildValidators(this RecipeDefinition recipeDefinition)
+ public IRecipeValidator[] BuildValidators(RecipeDefinition recipeDefinition)
{
return recipeDefinition.Validators
.Select(v => Activate(v.ValidatorType, v.Configuration,_recipeValidatorTypeMapping))
@@ -42,11 +75,11 @@ public static IRecipeValidator[] BuildValidators(this RecipeDefinition recipeDef
.ToArray();
}
- private static object? Activate(TValidatorList validatorType, object? configuration, Dictionary typeMappings) where TValidatorList : struct
+ private object? Activate(TValidatorList validatorType, object? configuration, Dictionary typeMappings) where TValidatorList : struct
{
if (null == configuration)
{
- var validatorInstance = Activator.CreateInstance(typeMappings[validatorType]);
+ var validatorInstance = ActivatorUtilities.CreateInstance(_serviceProvider, typeMappings[validatorType]);
if (validatorInstance == null)
throw new InvalidValidatorTypeException(DeployToolErrorCode.UnableToCreateValidatorInstance, $"Could not create an instance of validator type {validatorType}");
return validatorInstance;
@@ -54,7 +87,14 @@ public static IRecipeValidator[] BuildValidators(this RecipeDefinition recipeDef
if (configuration is JObject jObject)
{
- var validatorInstance = jObject.ToObject(typeMappings[validatorType]);
+ var validatorInstance = JsonConvert.DeserializeObject(
+ JsonConvert.SerializeObject(jObject),
+ typeMappings[validatorType],
+ new JsonSerializerSettings
+ {
+ ContractResolver = new ServiceContractResolver(_serviceProvider)
+ });
+
if (validatorInstance == null)
throw new InvalidValidatorTypeException(DeployToolErrorCode.UnableToCreateValidatorInstance, $"Could not create an instance of validator type {validatorType}");
return validatorInstance;
@@ -63,4 +103,27 @@ public static IRecipeValidator[] BuildValidators(this RecipeDefinition recipeDef
return configuration;
}
}
+
+ ///
+ /// Custom contract resolver that can inject services from an IServiceProvider
+ /// into the constructor of the type that is being deserialized from Json
+ ///
+ public class ServiceContractResolver : DefaultContractResolver
+ {
+ private readonly IServiceProvider _serviceProvider;
+
+ public ServiceContractResolver(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ protected override JsonObjectContract CreateObjectContract(Type objectType)
+ {
+ var contract = base.CreateObjectContract(objectType);
+
+ contract.DefaultCreator = () => ActivatorUtilities.CreateInstance(_serviceProvider, objectType);
+
+ return contract;
+ }
+ }
}
diff --git a/src/AWS.Deploy.Common/Recommendation.cs b/src/AWS.Deploy.Common/Recommendation.cs
index 39c8fd623..76a3db6ac 100644
--- a/src/AWS.Deploy.Common/Recommendation.cs
+++ b/src/AWS.Deploy.Common/Recommendation.cs
@@ -53,8 +53,37 @@ public Recommendation(RecipeDefinition recipe, ProjectDefinition projectDefiniti
}
}
+ public List GetConfigurableOptionSettingCategories()
+ {
+ var categories = Recipe.Categories;
+
+ // If any top level settings has a category of General make sure the General category is added to the list.
+ if(!categories.Any(x => string.Equals(x.Id, Category.General.Id)) &&
+ Recipe.OptionSettings.Any(x => string.IsNullOrEmpty(x.Category) || string.Equals(x.Category, Category.General.Id)))
+ {
+ categories.Insert(0, Category.General);
+ }
+
+ // Add the build settings category if it is not already in the list of categories.
+ if(!categories.Any(x => string.Equals(x.Id, Category.DeploymentBundle.Id)))
+ {
+ categories.Add(Category.DeploymentBundle);
+ }
+
+ return categories;
+ }
+
public IEnumerable GetConfigurableOptionSettingItems()
{
+ // For any top level settings that don't have a category assigned to them assign the General category.
+ foreach(var setting in Recipe.OptionSettings)
+ {
+ if(string.IsNullOrEmpty(setting.Category))
+ {
+ setting.Category = Category.General.Id;
+ }
+ }
+
if (DeploymentBundleSettings == null)
return Recipe.OptionSettings;
diff --git a/src/AWS.Deploy.Orchestration/CdkProjectHandler.cs b/src/AWS.Deploy.Orchestration/CdkProjectHandler.cs
index ce979c2ab..6993eba8e 100644
--- a/src/AWS.Deploy.Orchestration/CdkProjectHandler.cs
+++ b/src/AWS.Deploy.Orchestration/CdkProjectHandler.cs
@@ -91,7 +91,7 @@ public async Task PerformCdkDiff(string cdkProjectPath, CloudApplication
needAwsCredentials: true);
if (cdkDiff.ExitCode != 0)
- throw new FailedToRunCDKDiffException(DeployToolErrorCode.FailedToRunCDKDiff, "The CDK Diff command encountered an error and failed.");
+ throw new FailedToRunCDKDiffException(DeployToolErrorCode.FailedToRunCDKDiff, "The CDK Diff command encountered an error and failed.", cdkDiff.ExitCode);
var templateFilePath = Path.Combine(cdkProjectPath, "cdk.out", $"{cloudApplication.Name}.template.json");
return await _fileManager.ReadAllTextAsync(templateFilePath);
@@ -117,7 +117,7 @@ public async Task DeployCdkProject(OrchestratorSession session, CloudApplication
streamOutputToInteractiveService: true);
if (cdkBootstrap.ExitCode != 0)
- throw new FailedToDeployCDKAppException(DeployToolErrorCode.FailedToRunCDKBootstrap, "The AWS CDK Bootstrap, which is the process of provisioning initial resources for the deployment environment, has failed. Please review the output above for additional details [and check out our troubleshooting guide for the most common failure reasons]. You can learn more about CDK bootstrapping at https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html.");
+ throw new FailedToDeployCDKAppException(DeployToolErrorCode.FailedToRunCDKBootstrap, "The AWS CDK Bootstrap, which is the process of provisioning initial resources for the deployment environment, has failed. Please review the output above for additional details [and check out our troubleshooting guide for the most common failure reasons]. You can learn more about CDK bootstrapping at https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html.", cdkBootstrap.ExitCode);
}
else
{
@@ -156,7 +156,7 @@ public async Task DeployCdkProject(OrchestratorSession session, CloudApplication
cdkDeploy = await cdkDeployTask;
// We recapture the deployment end date at this point after the deployment task completes.
deploymentEndDate = DateTime.UtcNow;
- await CheckCdkDeploymentFailure(stackId, deploymentStartDate, deploymentEndDate);
+ await CheckCdkDeploymentFailure(stackId, deploymentStartDate, deploymentEndDate, cdkDeploy);
}
else
{
@@ -169,7 +169,7 @@ public async Task DeployCdkProject(OrchestratorSession session, CloudApplication
var deploymentTotalTime = Math.Round((deploymentEndDate - deploymentStartDate).TotalSeconds, 2);
if (cdkDeploy.ExitCode != 0)
- throw new FailedToDeployCDKAppException(DeployToolErrorCode.FailedToDeployCdkApplication, $"We had an issue deploying your application to AWS. Check the deployment output for more details. Deployment took {deploymentTotalTime}s.");
+ throw new FailedToDeployCDKAppException(DeployToolErrorCode.FailedToDeployCdkApplication, $"We had an issue deploying your application to AWS. Check the deployment output for more details. Deployment took {deploymentTotalTime}s.", cdkDeploy.ExitCode);
}
public async Task DetermineIfCDKBootstrapShouldRun()
@@ -219,7 +219,7 @@ await WaitUntilHelper.WaitUntil(async () =>
return stack?.StackId ?? throw new ResourceQueryException(DeployToolErrorCode.FailedToRetrieveStackId, "We were unable to retrieve the CloudFormation stack identifier.");
}
- private async Task CheckCdkDeploymentFailure(string stackId, DateTime deploymentStartDate, DateTime deploymentEndDate)
+ private async Task CheckCdkDeploymentFailure(string stackId, DateTime deploymentStartDate, DateTime deploymentEndDate, TryRunResult cdkDeployResult)
{
try
{
@@ -239,13 +239,13 @@ private async Task CheckCdkDeploymentFailure(string stackId, DateTime deployment
if (failedEvents.Any())
{
var errors = string.Join(". ", failedEvents.Reverse().Select(x => x.ResourceStatusReason));
- throw new FailedToDeployCDKAppException(DeployToolErrorCode.FailedToDeployCdkApplication, errors);
+ throw new FailedToDeployCDKAppException(DeployToolErrorCode.FailedToDeployCdkApplication, errors, cdkDeployResult.ExitCode);
}
}
catch (ResourceQueryException exception) when (exception.InnerException != null && exception.InnerException.Message.Equals($"Stack [{stackId}] does not exist"))
{
var deploymentTotalTime = Math.Round((deploymentEndDate - deploymentStartDate).TotalSeconds, 2);
- throw new FailedToDeployCDKAppException(DeployToolErrorCode.FailedToCreateCdkStack, $"A CloudFormation stack was not created. Check the deployment output for more details. Deployment took {deploymentTotalTime}s.");
+ throw new FailedToDeployCDKAppException(DeployToolErrorCode.FailedToCreateCdkStack, $"A CloudFormation stack was not created. Check the deployment output for more details. Deployment took {deploymentTotalTime}s.", cdkDeployResult.ExitCode);
}
}
diff --git a/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs b/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs
index 7142c9914..827adaf44 100644
--- a/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs
+++ b/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs
@@ -68,7 +68,7 @@ public async Task BuildDockerImage(CloudApplication cloudApplication, Recommenda
errorMessage += $"{Environment.NewLine}Docker builds usually fail due to executing them from a working directory that is incompatible with the Dockerfile.";
errorMessage += $"{Environment.NewLine}You can try setting the 'Docker Execution Directory' in the option settings.";
- throw new DockerBuildFailedException(DeployToolErrorCode.DockerBuildFailed, errorMessage);
+ throw new DockerBuildFailedException(DeployToolErrorCode.DockerBuildFailed, errorMessage, result.ExitCode);
}
}
@@ -125,7 +125,7 @@ public async Task CreateDotnetPublishZip(Recommendation recommendation)
if (!string.IsNullOrEmpty(result.StandardError))
errorMessage = $"We were unable to package the application using 'dotnet publish' due to the following error:{Environment.NewLine}{result.StandardError}";
- throw new DotnetPublishFailedException(DeployToolErrorCode.DotnetPublishFailed, errorMessage);
+ throw new DotnetPublishFailedException(DeployToolErrorCode.DotnetPublishFailed, errorMessage, result.ExitCode);
}
var zipFilePath = $"{publishDirectoryInfo.FullName}.zip";
@@ -178,21 +178,16 @@ private string GetDockerFilePath(Recommendation recommendation)
private string GetDockerBuildArgs(Recommendation recommendation)
{
- var buildArgs = string.Empty;
- var argsDictionary = recommendation.DeploymentBundle.DockerBuildArgs
- .Split(',')
- .Where(x => x.Contains("="))
- .ToDictionary(
- k => k.Split('=')[0],
- v => v.Split('=')[1]
- );
-
- foreach (var arg in argsDictionary.Keys)
- {
- buildArgs += $" --build-arg {arg}={argsDictionary[arg]}";
- }
+ var buildArgs = recommendation.DeploymentBundle.DockerBuildArgs;
- return buildArgs;
+ if (string.IsNullOrEmpty(buildArgs))
+ return buildArgs;
+
+ // Ensure it starts with a space so it doesn't collide with the previous option
+ if (!char.IsWhiteSpace(buildArgs[0]))
+ return $" {buildArgs}";
+ else
+ return buildArgs;
}
private async Task InitiateDockerLogin()
@@ -200,7 +195,7 @@ private async Task InitiateDockerLogin()
var authorizationTokens = await _awsResourceQueryer.GetECRAuthorizationToken();
if (authorizationTokens.Count == 0)
- throw new DockerLoginFailedException(DeployToolErrorCode.DockerLoginFailed, "Failed to login to Docker");
+ throw new DockerLoginFailedException(DeployToolErrorCode.FailedToGetECRAuthorizationToken, "Failed to login to Docker", null);
var authTokenBytes = Convert.FromBase64String(authorizationTokens[0].AuthorizationToken);
var authToken = Encoding.UTF8.GetString(authTokenBytes);
@@ -214,7 +209,7 @@ private async Task InitiateDockerLogin()
var errorMessage = "Failed to login to Docker";
if (!string.IsNullOrEmpty(result.StandardError))
errorMessage = $"Failed to login to Docker due to the following reason:{Environment.NewLine}{result.StandardError}";
- throw new DockerLoginFailedException(DeployToolErrorCode.DockerLoginFailed, errorMessage);
+ throw new DockerLoginFailedException(DeployToolErrorCode.DockerLoginFailed, errorMessage, result.ExitCode);
}
}
@@ -242,7 +237,7 @@ private async Task TagDockerImage(string sourceTagName, string targetTagName)
var errorMessage = "Failed to tag Docker image";
if (!string.IsNullOrEmpty(result.StandardError))
errorMessage = $"Failed to tag Docker Image due to the following reason:{Environment.NewLine}{result.StandardError}";
- throw new DockerTagFailedException(DeployToolErrorCode.DockerTagFailed, errorMessage);
+ throw new DockerTagFailedException(DeployToolErrorCode.DockerTagFailed, errorMessage, result.ExitCode);
}
}
@@ -256,7 +251,7 @@ private async Task PushDockerImage(string targetTagName)
var errorMessage = "Failed to push Docker Image";
if (!string.IsNullOrEmpty(result.StandardError))
errorMessage = $"Failed to push Docker Image due to the following reason:{Environment.NewLine}{result.StandardError}";
- throw new DockerPushFailedException(DeployToolErrorCode.DockerPushFailed, errorMessage);
+ throw new DockerPushFailedException(DeployToolErrorCode.DockerPushFailed, errorMessage, result.ExitCode);
}
}
}
diff --git a/src/AWS.Deploy.Orchestration/Exceptions.cs b/src/AWS.Deploy.Orchestration/Exceptions.cs
index 41271992e..b800fe264 100644
--- a/src/AWS.Deploy.Orchestration/Exceptions.cs
+++ b/src/AWS.Deploy.Orchestration/Exceptions.cs
@@ -42,7 +42,7 @@ public PackageJsonFileException(DeployToolErrorCode errorCode, string message, E
///
public class DockerBuildFailedException : DeployToolException
{
- public DockerBuildFailedException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }
+ public DockerBuildFailedException(DeployToolErrorCode errorCode, string message, int processExitCode, Exception? innerException = null) : base(errorCode, message, innerException, processExitCode) { }
}
///
@@ -58,7 +58,7 @@ public NPMCommandFailedException(DeployToolErrorCode errorCode, string message,
///
public class DockerLoginFailedException : DeployToolException
{
- public DockerLoginFailedException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }
+ public DockerLoginFailedException(DeployToolErrorCode errorCode, string message, int? processExitCode, Exception? innerException = null) : base(errorCode, message, innerException, processExitCode) { }
}
///
@@ -66,7 +66,7 @@ public DockerLoginFailedException(DeployToolErrorCode errorCode, string message,
///
public class DockerTagFailedException : DeployToolException
{
- public DockerTagFailedException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }
+ public DockerTagFailedException(DeployToolErrorCode errorCode, string message, int processExitCode, Exception? innerException = null) : base(errorCode, message, innerException, processExitCode) { }
}
///
@@ -74,7 +74,7 @@ public DockerTagFailedException(DeployToolErrorCode errorCode, string message, E
///
public class DockerPushFailedException : DeployToolException
{
- public DockerPushFailedException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }
+ public DockerPushFailedException(DeployToolErrorCode errorCode, string message, int processExitCode, Exception? innerException = null) : base(errorCode, message, innerException, processExitCode) { }
}
///
@@ -90,7 +90,7 @@ public NoRecipeDefinitionsFoundException(DeployToolErrorCode errorCode, string m
///
public class DotnetPublishFailedException : DeployToolException
{
- public DotnetPublishFailedException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }
+ public DotnetPublishFailedException(DeployToolErrorCode errorCode, string message, int processExitCode, Exception? innerException = null) : base(errorCode, message, innerException, processExitCode) { }
}
///
@@ -98,7 +98,7 @@ public DotnetPublishFailedException(DeployToolErrorCode errorCode, string messag
///
public class FailedToCreateZipFileException : DeployToolException
{
- public FailedToCreateZipFileException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }
+ public FailedToCreateZipFileException(DeployToolErrorCode errorCode, string message, int? processExitCode, Exception? innerException = null) : base(errorCode, message, innerException, processExitCode) { }
}
///
@@ -138,7 +138,7 @@ public InvalidAWSDeployRecipesCDKCommonVersionException(DeployToolErrorCode erro
///
public class FailedToDeployCDKAppException : DeployToolException
{
- public FailedToDeployCDKAppException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }
+ public FailedToDeployCDKAppException(DeployToolErrorCode errorCode, string message, int processExitCode, Exception? innerException = null) : base(errorCode, message, innerException, processExitCode) { }
}
///
@@ -146,7 +146,7 @@ public FailedToDeployCDKAppException(DeployToolErrorCode errorCode, string messa
///
public class FailedToRunCDKDiffException : DeployToolException
{
- public FailedToRunCDKDiffException(DeployToolErrorCode errorCode, string message, Exception? innerException = null) : base(errorCode, message, innerException) { }
+ public FailedToRunCDKDiffException(DeployToolErrorCode errorCode, string message, int processExitCode, Exception? innerException = null) : base(errorCode, message, innerException, processExitCode) { }
}
///
diff --git a/src/AWS.Deploy.Orchestration/OptionSettingHandler.cs b/src/AWS.Deploy.Orchestration/OptionSettingHandler.cs
index 3f666bee9..ff7ab4491 100644
--- a/src/AWS.Deploy.Orchestration/OptionSettingHandler.cs
+++ b/src/AWS.Deploy.Orchestration/OptionSettingHandler.cs
@@ -1,16 +1,25 @@
// 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 AWS.Deploy.Common;
using AWS.Deploy.Common.Extensions;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
namespace AWS.Deploy.Orchestration
{
public class OptionSettingHandler : IOptionSettingHandler
{
+ private readonly IValidatorFactory _validatorFactory;
+
+ public OptionSettingHandler(IValidatorFactory validatorFactory)
+ {
+ _validatorFactory = validatorFactory;
+ }
+
///
/// Assigns a value to the OptionSettingItem.
///
@@ -18,9 +27,47 @@ public class OptionSettingHandler : IOptionSettingHandler
/// Thrown if one or more determine
/// is not valid.
///
- public void SetOptionSettingValue(OptionSettingItem optionSettingItem, object value)
+ public void SetOptionSettingValue(Recommendation recommendation, OptionSettingItem optionSettingItem, object value)
+ {
+ optionSettingItem.SetValue(this, value, _validatorFactory.BuildValidators(optionSettingItem), recommendation);
+
+ // If the optionSettingItem came from the selected recommendation's deployment bundle,
+ // set the corresponding property on recommendation.DeploymentBundle
+ SetDeploymentBundleProperty(recommendation, optionSettingItem, value);
+ }
+
+ ///
+ /// Sets the corresponding value in when the
+ /// corresponding was just set
+ ///
+ /// Selected recommendation
+ /// Option setting that was just set
+ /// Value that was just set, assumed to be valid
+ private void SetDeploymentBundleProperty(Recommendation recommendation, OptionSettingItem optionSettingItem, object value)
{
- optionSettingItem.SetValue(this, value);
+ switch (optionSettingItem.Id)
+ {
+ case "DockerExecutionDirectory":
+ recommendation.DeploymentBundle.DockerExecutionDirectory = value.ToString() ?? string.Empty;
+ break;
+ case "DockerBuildArgs":
+ recommendation.DeploymentBundle.DockerBuildArgs = value.ToString() ?? string.Empty;
+ break;
+ case "ECRRepositoryName":
+ recommendation.DeploymentBundle.ECRRepositoryName = value.ToString() ?? string.Empty;
+ break;
+ case "DotnetBuildConfiguration":
+ recommendation.DeploymentBundle.DotnetPublishBuildConfiguration = value.ToString() ?? string.Empty;
+ break;
+ case "DotnetPublishArgs":
+ recommendation.DeploymentBundle.DotnetPublishAdditionalBuildArguments = value.ToString() ?? string.Empty;
+ break;
+ case "SelfContainedBuild":
+ recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild = Convert.ToBoolean(value);
+ break;
+ default:
+ return;
+ }
}
///
diff --git a/src/AWS.Deploy.Orchestration/Orchestrator.cs b/src/AWS.Deploy.Orchestration/Orchestrator.cs
index b9008d556..d4735950d 100644
--- a/src/AWS.Deploy.Orchestration/Orchestrator.cs
+++ b/src/AWS.Deploy.Orchestration/Orchestrator.cs
@@ -157,7 +157,7 @@ public Recommendation ApplyRecommendationPreviousSettings(Recommendation recomme
{
if (previousSettings.TryGetValue(optionSetting.Id, out var value))
{
- _optionSettingHandler.SetOptionSettingValue(optionSetting, value);
+ _optionSettingHandler.SetOptionSettingValue(recommendationCopy, optionSetting, value);
}
}
@@ -210,7 +210,7 @@ public async Task CreateDeploymentBundle(CloudApplication cloudApplication, Reco
}
catch (DeployToolException ex)
{
- throw new FailedToCreateDeploymentBundleException(ex.ErrorCode, ex.Message, ex);
+ throw new FailedToCreateDeploymentBundleException(ex.ErrorCode, ex.Message, ex.ProcessExitCode, ex);
}
}
else if (recommendation.Recipe.DeploymentBundle == DeploymentBundleTypes.DotnetPublishZipFile)
@@ -223,7 +223,7 @@ public async Task CreateDeploymentBundle(CloudApplication cloudApplication, Reco
}
catch (DeployToolException ex)
{
- throw new FailedToCreateDeploymentBundleException(ex.ErrorCode, ex.Message, ex);
+ throw new FailedToCreateDeploymentBundleException(ex.ErrorCode, ex.Message, ex.ProcessExitCode, ex);
}
}
}
@@ -255,6 +255,8 @@ private async Task CreateContainerDeploymentBundle(CloudApplication cloudApplica
_dockerEngine.DetermineDockerExecutionDirectory(recommendation);
+ // Read this from the OptionSetting instead of recommendation.DeploymentBundle.
+ // When its value comes from a replacement token, it wouldn't have been set back to the DeploymentBundle
var respositoryName = _optionSettingHandler.GetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, "ECRRepositoryName"));
string imageTag;
diff --git a/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationEngine.cs b/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationEngine.cs
index d6257cde4..96f360723 100644
--- a/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationEngine.cs
+++ b/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationEngine.cs
@@ -95,7 +95,15 @@ public List GetDeploymentBundleSettings(DeploymentBundleTypes
if (definition == null)
throw new FailedToDeserializeException(DeployToolErrorCode.FailedToDeserializeDeploymentBundle, $"Failed to Deserialize Deployment Bundle [{deploymentBundleFile}]");
if (definition.Type.Equals(deploymentBundleTypes))
+ {
+ // Assign Build category to all of the deployment bundle settings.
+ foreach(var setting in definition.Parameters)
+ {
+ setting.Category = Category.DeploymentBundle.Id;
+ }
+
return definition.Parameters;
+ }
}
catch (Exception e)
{
diff --git a/src/AWS.Deploy.Orchestration/SystemCapabilities.cs b/src/AWS.Deploy.Orchestration/SystemCapabilities.cs
index 102fe1cf5..b64055725 100644
--- a/src/AWS.Deploy.Orchestration/SystemCapabilities.cs
+++ b/src/AWS.Deploy.Orchestration/SystemCapabilities.cs
@@ -32,32 +32,22 @@ public DockerInfo(
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 readonly string Name;
+ public readonly string Message;
+ public readonly string? InstallationUrl;
- public SystemCapability(string name, bool installed, bool available)
+ public SystemCapability(string name, string message, string? installationUrl = null)
{
Name = name;
- Installed = installed;
- Available = available;
+ Message = message;
+ InstallationUrl = installationUrl;
}
public string GetMessage()
{
- if (!string.IsNullOrEmpty(Message))
- {
- if (!string.IsNullOrEmpty(InstallationUrl))
- return $"{Message} {InstallationUrl}";
- else
- return Message;
- }
-
- var availabilityMessage = Available ? "and available" : "but not available";
- var installationMessage = Installed ? $"installed {availabilityMessage}" : "not installed";
- return $"The system capability '{Name}' is {installationMessage}";
+ return string.IsNullOrEmpty(InstallationUrl)
+ ? Message
+ : $"{Message}{Environment.NewLine}You can install the missing {Name} dependency from: {InstallationUrl}";
}
}
}
diff --git a/src/AWS.Deploy.Orchestration/SystemCapabilityEvaluator.cs b/src/AWS.Deploy.Orchestration/SystemCapabilityEvaluator.cs
index ae7096cda..c582dd7b8 100644
--- a/src/AWS.Deploy.Orchestration/SystemCapabilityEvaluator.cs
+++ b/src/AWS.Deploy.Orchestration/SystemCapabilityEvaluator.cs
@@ -18,6 +18,12 @@ public interface ISystemCapabilityEvaluator
public class SystemCapabilityEvaluator : ISystemCapabilityEvaluator
{
+ private const string NODEJS_DEPENDENCY_NAME = "Node.js";
+ private const string NODEJS_INSTALLATION_URL = "https://nodejs.org/en/download/";
+
+ private const string DOCKER_DEPENDENCY_NAME = "Docker";
+ private const string DOCKER_INSTALLATION_URL = "https://docs.docker.com/engine/install/";
+
private readonly ICommandLineWrapper _commandLineWrapper;
private static readonly Version MinimumNodeJSVersion = new Version(10,13,0);
@@ -86,21 +92,22 @@ public async Task> EvaluateSystemCapabilities(Recommendat
{
var capabilities = new List();
var systemCapabilities = await Evaluate();
+ string? message;
if (selectedRecommendation.Recipe.DeploymentType == DeploymentTypes.CdkProject)
{
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. AWS CDK requires {MinimumNodeJSVersion} of Node.js or later, and the latest LTS version is recommended."
- });
+ message = $"The selected deployment uses the AWS CDK, which requires Node.js. AWS CDK requires {MinimumNodeJSVersion} of Node.js or later, and the latest LTS version is recommended. " +
+ "Please restart your IDE/Shell after installing Node.js.";
+
+ capabilities.Add(new SystemCapability(NODEJS_DEPENDENCY_NAME, message, NODEJS_INSTALLATION_URL));
}
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 a version of Node.js higher than your current installation ({systemCapabilities.NodeJsVersion}). AWS CDK requires {MinimumNodeJSVersion} of Node.js or later, and the latest LTS version is recommended."
- });
+ message = $"The selected deployment uses the AWS CDK, which requires a version of Node.js higher than your current installation ({systemCapabilities.NodeJsVersion}). " +
+ $"AWS CDK requires {MinimumNodeJSVersion} of Node.js or later, and the latest LTS version is recommended. Please restart your IDE/Shell after installing Node.js";
+
+ capabilities.Add(new SystemCapability(NODEJS_DEPENDENCY_NAME, message, NODEJS_INSTALLATION_URL));
}
}
@@ -108,18 +115,13 @@ public async Task> EvaluateSystemCapabilities(Recommendat
{
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 your OS."
- });
+ message = "The selected deployment option requires Docker, which was not detected. Please install and start the appropriate version of Docker for your OS.";
+ capabilities.Add(new SystemCapability(DOCKER_DEPENDENCY_NAME, message, DOCKER_INSTALLATION_URL));
}
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."
- });
+ message = "The deployment tool requires Docker to be running in linux mode. Please switch Docker to linux mode to continue.";
+ capabilities.Add(new SystemCapability(DOCKER_DEPENDENCY_NAME, message));
}
}
diff --git a/src/AWS.Deploy.Orchestration/Utilities/CloudApplicationNameGenerator.cs b/src/AWS.Deploy.Orchestration/Utilities/CloudApplicationNameGenerator.cs
index e1073c084..692325abd 100644
--- a/src/AWS.Deploy.Orchestration/Utilities/CloudApplicationNameGenerator.cs
+++ b/src/AWS.Deploy.Orchestration/Utilities/CloudApplicationNameGenerator.cs
@@ -8,6 +8,7 @@
using System.Text.RegularExpressions;
using AWS.Deploy.Common;
using AWS.Deploy.Common.IO;
+using AWS.Deploy.Common.Recipes;
namespace AWS.Deploy.Orchestration.Utilities
{
@@ -20,23 +21,38 @@ public interface ICloudApplicationNameGenerator
///
/// Thrown if can't generate a valid name from .
///
- string GenerateValidName(ProjectDefinition target, List existingApplications);
+ string GenerateValidName(ProjectDefinition target, List existingApplications, DeploymentTypes? deploymentType = null);
///
- /// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-using-console-create-stack-parameters.html
+ /// Validates the cloud application name
///
- bool IsValidName(string name);
+ /// User provided cloud application name
+ /// The deployment type of the selected recommendation
+ /// List of existing deployed applications
+ ///
+ CloudApplicationNameValidationResult IsValidName(string name, IList existingApplications, DeploymentTypes? deploymentType = null);
+ }
- ///
- /// The message that should be displayed when an invalid name is passed
- ///
- string InvalidNameMessage(string name);
+ ///
+ /// Stores the result from validating the cloud application name.
+ ///
+ public class CloudApplicationNameValidationResult
+ {
+ public readonly bool IsValid;
+ public readonly string ErrorMessage;
+
+ public CloudApplicationNameValidationResult(bool isValid, string errorMessage)
+ {
+ IsValid = isValid;
+ ErrorMessage = errorMessage;
+ }
}
public class CloudApplicationNameGenerator : ICloudApplicationNameGenerator
{
private readonly IFileManager _fileManager;
private readonly IDirectoryManager _directoryManager;
+
///
/// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-using-console-create-stack-parameters.html
///
@@ -48,7 +64,7 @@ public CloudApplicationNameGenerator(IFileManager fileManager, IDirectoryManager
_directoryManager = directoryManager;
}
- public string GenerateValidName(ProjectDefinition target, List existingApplications)
+ public string GenerateValidName(ProjectDefinition target, List existingApplications, DeploymentTypes? deploymentType = null)
{
// generate recommendation
var recommendedPrefix = "deployment";
@@ -86,7 +102,9 @@ public string GenerateValidName(ProjectDefinition target, List
var suffix = !string.IsNullOrEmpty(suffixString) ? int.Parse(suffixString): 0;
while (suffix < int.MaxValue)
{
- if (existingApplications.All(x => x.Name != recommendation) && IsValidName(recommendation))
+ var validationResult = IsValidName(recommendation, existingApplications, deploymentType);
+
+ if (validationResult.IsValid)
return recommendation;
recommendation = $"{prefix}{++suffix}";
@@ -95,15 +113,52 @@ public string GenerateValidName(ProjectDefinition target, List
throw new ArgumentException("Failed to generate a valid and unique name.");
}
- ///
- /// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-using-console-create-stack-parameters.html
- /// >
- public bool IsValidName(string name) => _validatorRegex.IsMatch(name);
- public string InvalidNameMessage(string name)
+ public CloudApplicationNameValidationResult IsValidName(string name, IList existingApplications, DeploymentTypes? deploymentType = null)
+ {
+ var errorMessage = string.Empty;
+
+ if (!SatisfiesRegex(name))
+ {
+ errorMessage += $"The application name can contain only alphanumeric characters (case-sensitive) and hyphens. " +
+ $"It must start with an alphabetic character and can't be longer than 128 characters.{Environment.NewLine}";
+ }
+ if (MatchesExistingDeployment(name, existingApplications, deploymentType))
+ {
+ errorMessage += "A cloud application already exists with this name.";
+ }
+
+ if (string.IsNullOrEmpty(errorMessage))
+ return new CloudApplicationNameValidationResult(true, string.Empty);
+
+ return new CloudApplicationNameValidationResult(false, $"Invalid cloud application name: {name}{Environment.NewLine}{errorMessage}");
+ }
+
+ ///
+ /// This method first filters the existing applications by the current deploymentType if the deploymentType is not null
+ /// It will then check if the current name matches the filtered list of existing applications
+ ///
+ /// User provided cloud application name
+ /// The deployment type of the selected recommendation
+ /// List of existing deployed applications
+ /// true if found a match. false otherwise
+ private bool MatchesExistingDeployment(string name, IList existingApplications, DeploymentTypes? deploymentType = null)
+ {
+ if (!existingApplications.Any())
+ return false;
+
+ if (deploymentType != null)
+ existingApplications = existingApplications.Where(x => x.DeploymentType == deploymentType).ToList();
+
+ return existingApplications.Any(x => x.Name == name);
+ }
+
+ ///
+ /// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-using-console-create-stack-parameters.html
+ ///
+ private bool SatisfiesRegex(string name)
{
- return $"Invalid cloud application name {name}. The application name can contain only alphanumeric characters (case-sensitive) and hyphens. " +
- "It must start with an alphabetic character and can't be longer than 128 characters";
+ return _validatorRegex.IsMatch(name);
}
}
}
diff --git a/src/AWS.Deploy.Orchestration/Utilities/ZipFileManager.cs b/src/AWS.Deploy.Orchestration/Utilities/ZipFileManager.cs
index 20d2156f7..352af439b 100644
--- a/src/AWS.Deploy.Orchestration/Utilities/ZipFileManager.cs
+++ b/src/AWS.Deploy.Orchestration/Utilities/ZipFileManager.cs
@@ -49,7 +49,7 @@ private async Task BuildZipForLinux(string sourceDirectoryName, string destinati
var zipCLI = FindExecutableInPath("zip");
if (string.IsNullOrEmpty(zipCLI))
- throw new FailedToCreateZipFileException(DeployToolErrorCode.FailedToFindZipUtility, "Failed to find the \"zip\" utility program in path. This program is required to maintain Linux file permissions in the zip archive.");
+ throw new FailedToCreateZipFileException(DeployToolErrorCode.FailedToFindZipUtility, "Failed to find the \"zip\" utility program in path. This program is required to maintain Linux file permissions in the zip archive.", null);
var args = new StringBuilder($"\"{destinationArchiveFileName}\"");
@@ -68,7 +68,7 @@ private async Task BuildZipForLinux(string sourceDirectoryName, string destinati
errorMessage = $"We were unable to create a zip archive of the packaged application due to the following reason:{Environment.NewLine}{result.StandardError}";
errorMessage += $"{Environment.NewLine}Normally this indicates a problem running the \"zip\" utility. Make sure that application is installed and available in your PATH.";
- throw new FailedToCreateZipFileException(DeployToolErrorCode.ZipUtilityFailedToZip, errorMessage);
+ throw new FailedToCreateZipFileException(DeployToolErrorCode.ZipUtilityFailedToZip, errorMessage, result.ExitCode);
}
}
diff --git a/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/Container.deploymentbundle b/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/Container.deploymentbundle
index d1537328c..816b68d4c 100644
--- a/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/Container.deploymentbundle
+++ b/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/Container.deploymentbundle
@@ -4,12 +4,17 @@
{
"Id": "DockerBuildArgs",
"Name": "Docker Build Args",
- "Description": "The list of additional docker build args.",
+ "Description": "The list of additional options to append to the `docker build` command.",
"Type": "String",
"TypeHint": "DockerBuildArgs",
"DefaultValue": "",
"AdvancedSetting": true,
- "Updatable": true
+ "Updatable": true,
+ "Validators": [
+ {
+ "ValidatorType": "DockerBuildArgs"
+ }
+ ]
},
{
"Id": "DockerExecutionDirectory",
@@ -19,7 +24,12 @@
"TypeHint": "DockerExecutionDirectory",
"DefaultValue": "",
"AdvancedSetting": true,
- "Updatable": true
+ "Updatable": true,
+ "Validators": [
+ {
+ "ValidatorType": "DirectoryExists"
+ }
+ ]
},
{
"Id": "ECRRepositoryName",
diff --git a/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/DotnetPublishZipFile.deploymentbundle b/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/DotnetPublishZipFile.deploymentbundle
index 9405c6a0e..aae6026a1 100644
--- a/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/DotnetPublishZipFile.deploymentbundle
+++ b/src/AWS.Deploy.Recipes/DeploymentBundleDefinitions/DotnetPublishZipFile.deploymentbundle
@@ -19,7 +19,12 @@
"TypeHint": "DotnetPublishAdditionalBuildArguments",
"DefaultValue": "",
"AdvancedSetting": true,
- "Updatable": true
+ "Updatable": true,
+ "Validators": [
+ {
+ "ValidatorType": "DotnetPublishArgs"
+ }
+ ]
},
{
"Id": "SelfContainedBuild",
diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe
index e10f3a646..782380894 100644
--- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe
+++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe
@@ -53,11 +53,43 @@
}
],
-
+ "Categories": [
+ {
+ "Id": "General",
+ "DisplayName": "General",
+ "Order": 10
+ },
+ {
+ "Id": "Compute",
+ "DisplayName": "Compute",
+ "Order": 20
+ },
+ {
+ "Id": "Health",
+ "DisplayName": "Health",
+ "Order": 30
+ },
+ {
+ "Id": "Permissions",
+ "DisplayName": "Permissions",
+ "Order": 40
+ },
+ {
+ "Id": "VPC",
+ "DisplayName": "VPC",
+ "Order": 50
+ },
+ {
+ "Id": "EnvVariables",
+ "DisplayName": "Environment Variables",
+ "Order": 60
+ }
+ ],
"OptionSettings": [
{
"Id": "ServiceName",
"Name": "Service Name",
+ "Category": "General",
"Description": "The name of the AWS App Runner service.",
"Type": "String",
"TypeHint": "AppRunnerService",
@@ -67,8 +99,7 @@
"Validators": [
{
"ValidatorType": "Regex",
- "Configuration":
- {
+ "Configuration": {
"Regex": "^([A-Za-z0-9][A-Za-z0-9_-]{3,39})$",
"ValidationFailedMessage": "Invalid service name. The service name must be between 4 and 40 characters in length and can contain uppercase and lowercase letters, numbers, hyphen(-) and underscore(_). It must start with a letter or a number."
}
@@ -78,6 +109,7 @@
{
"Id": "Port",
"Name": "Port",
+ "Category": "General",
"Description": "The port the container is listening for requests on.",
"Type": "Int",
"DefaultValue": 80,
@@ -86,8 +118,7 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 0,
"Max": 51200
}
@@ -97,6 +128,7 @@
{
"Id": "StartCommand",
"Name": "Start Command",
+ "Category": "General",
"Description": "Override the start command from the image's default start command.",
"Type": "String",
"AdvancedSetting": true,
@@ -105,6 +137,7 @@
{
"Id": "ApplicationIAMRole",
"Name": "Application IAM Role",
+ "Category": "Permissions",
"Description": "The Identity and Access Management (IAM) role that provides AWS credentials to the application to access AWS services.",
"Type": "Object",
"TypeHint": "IAMRole",
@@ -137,8 +170,7 @@
"Validators": [
{
"ValidatorType": "Regex",
- "Configuration":
- {
+ "Configuration": {
"Regex": "arn:(aws|aws-us-gov|aws-cn|aws-iso|aws-iso-b):iam::[0-9]{12}:(role|role/service-role)/[\\w+=,.@\\-/]{1,1000}",
"AllowEmptyString": true,
"ValidationFailedMessage": "Invalid IAM Role ARN. The ARN should contain the arn:[PARTITION]:iam namespace, followed by the account ID, and then the resource path. For example - arn:aws:iam::123456789012:role/S3Access is a valid IAM Role ARN. For more information visit https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apprunner-service-authenticationconfiguration.html"
@@ -157,6 +189,7 @@
{
"Id": "ServiceAccessIAMRole",
"Name": "Service Access IAM Role",
+ "Category": "Permissions",
"Description": "The Identity and Access Management (IAM) role that provides gives the AWS App Runner service access to pull the container image from ECR.",
"Type": "Object",
"TypeHint": "IAMRole",
@@ -189,8 +222,7 @@
"Validators": [
{
"ValidatorType": "Regex",
- "Configuration":
- {
+ "Configuration": {
"Regex": "arn:(aws|aws-us-gov|aws-cn|aws-iso|aws-iso-b):iam::[0-9]{12}:(role|role/service-role)/[\\w+=,.@\\-/]{1,1000}",
"AllowEmptyString": true,
"ValidationFailedMessage": "Invalid IAM Role ARN. The ARN should contain the arn:[PARTITION]:iam namespace, followed by the account ID, and then the resource path. For example - arn:aws:iam::123456789012:role/S3Access is a valid IAM Role ARN. For more information visit https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apprunner-service-authenticationconfiguration.html"
@@ -209,6 +241,7 @@
{
"Id": "Cpu",
"Name": "CPU",
+ "Category": "Compute",
"Description": "The number of CPU units reserved for each instance of your App Runner service.",
"Type": "String",
"AdvancedSetting": false,
@@ -226,6 +259,7 @@
{
"Id": "Memory",
"Name": "Memory",
+ "Category": "Compute",
"Description": "The amount of memory reserved for each instance of your App Runner service.",
"Type": "String",
"AdvancedSetting": false,
@@ -245,6 +279,7 @@
{
"Id": "EncryptionKmsKey",
"Name": "Encryption KMS Key",
+ "Category": "Permissions",
"Description": "The ARN of the KMS key that's used for encryption of application logs.",
"Type": "String",
"AdvancedSetting": true,
@@ -252,8 +287,7 @@
"Validators": [
{
"ValidatorType": "Regex",
- "Configuration":
- {
+ "Configuration": {
"Regex": "arn:aws(-[\\w]+)*:kms:[a-z\\-]+-[0-9]{1}:[0-9]{12}:key/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
"AllowEmptyString": true,
"ValidationFailedMessage": "Invalid KMS key ARN. The ARN should contain the arn:[PARTITION]:kms namespace, followed by the region, account ID, and then the key-id. For example - arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab is a valid KMS key ARN. For more information visit https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apprunner-service-encryptionconfiguration.html"
@@ -264,6 +298,7 @@
{
"Id": "HealthCheckProtocol",
"Name": "Health Check Protocol",
+ "Category": "Health",
"Description": "The IP protocol that App Runner uses to perform health checks for your service.",
"Type": "String",
"DefaultValue": "TCP",
@@ -277,6 +312,7 @@
{
"Id": "HealthCheckPath",
"Name": "Health Check Path",
+ "Category": "Health",
"Description": "The URL that health check requests are sent to.",
"Type": "String",
"AdvancedSetting": true,
@@ -291,6 +327,7 @@
{
"Id": "HealthCheckInterval",
"Name": "Health Check Interval",
+ "Category": "Health",
"Description": "The time interval, in seconds, between health checks.",
"Type": "Int",
"DefaultValue": 5,
@@ -298,9 +335,8 @@
"Updatable": true,
"Validators": [
{
- "ValidatorType":"Range",
- "Configuration":
- {
+ "ValidatorType": "Range",
+ "Configuration": {
"Min": 1,
"Max": 20
}
@@ -310,6 +346,7 @@
{
"Id": "HealthCheckTimeout",
"Name": "Health Check Timeout",
+ "Category": "Health",
"Description": "The time, in seconds, to wait for a health check response before deciding it failed.",
"Type": "Int",
"DefaultValue": 2,
@@ -317,9 +354,8 @@
"Updatable": true,
"Validators": [
{
- "ValidatorType":"Range",
- "Configuration":
- {
+ "ValidatorType": "Range",
+ "Configuration": {
"Min": 1,
"Max": 20
}
@@ -329,6 +365,7 @@
{
"Id": "HealthCheckHealthyThreshold",
"Name": "Health Check Healthy Threshold",
+ "Category": "Health",
"Description": "The number of consecutive checks that must succeed before App Runner decides that the service is healthy.",
"Type": "Int",
"DefaultValue": 3,
@@ -336,9 +373,8 @@
"Updatable": true,
"Validators": [
{
- "ValidatorType":"Range",
- "Configuration":
- {
+ "ValidatorType": "Range",
+ "Configuration": {
"Min": 1,
"Max": 20
}
@@ -348,6 +384,7 @@
{
"Id": "HealthCheckUnhealthyThreshold",
"Name": "Health Check Unhealthy Threshold",
+ "Category": "Health",
"Description": "The number of consecutive checks that must fail before App Runner decides that the service is unhealthy.",
"Type": "Int",
"DefaultValue": 3,
@@ -355,9 +392,8 @@
"Updatable": true,
"Validators": [
{
- "ValidatorType":"Range",
- "Configuration":
- {
+ "ValidatorType": "Range",
+ "Configuration": {
"Min": 1,
"Max": 20
}
@@ -367,6 +403,7 @@
{
"Id": "VPCConnector",
"Name": "VPC Connector",
+ "Category": "VPC",
"Description": "App Runner requires this resource when you want to associate your App Runner service to a custom Amazon Virtual Private Cloud (Amazon VPC).",
"Type": "Object",
"TypeHint": "VPCConnector",
@@ -481,6 +518,7 @@
{
"Id": "AppRunnerEnvironmentVariables",
"Name": "Environment Variables",
+ "Category": "EnvVariables",
"Description": "Configure environment properties for your application.",
"Type": "KeyValue",
"AdvancedSetting": false,
diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe
index 88bb0c219..f2d7c8b6f 100644
--- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe
+++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe
@@ -68,18 +68,54 @@
},
{
"ValidatorType": "MinMaxConstraint",
- "Configuration":
- {
+ "Configuration": {
"MinValueOptionSettingsId": "AutoScaling.MinCapacity",
"MaxValueOptionSettingsId": "AutoScaling.MaxCapacity"
}
}
],
-
+ "Categories": [
+ {
+ "Id": "General",
+ "DisplayName": "General",
+ "Order": 10
+ },
+ {
+ "Id": "Compute",
+ "DisplayName": "Compute",
+ "Order": 20
+ },
+ {
+ "Id": "LoadBalancer",
+ "DisplayName": "Load Balancer",
+ "Order": 30
+ },
+ {
+ "Id": "AutoScaling",
+ "DisplayName": "Auto Scaling",
+ "Order": 40
+ },
+ {
+ "Id": "Permissions",
+ "DisplayName": "Permissions",
+ "Order": 50
+ },
+ {
+ "Id": "VPC",
+ "DisplayName": "VPC",
+ "Order": 60
+ },
+ {
+ "Id": "EnvVariables",
+ "DisplayName": "Environment Variables",
+ "Order": 70
+ }
+ ],
"OptionSettings": [
{
"Id": "ECSCluster",
"Name": "ECS Cluster",
+ "Category": "General",
"Description": "The ECS cluster used for the deployment.",
"Type": "Object",
"TypeHint": "ECSCluster",
@@ -151,6 +187,7 @@
"Id": "ECSServiceName",
"ParentSettingId": "ClusterName",
"Name": "ECS Service Name",
+ "Category": "General",
"Description": "The name of the ECS service running in the cluster.",
"Type": "String",
"TypeHint": "ECSService",
@@ -170,6 +207,7 @@
{
"Id": "DesiredCount",
"Name": "Desired Task Count",
+ "Category": "Compute",
"Description": "The desired number of ECS tasks to run for the service.",
"Type": "Int",
"DefaultValue": 3,
@@ -188,6 +226,7 @@
{
"Id": "ApplicationIAMRole",
"Name": "Application IAM Role",
+ "Category": "Permissions",
"Description": "The Identity and Access Management (IAM) role that provides AWS credentials to the application to access AWS services.",
"Type": "Object",
"TypeHint": "IAMRole",
@@ -239,6 +278,7 @@
{
"Id": "Vpc",
"Name": "Virtual Private Cloud (VPC)",
+ "Category": "VPC",
"Description": "A VPC enables you to launch the application into a virtual network that you've defined.",
"Type": "Object",
"TypeHint": "Vpc",
@@ -304,6 +344,7 @@
{
"Id": "AdditionalECSServiceSecurityGroups",
"Name": "ECS Service Security Groups",
+ "Category": "Permissions",
"Description": "A comma-delimited list of EC2 security groups to assign to the ECS service. This is commonly used to provide access to Amazon RDS databases running in their own security groups.",
"Type": "String",
"DefaultValue": "",
@@ -313,6 +354,7 @@
{
"Id": "TaskCpu",
"Name": "Task CPU",
+ "Category": "Compute",
"Description": "The number of CPU units used by the task. See the following for details on CPU values: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html#fargate-task-defs",
"Type": "Int",
"DefaultValue": 256,
@@ -330,6 +372,7 @@
{
"Id": "TaskMemory",
"Name": "Task Memory",
+ "Category": "Compute",
"Description": "The amount of memory (in MB) used by the task. See the following for details on memory values: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html#fargate-task-defs",
"Type": "Int",
"DefaultValue": 512,
@@ -405,6 +448,7 @@
{
"Id": "LoadBalancer",
"Name": "Elastic Load Balancer",
+ "Category": "LoadBalancer",
"Description": "Load Balancer the ECS Service will register tasks to.",
"Type": "Object",
"AdvancedSetting": true,
@@ -431,8 +475,7 @@
"Validators": [
{
"ValidatorType": "Regex",
- "Configuration":
- {
+ "Configuration": {
"Regex": "arn:[^:]+:elasticloadbalancing:[^:]*:[0-9]{12}:loadbalancer/.+",
"AllowEmptyString": true,
"ValidationFailedMessage": "Invalid load balancer ARN. The ARN should contain the arn:[PARTITION]:elasticloadbalancing namespace, followed by the Region of the load balancer, the AWS account ID of the load balancer owner, the loadbalancer namespace, and then the load balancer name. For example, arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188"
@@ -457,10 +500,9 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 0,
- "Max": 3600
+ "Max": 3600
}
}
]
@@ -485,10 +527,9 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 5,
- "Max": 300
+ "Max": 300
}
}
]
@@ -504,10 +545,9 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 2,
- "Max": 10
+ "Max": 10
}
}
]
@@ -523,10 +563,9 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 2,
- "Max": 10
+ "Max": 10
}
}
]
@@ -561,8 +600,7 @@
"Validators": [
{
"ValidatorType": "Regex",
- "Configuration":
- {
+ "Configuration": {
"Regex": "^/[a-zA-Z0-9*?&_\\-.$/~\"'@:+]{0,127}$",
"AllowEmptyString": true,
"ValidationFailedMessage": "Invalid listener condition path. The path is case-sensitive and can be up to 128. It starts with '/' and consists of alpha-numeric characters, wildcards (* and ?), & (using &), and the following special characters: '_-.$/~\"'@:+'"
@@ -591,8 +629,7 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 1,
"Max": 50000
}
@@ -614,6 +651,7 @@
{
"Id": "AutoScaling",
"Name": "AutoScaling",
+ "Category": "AutoScaling",
"Description": "The AutoScaling configuration for the ECS service.",
"Type": "Object",
"AdvancedSetting": true,
@@ -639,8 +677,7 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 1,
"Max": 5000
}
@@ -664,8 +701,7 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 1,
"Max": 5000
}
@@ -709,8 +745,7 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 1,
"Max": 100
}
@@ -738,8 +773,7 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 0,
"Max": 3600
}
@@ -767,8 +801,7 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 0,
"Max": 3600
}
@@ -796,8 +829,7 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 1,
"Max": 100
}
@@ -825,8 +857,7 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 0,
"Max": 3600
}
@@ -854,8 +885,7 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 0,
"Max": 3600
}
@@ -883,8 +913,7 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 1
}
}
@@ -911,8 +940,7 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 0,
"Max": 3600
}
@@ -940,8 +968,7 @@
"Validators": [
{
"ValidatorType": "Range",
- "Configuration":
- {
+ "Configuration": {
"Min": 0,
"Max": 3600
}
@@ -963,6 +990,7 @@
{
"Id": "ECSEnvironmentVariables",
"Name": "Environment Variables",
+ "Category": "EnvVariables",
"Description": "Configure environment properties for your application.",
"Type": "KeyValue",
"AdvancedSetting": false,
diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe
index bae15e6ca..36011703a 100644
--- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe
+++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe
@@ -73,11 +73,63 @@
}
}
],
-
+ "Categories": [
+ {
+ "Id": "General",
+ "DisplayName": "General",
+ "Order": 10
+ },
+ {
+ "Id": "Hosting",
+ "DisplayName": "Hosting",
+ "Order": 20
+ },
+ {
+ "Id": "Platform",
+ "DisplayName": "Platform",
+ "Order": 30
+ },
+ {
+ "Id": "RollingUpdates",
+ "DisplayName": "Rolling updates & deployments",
+ "Order": 40
+ },
+ {
+ "Id": "Health",
+ "DisplayName": "Health & Monitoring",
+ "Order": 50
+ },
+ {
+ "Id": "Compute",
+ "DisplayName": "Compute",
+ "Order": 60
+ },
+ {
+ "Id": "LoadBalancer",
+ "DisplayName": "LoadBalancer",
+ "Order": 70
+ },
+ {
+ "Id": "Permissions",
+ "DisplayName": "Permissions",
+ "Order": 80
+ },
+ {
+ "Id": "VPC",
+ "DisplayName": "VPC",
+ "Order": 90
+ },
+ {
+ "Id": "EnvVariables",
+ "DisplayName": "Environment Variables",
+ "Order": 100
+ }
+ ],
"OptionSettings": [
{
"Id": "BeanstalkApplication",
"Name": "Application Name",
+ "Category": "General",
"Description": "The Elastic Beanstalk application name.",
"Type": "Object",
"TypeHint": "BeanstalkApplication",
@@ -150,6 +202,7 @@
"Id": "BeanstalkEnvironment",
"ParentSettingId": "BeanstalkApplication.ApplicationName",
"Name": "Environment Name",
+ "Category": "General",
"Description": "The Elastic Beanstalk environment name.",
"Type": "Object",
"AdvancedSetting": false,
@@ -167,7 +220,7 @@
"Validators": [
{
"ValidatorType": "Regex",
- "Configuration" : {
+ "Configuration": {
"Regex": "^[a-zA-Z0-9][a-zA-Z0-9-]{2,38}[a-zA-Z0-9]$",
"ValidationFailedMessage": "Invalid Environment Name. The Environment Name Must be from 4 to 40 characters in length. The name can contain only letters, numbers, and hyphens. It can't start or end with a hyphen."
}
@@ -179,6 +232,7 @@
{
"Id": "InstanceType",
"Name": "EC2 Instance Type",
+ "Category": "Compute",
"Description": "The EC2 instance type of the EC2 instances created for the environment.",
"Type": "String",
"TypeHint": "InstanceType",
@@ -188,6 +242,7 @@
{
"Id": "EnvironmentType",
"Name": "Environment Type",
+ "Category": "General",
"Description": "The type of environment to create; for example, a single instance for development work or load balanced for production.",
"Type": "String",
"DefaultValue": "SingleInstance",
@@ -205,6 +260,7 @@
{
"Id": "LoadBalancerType",
"Name": "Load Balancer Type",
+ "Category": "LoadBalancer",
"Description": "The type of load balancer for your environment.",
"Type": "String",
"DefaultValue": "application",
@@ -230,6 +286,7 @@
{
"Id": "ApplicationIAMRole",
"Name": "Application IAM Role",
+ "Category": "Permissions",
"Description": "The Identity and Access Management (IAM) role that provides AWS credentials to the application to access AWS services.",
"Type": "Object",
"TypeHint": "IAMRole",
@@ -268,7 +325,7 @@
"Validators": [
{
"ValidatorType": "Regex",
- "Configuration" : {
+ "Configuration": {
"Regex": "arn:.+:iam::[0-9]{12}:.+",
"AllowEmptyString": true,
"ValidationFailedMessage": "Invalid IAM Role ARN. The ARN should contain the arn:[PARTITION]:iam namespace, followed by the account ID, and then the resource path. For example - arn:aws:iam::123456789012:role/S3Access is a valid IAM Role ARN. For more information visit https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-arns"
@@ -281,6 +338,7 @@
{
"Id": "ServiceIAMRole",
"Name": "Service IAM Role",
+ "Category": "Permissions",
"Description": "A service role is the IAM role that Elastic Beanstalk assumes when calling other services on your behalf.",
"Type": "Object",
"TypeHint": "IAMRole",
@@ -319,7 +377,7 @@
"Validators": [
{
"ValidatorType": "Regex",
- "Configuration" : {
+ "Configuration": {
"Regex": "arn:.+:iam::[0-9]{12}:.+",
"AllowEmptyString": true,
"ValidationFailedMessage": "Invalid IAM Role ARN. The ARN should contain the arn:[PARTITION]:iam namespace, followed by the account ID, and then the resource path. For example - arn:aws:iam::123456789012:role/S3Access is a valid IAM Role ARN. For more information visit https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-arns"
@@ -332,6 +390,7 @@
{
"Id": "EC2KeyPair",
"Name": "Key Pair",
+ "Category": "Permissions",
"Description": "The EC2 key pair used to SSH into EC2 instances for the Elastic Beanstalk environment.",
"Type": "String",
"TypeHint": "EC2KeyPair",
@@ -341,7 +400,7 @@
"Validators": [
{
"ValidatorType": "Regex",
- "Configuration" : {
+ "Configuration": {
"Regex": "^(?! ).+(? FailedConfigUpdates { get; set; }
+ }
+
+ /// A category defined in the recipe that settings will be mapped to via the Id property.
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.1.0 (Newtonsoft.Json v13.0.0.0)")]
+ public partial class CategorySummary
+ {
+ /// The id of the category that will be specified on top level settings.
+ [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Id { get; set; }
+
+ /// The display name of the category shown to users in UI screens.
+ [Newtonsoft.Json.JsonProperty("displayName", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string DisplayName { get; set; }
+
+ /// The order used to sort categories in UI screens. Categories will be shown in sorted descending order.
+ [Newtonsoft.Json.JsonProperty("order", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public int Order { get; set; }
+
+
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.1.0 (Newtonsoft.Json v13.0.0.0)")]
@@ -1817,6 +1836,9 @@ public partial class DeployToolExceptionSummary
[Newtonsoft.Json.JsonProperty("message", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Message { get; set; }
+ [Newtonsoft.Json.JsonProperty("processExitCode", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public int? ProcessExitCode { get; set; }
+
}
@@ -1879,6 +1901,9 @@ public partial class ExistingDeploymentSummary
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public DeploymentTypes DeploymentType { get; set; }
+ [Newtonsoft.Json.JsonProperty("settingsCategories", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public System.Collections.Generic.ICollection SettingsCategories { get; set; }
+
[Newtonsoft.Json.JsonProperty("existingDeploymentId", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string ExistingDeploymentId { get; set; }
@@ -1987,6 +2012,9 @@ public partial class OptionSettingItemSummary
[Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Name { get; set; }
+ [Newtonsoft.Json.JsonProperty("category", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Category { get; set; }
+
[Newtonsoft.Json.JsonProperty("description", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Description { get; set; }
@@ -2114,6 +2142,9 @@ public partial class RecommendationSummary
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public DeploymentTypes DeploymentType { get; set; }
+ [Newtonsoft.Json.JsonProperty("settingsCategories", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public System.Collections.Generic.ICollection SettingsCategories { get; set; }
+
}
@@ -2162,12 +2193,6 @@ public partial class SystemCapabilitySummary
[Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Name { get; set; }
- [Newtonsoft.Json.JsonProperty("installed", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
- public bool Installed { get; set; }
-
- [Newtonsoft.Json.JsonProperty("available", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
- public bool Available { get; set; }
-
[Newtonsoft.Json.JsonProperty("message", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Message { get; set; }
diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/IO/TestDirectoryManager.cs b/test/AWS.Deploy.CLI.Common.UnitTests/IO/TestDirectoryManager.cs
index 9fdccc997..ab885adb8 100644
--- a/test/AWS.Deploy.CLI.Common.UnitTests/IO/TestDirectoryManager.cs
+++ b/test/AWS.Deploy.CLI.Common.UnitTests/IO/TestDirectoryManager.cs
@@ -37,6 +37,9 @@ public bool Exists(string path)
return CreatedDirectories.Contains(path);
}
+ public bool Exists(string path, string relativeTo) =>
+ throw new NotImplementedException("If your test needs this method, you'll need to implement this.");
+
public string[] GetDirectories(string path, string searchPattern = null, SearchOption searchOption = SearchOption.TopDirectoryOnly) =>
throw new NotImplementedException("If your test needs this method, you'll need to implement this.");
diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/AppRunnerOptionSettingItemValidationTests.cs b/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/AppRunnerOptionSettingItemValidationTests.cs
index 1291ac33a..6d985fc7c 100644
--- a/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/AppRunnerOptionSettingItemValidationTests.cs
+++ b/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/AppRunnerOptionSettingItemValidationTests.cs
@@ -2,12 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
using System;
-using System.Collections.Generic;
-using System.Text;
using AWS.Deploy.Common;
using AWS.Deploy.Common.Recipes;
using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
+using Moq;
using Should;
using Xunit;
@@ -16,10 +15,12 @@ namespace AWS.Deploy.CLI.Common.UnitTests.Recipes.Validation
public class AppRunnerOptionSettingItemValidationTests
{
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public AppRunnerOptionSettingItemValidationTests()
{
- _optionSettingHandler = new OptionSettingHandler();
+ _serviceProvider = new Mock().Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
[Theory]
@@ -70,7 +71,7 @@ private void Validate(OptionSettingItem optionSettingItem, T value, bool isVa
ValidationFailedException exception = null;
try
{
- _optionSettingHandler.SetOptionSettingValue(optionSettingItem, value);
+ _optionSettingHandler.SetOptionSettingValue(null, optionSettingItem, value);
}
catch (ValidationFailedException e)
{
diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ECSFargateOptionSettingItemValidationTests.cs b/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ECSFargateOptionSettingItemValidationTests.cs
index 38d0d9585..bb98901dd 100644
--- a/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ECSFargateOptionSettingItemValidationTests.cs
+++ b/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ECSFargateOptionSettingItemValidationTests.cs
@@ -1,23 +1,33 @@
// 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.Common.UnitTests.IO;
using AWS.Deploy.Common;
+using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
+using Moq;
using Should;
using Xunit;
-using Xunit.Abstractions;
namespace AWS.Deploy.CLI.Common.UnitTests.Recipes.Validation
{
public class ECSFargateOptionSettingItemValidationTests
{
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly IDirectoryManager _directoryManager;
public ECSFargateOptionSettingItemValidationTests()
{
- _optionSettingHandler = new OptionSettingHandler();
+ _directoryManager = new TestDirectoryManager();
+ var mockServiceProvider = new Mock();
+ mockServiceProvider.Setup(x => x.GetService(typeof(IDirectoryManager))).Returns(_directoryManager);
+ _serviceProvider = mockServiceProvider.Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
[Theory]
@@ -145,6 +155,50 @@ public void ECRRepositoryNameValidationTest(string value, bool isValid)
Validate(optionSettingItem, value, isValid);
}
+ [Theory]
+ [InlineData("", true)]
+ [InlineData("--build-arg arg=val --no-cache", true)]
+ [InlineData("-t name:tag", false)]
+ [InlineData("--tag name:tag", false)]
+ [InlineData("-f file", false)]
+ [InlineData("--file file", false)]
+ public void DockerBuildArgsValidationTest(string value, bool isValid)
+ {
+ var optionSettingItem = new OptionSettingItem("id", "name", "description");
+ optionSettingItem.Validators.Add(new OptionSettingItemValidatorConfig
+ {
+ ValidatorType = OptionSettingItemValidatorList.DockerBuildArgs
+ });
+
+ Validate(optionSettingItem, value, isValid);
+ }
+
+ [Fact]
+ public void DockerExecutionDirectory_AbsoluteExists()
+ {
+ var optionSettingItem = new OptionSettingItem("id", "name", "description");
+ optionSettingItem.Validators.Add(new OptionSettingItemValidatorConfig
+ {
+ ValidatorType = OptionSettingItemValidatorList.DirectoryExists,
+ });
+
+ _directoryManager.CreateDirectory(Path.Join("C:", "project"));
+
+ Validate(optionSettingItem, Path.Join("C:", "project"), true);
+ }
+
+ [Fact]
+ public void DockerExecutionDirectory_AbsoluteDoesNotExist()
+ {
+ var optionSettingItem = new OptionSettingItem("id", "name", "description");
+ optionSettingItem.Validators.Add(new OptionSettingItemValidatorConfig
+ {
+ ValidatorType = OptionSettingItemValidatorList.DirectoryExists,
+ });
+
+ Validate(optionSettingItem, Path.Join("C:", "other_project"), false);
+ }
+
private OptionSettingItemValidatorConfig GetRegexValidatorConfig(string regex)
{
var regexValidatorConfig = new OptionSettingItemValidatorConfig
@@ -177,7 +231,7 @@ private void Validate(OptionSettingItem optionSettingItem, T value, bool isVa
ValidationFailedException exception = null;
try
{
- _optionSettingHandler.SetOptionSettingValue(optionSettingItem, value);
+ _optionSettingHandler.SetOptionSettingValue(null, optionSettingItem, value);
}
catch (ValidationFailedException e)
{
diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ElasticBeanStalkOptionSettingItemValidationTests.cs b/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ElasticBeanStalkOptionSettingItemValidationTests.cs
index b5d45cb9f..1113a90fe 100644
--- a/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ElasticBeanStalkOptionSettingItemValidationTests.cs
+++ b/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ElasticBeanStalkOptionSettingItemValidationTests.cs
@@ -1,10 +1,12 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
+using System;
using AWS.Deploy.Common;
using AWS.Deploy.Common.Recipes;
using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
+using Moq;
using Should;
using Xunit;
@@ -13,10 +15,12 @@ namespace AWS.Deploy.CLI.Common.UnitTests.Recipes.Validation
public class ElasticBeanStalkOptionSettingItemValidationTests
{
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public ElasticBeanStalkOptionSettingItemValidationTests()
{
- _optionSettingHandler = new OptionSettingHandler();
+ _serviceProvider = new Mock().Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
[Theory]
@@ -100,6 +104,26 @@ public void ElasticBeanstalkRollingUpdatesPauseTime(string value, bool isValid)
Validate(optionSettingItem, value, isValid);
}
+ [Theory]
+ [InlineData("", true)]
+ [InlineData("--no-restore --nologo --framework net5.0", true)]
+ [InlineData("-o dir", false)] // -o or --output is reserved by the deploy tool
+ [InlineData("--output dir", false)]
+ [InlineData("-c Release", false)] // -c or --configuration is controlled by DotnetPublishBuildConfiguration instead
+ [InlineData("--configuration Release", false)]
+ [InlineData("--self-contained true", false)] // --self-contained is controlled by SelfContainedBuild instead
+ [InlineData("--no-self-contained", false)]
+ public void DotnetPublishArgsValidationTest(string value, bool isValid)
+ {
+ var optionSettingItem = new OptionSettingItem("id", "name", "description");
+ optionSettingItem.Validators.Add(new OptionSettingItemValidatorConfig
+ {
+ ValidatorType = OptionSettingItemValidatorList.DotnetPublishArgs
+ });
+
+ Validate(optionSettingItem, value, isValid);
+ }
+
private OptionSettingItemValidatorConfig GetRegexValidatorConfig(string regex)
{
var regexValidatorConfig = new OptionSettingItemValidatorConfig
@@ -133,7 +157,7 @@ private void Validate(OptionSettingItem optionSettingItem, T value, bool isVa
try
{
- _optionSettingHandler.SetOptionSettingValue(optionSettingItem, value);
+ _optionSettingHandler.SetOptionSettingValue(null, optionSettingItem, value);
}
catch (ValidationFailedException e)
{
diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/OptionSettingsItemValidationTests.cs b/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/OptionSettingsItemValidationTests.cs
index c241fae29..54db114e8 100644
--- a/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/OptionSettingsItemValidationTests.cs
+++ b/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/OptionSettingsItemValidationTests.cs
@@ -1,10 +1,12 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r
// SPDX-License-Identifier: Apache-2.0
+using System;
using AWS.Deploy.Common;
using AWS.Deploy.Common.Recipes;
using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
+using Moq;
using Should;
using Xunit;
using Xunit.Abstractions;
@@ -22,11 +24,13 @@ public class OptionSettingsItemValidationTests
{
private readonly ITestOutputHelper _output;
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public OptionSettingsItemValidationTests(ITestOutputHelper output)
{
_output = output;
- _optionSettingHandler = new OptionSettingHandler();
+ _serviceProvider = new Mock().Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
[Theory]
@@ -60,7 +64,7 @@ public void InvalidInputInMultipleValidatorsThrowsException(string invalidValue)
// ACT
try
{
- _optionSettingHandler.SetOptionSettingValue(optionSettingItem, invalidValue);
+ _optionSettingHandler.SetOptionSettingValue(null, optionSettingItem, invalidValue);
}
catch (ValidationFailedException e)
{
@@ -96,7 +100,7 @@ public void InvalidInputInSingleValidatorThrowsException()
// ACT
try
{
- _optionSettingHandler.SetOptionSettingValue(optionSettingItem, invalidValue);
+ _optionSettingHandler.SetOptionSettingValue(null, optionSettingItem, invalidValue);
}
catch (ValidationFailedException e)
{
@@ -138,7 +142,7 @@ public void ValidInputDoesNotThrowException()
// ACT
try
{
- _optionSettingHandler.SetOptionSettingValue(optionSettingItem, validValue);
+ _optionSettingHandler.SetOptionSettingValue(null, optionSettingItem, validValue);
}
catch (ValidationFailedException e)
{
@@ -182,7 +186,7 @@ public void CustomValidatorMessagePropagatesToValidationException()
// ACT
try
{
- _optionSettingHandler.SetOptionSettingValue(optionSettingItem, invalidValue);
+ _optionSettingHandler.SetOptionSettingValue(null, optionSettingItem, invalidValue);
}
catch (ValidationFailedException e)
{
diff --git a/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ValidatorFactoryTests.cs b/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ValidatorFactoryTests.cs
index cdd74e91c..df8abbb47 100644
--- a/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ValidatorFactoryTests.cs
+++ b/test/AWS.Deploy.CLI.Common.UnitTests/Recipes/Validation/ValidatorFactoryTests.cs
@@ -5,8 +5,11 @@
using System.Collections.Generic;
using System.Linq;
using Amazon.Runtime.Internal;
+using AWS.Deploy.CLI.Common.UnitTests.IO;
+using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
using AWS.Deploy.Common.Recipes.Validation;
+using Moq;
using Newtonsoft.Json;
using Should;
using Xunit;
@@ -21,6 +24,21 @@ namespace AWS.Deploy.CLI.Common.UnitTests.Recipes.Validation
///
public class ValidatorFactoryTests
{
+ private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly IValidatorFactory _validatorFactory;
+
+ public ValidatorFactoryTests()
+ {
+ _optionSettingHandler = new Mock().Object;
+
+ var mockServiceProvider = new Mock();
+ mockServiceProvider.Setup(x => x.GetService(typeof(IOptionSettingHandler))).Returns(_optionSettingHandler);
+ mockServiceProvider.Setup(x => x.GetService(typeof(IDirectoryManager))).Returns(new TestDirectoryManager());
+ _serviceProvider = mockServiceProvider.Object;
+ _validatorFactory = new ValidatorFactory(_serviceProvider);
+ }
+
[Fact]
public void HasABindingForAllOptionSettingItemValidators()
{
@@ -42,7 +60,7 @@ public void HasABindingForAllOptionSettingItemValidators()
};
// ACT
- var validators = optionSettingItem.BuildValidators();
+ var validators = _validatorFactory.BuildValidators(optionSettingItem);
// ASSERT
validators.Length.ShouldEqual(allValidators.Length);
@@ -70,7 +88,7 @@ public void HasABindingForAllRecipeValidators()
};
// ACT
- var validators = recipeDefinition.BuildValidators();
+ var validators = _validatorFactory.BuildValidators(recipeDefinition);
// ASSERT
validators.Length.ShouldEqual(allValidators.Length);
@@ -106,7 +124,7 @@ public void CanBuildRehydratedOptionSettingsItem()
var deserialized = JsonConvert.DeserializeObject(json);
// ACT
- var validators = deserialized.BuildValidators();
+ var validators = _validatorFactory.BuildValidators(deserialized);
// ASSERT
validators.Length.ShouldEqual(1);
@@ -155,7 +173,7 @@ public void WhenValidatorTypeAndConfigurationHaveAMismatchThenValidatorTypeWins(
// ACT
try
{
- validators = deserialized.BuildValidators();
+ validators = _validatorFactory.BuildValidators(deserialized);
}
catch (Exception e)
{
diff --git a/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/RecommendationTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/RecommendationTests.cs
index 355ad6396..9272450b5 100644
--- a/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/RecommendationTests.cs
+++ b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/RecommendationTests.cs
@@ -26,6 +26,7 @@
using AWS.Deploy.Orchestration.LocalUserSettings;
using AWS.Deploy.Orchestration.Utilities;
using AWS.Deploy.Orchestration.ServiceHandlers;
+using AWS.Deploy.Common.Recipes.Validation;
namespace AWS.Deploy.CLI.IntegrationTests.SaveCdkDeploymentProject
{
@@ -251,7 +252,7 @@ private async Task GetOrchestrator(string targetApplicationProject
fileManager,
directoryManager,
new Mock().Object,
- new OptionSettingHandler());
+ new OptionSettingHandler(new Mock().Object));
}
private async Task GetCustomRecipeId(string recipeFilePath)
diff --git a/test/AWS.Deploy.CLI.IntegrationTests/ServerModeTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/ServerModeTests.cs
index b0c3dace4..b2a3016a8 100644
--- a/test/AWS.Deploy.CLI.IntegrationTests/ServerModeTests.cs
+++ b/test/AWS.Deploy.CLI.IntegrationTests/ServerModeTests.cs
@@ -157,49 +157,6 @@ public async Task GetRecommendationsWithEncryptedCredentials()
}
}
- [Fact]
- public async Task SetInvalidCloudFormationStackName()
- {
- var projectPath = _testAppManager.GetProjectPath(Path.Combine("testapps", "WebAppNoDockerFile", "WebAppNoDockerFile.csproj"));
- var portNumber = 4080;
- using var httpClient = ServerModeHttpClientFactory.ConstructHttpClient(ResolveCredentials);
-
- var serverCommand = new ServerModeCommand(_serviceProvider.GetRequiredService(), portNumber, null, true);
- var cancelSource = new CancellationTokenSource();
-
- var serverTask = serverCommand.ExecuteAsync(cancelSource.Token);
- try
- {
- var restClient = new RestAPIClient($"http://localhost:{portNumber}/", httpClient);
- await WaitTillServerModeReady(restClient);
-
- var startSessionOutput = await restClient.StartDeploymentSessionAsync(new StartDeploymentSessionInput
- {
- AwsRegion = _awsRegion,
- ProjectPath = projectPath
- });
-
- var sessionId = startSessionOutput.SessionId;
- Assert.NotNull(sessionId);
-
- var getRecommendationOutput = await restClient.GetRecommendationsAsync(sessionId);
- Assert.NotEmpty(getRecommendationOutput.Recommendations);
- var beanstalkRecommendation = getRecommendationOutput.Recommendations.FirstOrDefault();
-
- var exception = await Assert.ThrowsAsync>(() => restClient.SetDeploymentTargetAsync(sessionId, new SetDeploymentTargetInput
- {
- NewDeploymentRecipeId = beanstalkRecommendation.RecipeId,
- NewDeploymentName = "Hello$World"
- }));
-
- Assert.Contains("Invalid cloud application name Hello$World.", exception.Result.Detail);
- }
- finally
- {
- cancelSource.Cancel();
- }
- }
-
[Fact]
public async Task WebFargateDeploymentNoConfigChanges()
{
@@ -282,6 +239,11 @@ public async Task WebFargateDeploymentNoConfigChanges()
Assert.Equal(fargateRecommendation.Description, existingDeployment.Description);
Assert.Equal(fargateRecommendation.TargetService, existingDeployment.TargetService);
Assert.Equal(DeploymentTypes.CloudFormationStack, existingDeployment.DeploymentType);
+
+ Assert.NotEmpty(existingDeployment.SettingsCategories);
+ Assert.Contains(existingDeployment.SettingsCategories, x => string.Equals(x.Id, AWS.Deploy.Common.Recipes.Category.DeploymentBundle.Id));
+ Assert.DoesNotContain(existingDeployment.SettingsCategories, x => string.IsNullOrEmpty(x.Id));
+ Assert.DoesNotContain(existingDeployment.SettingsCategories, x => string.IsNullOrEmpty(x.DisplayName));
}
finally
{
@@ -400,8 +362,7 @@ public async Task InvalidStackName_ThrowsException(string invalidStackName)
Assert.Equal(400, exception.StatusCode);
- var errorMessage = $"Invalid cloud application name {invalidStackName}. The application name can contain only alphanumeric characters (case-sensitive) and hyphens. " +
- "It must start with an alphabetic character and can't be longer than 128 characters";
+ var errorMessage = $"Invalid cloud application name: {invalidStackName}";
Assert.Contains(errorMessage, exception.Result.Detail);
}
finally
@@ -410,6 +371,64 @@ public async Task InvalidStackName_ThrowsException(string invalidStackName)
}
}
+ [Fact]
+ public async Task CheckCategories()
+ {
+ var projectPath = _testAppManager.GetProjectPath(Path.Combine("testapps", "WebAppNoDockerFile", "WebAppNoDockerFile.csproj"));
+ var portNumber = 4200;
+ using var httpClient = ServerModeHttpClientFactory.ConstructHttpClient(ResolveCredentials);
+
+ var serverCommand = new ServerModeCommand(_serviceProvider.GetRequiredService(), portNumber, null, true);
+ var cancelSource = new CancellationTokenSource();
+
+ var serverTask = serverCommand.ExecuteAsync(cancelSource.Token);
+ try
+ {
+ var restClient = new RestAPIClient($"http://localhost:{portNumber}/", httpClient);
+ await WaitTillServerModeReady(restClient);
+
+ var startSessionOutput = await restClient.StartDeploymentSessionAsync(new StartDeploymentSessionInput
+ {
+ AwsRegion = _awsRegion,
+ ProjectPath = projectPath
+ });
+
+ var sessionId = startSessionOutput.SessionId;
+ Assert.NotNull(sessionId);
+
+ var getRecommendationOutput = await restClient.GetRecommendationsAsync(sessionId);
+
+ foreach(var recommendation in getRecommendationOutput.Recommendations)
+ {
+ Assert.NotEmpty(recommendation.SettingsCategories);
+ Assert.Contains(recommendation.SettingsCategories, x => string.Equals(x.Id, AWS.Deploy.Common.Recipes.Category.DeploymentBundle.Id));
+ Assert.DoesNotContain(recommendation.SettingsCategories, x => string.IsNullOrEmpty(x.Id));
+ Assert.DoesNotContain(recommendation.SettingsCategories, x => string.IsNullOrEmpty(x.DisplayName));
+ }
+
+ var selectedRecommendation = getRecommendationOutput.Recommendations.First();
+ await restClient.SetDeploymentTargetAsync(sessionId, new SetDeploymentTargetInput
+ {
+ NewDeploymentRecipeId = selectedRecommendation.RecipeId,
+ NewDeploymentName = "TestStack-" + DateTime.UtcNow.Ticks
+ });
+
+ var getConfigSettingsResponse = await restClient.GetConfigSettingsAsync(sessionId);
+
+ // Make sure all top level settings have a category
+ Assert.DoesNotContain(getConfigSettingsResponse.OptionSettings, x => string.IsNullOrEmpty(x.Category));
+
+ // Make sure build settings have been applied a category.
+ var buildSetting = getConfigSettingsResponse.OptionSettings.FirstOrDefault(x => string.Equals(x.Id, "DotnetBuildConfiguration"));
+ Assert.NotNull(buildSetting);
+ Assert.Equal(AWS.Deploy.Common.Recipes.Category.DeploymentBundle.Id, buildSetting.Category);
+ }
+ finally
+ {
+ cancelSource.Cancel();
+ }
+ }
+
internal static void RegisterSignalRMessageCallbacks(IDeploymentCommunicationClient signalRClient, StringBuilder logOutput)
{
signalRClient.ReceiveLogSectionStart = (message, description) =>
diff --git a/test/AWS.Deploy.CLI.UnitTests/ApplyPreviousSettingsTests.cs b/test/AWS.Deploy.CLI.UnitTests/ApplyPreviousSettingsTests.cs
index 950eabd99..a92f47ffb 100644
--- a/test/AWS.Deploy.CLI.UnitTests/ApplyPreviousSettingsTests.cs
+++ b/test/AWS.Deploy.CLI.UnitTests/ApplyPreviousSettingsTests.cs
@@ -18,6 +18,7 @@
using Xunit;
using Assert = Should.Core.Assertions.Assert;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
namespace AWS.Deploy.CLI.UnitTests
{
@@ -25,10 +26,13 @@ public class ApplyPreviousSettingsTests
{
private readonly IOptionSettingHandler _optionSettingHandler;
private readonly Orchestrator _orchestrator;
+ private readonly IServiceProvider _serviceProvider;
+
public ApplyPreviousSettingsTests()
{
- _optionSettingHandler = new OptionSettingHandler();
+ _serviceProvider = new Mock().Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
_orchestrator = new Orchestrator(null, null, null, null, null, null, null, null, null, null, null, null, null, null, _optionSettingHandler);
}
diff --git a/test/AWS.Deploy.CLI.UnitTests/CategoryTests.cs b/test/AWS.Deploy.CLI.UnitTests/CategoryTests.cs
new file mode 100644
index 000000000..95ad92135
--- /dev/null
+++ b/test/AWS.Deploy.CLI.UnitTests/CategoryTests.cs
@@ -0,0 +1,59 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using AWS.Deploy.Recipes;
+using Xunit;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Xunit.Abstractions;
+
+namespace AWS.Deploy.CLI.UnitTests
+{
+ public class CategoryTests
+ {
+ private readonly ITestOutputHelper _output;
+
+ public CategoryTests(ITestOutputHelper output)
+ {
+ this._output = output;
+ }
+
+ [Fact]
+ public void ValidateSettingCategories()
+ {
+ var recipes = Directory.GetFiles(RecipeLocator.FindRecipeDefinitionsPath(), "*.recipe", SearchOption.TopDirectoryOnly);
+
+ foreach(var recipe in recipes)
+ {
+ _output.WriteLine($"Validating recipe: {recipe}");
+ var root = JsonConvert.DeserializeObject(File.ReadAllText(recipe)) as JObject;
+
+ _output.WriteLine("\tCategories");
+ var categoryIds = new HashSet();
+ var categoryOrders = new HashSet();
+ foreach(JObject category in root["Categories"])
+ {
+ _output.WriteLine($"\t\t{category["Id"]}");
+ categoryIds.Add(category["Id"].ToString());
+
+ // Make sure all order ids are unique in recipe
+ var order = (int)category["Order"];
+ Assert.DoesNotContain(order, categoryOrders);
+ categoryOrders.Add(order);
+ }
+
+ _output.WriteLine("\tSettings");
+ foreach (JObject setting in root["OptionSettings"])
+ {
+ var settingCategoryId = setting["Category"]?.ToString();
+ _output.WriteLine($"\t\t{settingCategoryId}");
+ Assert.Contains(settingCategoryId, categoryIds);
+ }
+ }
+ }
+ }
+}
diff --git a/test/AWS.Deploy.CLI.UnitTests/ConsoleUtilitiesTests.cs b/test/AWS.Deploy.CLI.UnitTests/ConsoleUtilitiesTests.cs
index 34bc2920a..cff537b3a 100644
--- a/test/AWS.Deploy.CLI.UnitTests/ConsoleUtilitiesTests.cs
+++ b/test/AWS.Deploy.CLI.UnitTests/ConsoleUtilitiesTests.cs
@@ -1,22 +1,22 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-using System.Collections.Generic;
-using System.Net.Http;
-using Should;
-using AWS.Deploy.Common;
-using Xunit;
-using Amazon.Runtime;
-using AWS.Deploy.CLI.Utilities;
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Amazon.EC2.Model;
-using AWS.Deploy.Common.IO;
+using Amazon.Runtime;
using AWS.Deploy.CLI.Common.UnitTests.IO;
using AWS.Deploy.CLI.UnitTests.Utilities;
+using AWS.Deploy.CLI.Utilities;
+using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
+using Moq;
+using Should;
+using Xunit;
namespace AWS.Deploy.CLI.UnitTests
{
@@ -24,11 +24,13 @@ public class ConsoleUtilitiesTests
{
private readonly IDirectoryManager _directoryManager;
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public ConsoleUtilitiesTests()
{
+ _serviceProvider = new Mock().Object;
_directoryManager = new TestDirectoryManager();
- _optionSettingHandler = new OptionSettingHandler();
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
private readonly List _options = new List
diff --git a/test/AWS.Deploy.CLI.UnitTests/GetOptionSettingTests.cs b/test/AWS.Deploy.CLI.UnitTests/GetOptionSettingTests.cs
index 7196d8b68..8aadd5fab 100644
--- a/test/AWS.Deploy.CLI.UnitTests/GetOptionSettingTests.cs
+++ b/test/AWS.Deploy.CLI.UnitTests/GetOptionSettingTests.cs
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r
// SPDX-License-Identifier: Apache-2.0
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -9,6 +10,7 @@
using AWS.Deploy.Common;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
using AWS.Deploy.Orchestration.RecommendationEngine;
using AWS.Deploy.Recipes;
@@ -20,10 +22,12 @@ namespace AWS.Deploy.CLI.UnitTests
public class GetOptionSettingTests
{
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public GetOptionSettingTests()
{
- _optionSettingHandler = new OptionSettingHandler();
+ _serviceProvider = new Mock().Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
private async Task BuildRecommendationEngine(string testProjectName)
@@ -85,7 +89,7 @@ public async Task GetOptionSettingTests_GetDisplayableChildren(string optionSett
var beanstalkRecommendation = recommendations.First(r => r.Recipe.Id == Constants.ASPNET_CORE_BEANSTALK_RECIPE_ID);
var managedActionsEnabled = _optionSettingHandler.GetOptionSetting(beanstalkRecommendation, $"{optionSetting}.{childSetting}");
- _optionSettingHandler.SetOptionSettingValue(managedActionsEnabled, childValue);
+ _optionSettingHandler.SetOptionSettingValue(beanstalkRecommendation, managedActionsEnabled, childValue);
var elasticBeanstalkManagedPlatformUpdates = _optionSettingHandler.GetOptionSetting(beanstalkRecommendation, optionSetting);
var elasticBeanstalkManagedPlatformUpdatesValue = _optionSettingHandler.GetOptionSettingValue>(beanstalkRecommendation, elasticBeanstalkManagedPlatformUpdates);
@@ -105,8 +109,8 @@ public async Task GetOptionSettingTests_ListType_InvalidValue()
var subnets = _optionSettingHandler.GetOptionSetting(appRunnerRecommendation, "VPCConnector.Subnets");
var securityGroups = _optionSettingHandler.GetOptionSetting(appRunnerRecommendation, "VPCConnector.SecurityGroups");
- Assert.Throws(() => _optionSettingHandler.SetOptionSettingValue(subnets, new SortedSet(){ "subnet1" }));
- Assert.Throws(() => _optionSettingHandler.SetOptionSettingValue(securityGroups, new SortedSet(){ "securityGroup1" }));
+ Assert.Throws(() => _optionSettingHandler.SetOptionSettingValue(appRunnerRecommendation, subnets, new SortedSet(){ "subnet1" }));
+ Assert.Throws(() => _optionSettingHandler.SetOptionSettingValue(appRunnerRecommendation, securityGroups, new SortedSet(){ "securityGroup1" }));
}
[Fact]
@@ -121,7 +125,7 @@ public async Task GetOptionSettingTests_ListType()
var subnets = _optionSettingHandler.GetOptionSetting(appRunnerRecommendation, "VPCConnector.Subnets");
var emptySubnetsValue = _optionSettingHandler.GetOptionSettingValue(appRunnerRecommendation, subnets);
- _optionSettingHandler.SetOptionSettingValue(subnets, new SortedSet(){ "subnet-1234abcd" });
+ _optionSettingHandler.SetOptionSettingValue(appRunnerRecommendation, subnets, new SortedSet(){ "subnet-1234abcd" });
var subnetsValue = _optionSettingHandler.GetOptionSettingValue(appRunnerRecommendation, subnets);
var emptySubnetsString = Assert.IsType(emptySubnetsValue);
diff --git a/test/AWS.Deploy.CLI.UnitTests/RecommendationTests.cs b/test/AWS.Deploy.CLI.UnitTests/RecommendationTests.cs
index d23df368f..71038fc98 100644
--- a/test/AWS.Deploy.CLI.UnitTests/RecommendationTests.cs
+++ b/test/AWS.Deploy.CLI.UnitTests/RecommendationTests.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;
@@ -11,6 +12,7 @@
using AWS.Deploy.Common;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
using AWS.Deploy.Orchestration.RecommendationEngine;
using AWS.Deploy.Recipes;
@@ -25,11 +27,13 @@ public class RecommendationTests
private OrchestratorSession _session;
private readonly IDirectoryManager _directoryManager;
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public RecommendationTests()
{
_directoryManager = new TestDirectoryManager();
- _optionSettingHandler = new OptionSettingHandler();
+ _serviceProvider = new Mock().Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
private async Task BuildRecommendationEngine(string testProjectName)
@@ -181,11 +185,11 @@ public async Task ResetOptionSettingValue_Int()
var originalDefaultValue = _optionSettingHandler.GetOptionSettingDefaultValue(fargateRecommendation, desiredCountOptionSetting);
- _optionSettingHandler.SetOptionSettingValue(desiredCountOptionSetting, 2);
+ _optionSettingHandler.SetOptionSettingValue(fargateRecommendation, desiredCountOptionSetting, 2);
Assert.Equal(2, _optionSettingHandler.GetOptionSettingValue(fargateRecommendation, desiredCountOptionSetting));
- _optionSettingHandler.SetOptionSettingValue(desiredCountOptionSetting, consoleUtilities.AskUserForValue("Title", "2", true, originalDefaultValue.ToString()));
+ _optionSettingHandler.SetOptionSettingValue(fargateRecommendation, desiredCountOptionSetting, consoleUtilities.AskUserForValue("Title", "2", true, originalDefaultValue.ToString()));
Assert.Equal(originalDefaultValue, _optionSettingHandler.GetOptionSettingValue(fargateRecommendation, desiredCountOptionSetting));
}
@@ -210,11 +214,11 @@ public async Task ResetOptionSettingValue_String()
var originalDefaultValue = _optionSettingHandler.GetOptionSettingDefaultValue(fargateRecommendation, ecsServiceNameOptionSetting);
- _optionSettingHandler.SetOptionSettingValue(ecsServiceNameOptionSetting, "TestService");
+ _optionSettingHandler.SetOptionSettingValue(fargateRecommendation, ecsServiceNameOptionSetting, "TestService");
Assert.Equal("TestService", _optionSettingHandler.GetOptionSettingValue(fargateRecommendation, ecsServiceNameOptionSetting));
- _optionSettingHandler.SetOptionSettingValue(ecsServiceNameOptionSetting, consoleUtilities.AskUserForValue("Title", "TestService", true, originalDefaultValue));
+ _optionSettingHandler.SetOptionSettingValue(fargateRecommendation, ecsServiceNameOptionSetting, consoleUtilities.AskUserForValue("Title", "TestService", true, originalDefaultValue));
Assert.Equal(originalDefaultValue, _optionSettingHandler.GetOptionSettingValue(fargateRecommendation, ecsServiceNameOptionSetting));
}
@@ -258,7 +262,7 @@ public async Task ValueMappingSetWithValue()
var beanstalkRecommendation = recommendations.First(r => r.Recipe.Id == Constants.ASPNET_CORE_BEANSTALK_RECIPE_ID);
var environmentTypeOptionSetting = beanstalkRecommendation.Recipe.OptionSettings.First(optionSetting => optionSetting.Id.Equals("EnvironmentType"));
- _optionSettingHandler.SetOptionSettingValue(environmentTypeOptionSetting, "LoadBalanced");
+ _optionSettingHandler.SetOptionSettingValue(beanstalkRecommendation, environmentTypeOptionSetting, "LoadBalanced");
Assert.Equal("LoadBalanced", _optionSettingHandler.GetOptionSettingValue(beanstalkRecommendation, environmentTypeOptionSetting));
}
@@ -272,7 +276,7 @@ public async Task ObjectMappingSetWithValue()
var beanstalkRecommendation = recommendations.First(r => r.Recipe.Id == Constants.ASPNET_CORE_BEANSTALK_RECIPE_ID);
var applicationIAMRoleOptionSetting = beanstalkRecommendation.Recipe.OptionSettings.First(optionSetting => optionSetting.Id.Equals("ApplicationIAMRole"));
- _optionSettingHandler.SetOptionSettingValue(applicationIAMRoleOptionSetting, new IAMRoleTypeHintResponse {CreateNew = false,
+ _optionSettingHandler.SetOptionSettingValue(beanstalkRecommendation, applicationIAMRoleOptionSetting, new IAMRoleTypeHintResponse {CreateNew = false,
RoleArn = "arn:aws:iam::123456789012:group/Developers" });
var iamRoleTypeHintResponse = _optionSettingHandler.GetOptionSettingValue(beanstalkRecommendation, applicationIAMRoleOptionSetting);
@@ -387,7 +391,7 @@ public async Task IsDisplayable_OneDependency()
Assert.False(_optionSettingHandler.IsOptionSettingDisplayable(beanstalkRecommendation, loadBalancerTypeOptionSetting));
// Satisfy dependency
- _optionSettingHandler.SetOptionSettingValue(environmentTypeOptionSetting, "LoadBalanced");
+ _optionSettingHandler.SetOptionSettingValue(beanstalkRecommendation, environmentTypeOptionSetting, "LoadBalanced");
Assert.Equal("LoadBalanced", _optionSettingHandler.GetOptionSettingValue(beanstalkRecommendation, environmentTypeOptionSetting));
// Verify
@@ -410,11 +414,11 @@ public async Task IsDisplayable_ManyDependencies()
Assert.False(_optionSettingHandler.IsOptionSettingDisplayable(fargateRecommendation, vpcIdOptionSetting));
// Satisfy dependencies
- _optionSettingHandler.SetOptionSettingValue(isDefaultOptionSetting, false);
+ _optionSettingHandler.SetOptionSettingValue(fargateRecommendation, isDefaultOptionSetting, false);
Assert.False(_optionSettingHandler.GetOptionSettingValue(fargateRecommendation, isDefaultOptionSetting));
// Default value for Vpc.CreateNew already false, this is to show explicitly setting an override that satisfies Vpc Id option setting
- _optionSettingHandler.SetOptionSettingValue(createNewOptionSetting, false);
+ _optionSettingHandler.SetOptionSettingValue(fargateRecommendation, createNewOptionSetting, false);
Assert.False(_optionSettingHandler.GetOptionSettingValue(fargateRecommendation, createNewOptionSetting));
// Verify
@@ -437,7 +441,7 @@ public async Task IsDisplayable_NotEmptyOperation()
Assert.False(_optionSettingHandler.IsOptionSettingDisplayable(beanstalkRecommendation, subnetsSetting));
// Satisfy dependencies
- _optionSettingHandler.SetOptionSettingValue(vpcIdOptionSetting, "vpc-1234abcd");
+ _optionSettingHandler.SetOptionSettingValue(beanstalkRecommendation, vpcIdOptionSetting, "vpc-1234abcd");
Assert.True(_optionSettingHandler.IsOptionSettingDisplayable(beanstalkRecommendation, subnetsSetting));
}
@@ -459,11 +463,11 @@ public async Task IsDisplayable_NotEmptyOperation_ListType()
Assert.False(_optionSettingHandler.IsOptionSettingDisplayable(beanstalkRecommendation, securityGroupsSetting));
// Satisfy 1 dependency
- _optionSettingHandler.SetOptionSettingValue(vpcIdOptionSetting, "vpc-1234abcd");
+ _optionSettingHandler.SetOptionSettingValue(beanstalkRecommendation, vpcIdOptionSetting, "vpc-1234abcd");
Assert.False(_optionSettingHandler.IsOptionSettingDisplayable(beanstalkRecommendation, securityGroupsSetting));
// Satisfy 2 dependencies
- _optionSettingHandler.SetOptionSettingValue(subnetsSetting, new SortedSet { "subnet-1234abcd" });
+ _optionSettingHandler.SetOptionSettingValue(beanstalkRecommendation, subnetsSetting, new SortedSet { "subnet-1234abcd" });
Assert.True(_optionSettingHandler.IsOptionSettingDisplayable(beanstalkRecommendation, securityGroupsSetting));
}
diff --git a/test/AWS.Deploy.CLI.UnitTests/ServerModeTests.cs b/test/AWS.Deploy.CLI.UnitTests/ServerModeTests.cs
index f1fef868d..6a06a4cae 100644
--- a/test/AWS.Deploy.CLI.UnitTests/ServerModeTests.cs
+++ b/test/AWS.Deploy.CLI.UnitTests/ServerModeTests.cs
@@ -135,6 +135,7 @@ public void ExistingDeploymentSummary_ContainsCorrectDeploymentType(CloudApplica
"baseRecipeId",
"recipeId",
"recipeName",
+ new List(),
false,
"shortDescription",
"description",
@@ -156,6 +157,7 @@ public void RecommendationSummary_ContainsCorrectDeploymentType(Deploy.Common.Re
"baseRecipeId",
"recipeId",
"name",
+ new List(),
false,
"shortDescription",
"description",
diff --git a/test/AWS.Deploy.CLI.UnitTests/SetOptionSettingTests.cs b/test/AWS.Deploy.CLI.UnitTests/SetOptionSettingTests.cs
index 8886c6a07..2bc7ab0f2 100644
--- a/test/AWS.Deploy.CLI.UnitTests/SetOptionSettingTests.cs
+++ b/test/AWS.Deploy.CLI.UnitTests/SetOptionSettingTests.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;
@@ -9,6 +10,7 @@
using AWS.Deploy.Common;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
using AWS.Deploy.Orchestration.RecommendationEngine;
using AWS.Deploy.Recipes;
@@ -22,12 +24,14 @@ public class SetOptionSettingTests
{
private readonly List _recommendations;
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public SetOptionSettingTests()
{
var projectPath = SystemIOUtilities.ResolvePath("WebAppNoDockerFile");
+ var directoryManager = new DirectoryManager();
- var parser = new ProjectDefinitionParser(new FileManager(), new DirectoryManager());
+ var parser = new ProjectDefinitionParser(new FileManager(), directoryManager);
var awsCredentials = new Mock();
var session = new OrchestratorSession(
parser.Parse(projectPath).Result,
@@ -40,7 +44,12 @@ public SetOptionSettingTests()
var engine = new RecommendationEngine(new[] { RecipeLocator.FindRecipeDefinitionsPath() }, session);
_recommendations = engine.ComputeRecommendations().GetAwaiter().GetResult();
- _optionSettingHandler = new OptionSettingHandler();
+
+ var mockServiceProvider = new Mock();
+ mockServiceProvider.Setup(x => x.GetService(typeof(IDirectoryManager))).Returns(directoryManager);
+ _serviceProvider = mockServiceProvider.Object;
+
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
///
@@ -53,7 +62,7 @@ public void SetOptionSettingTests_AllowedValues()
var recommendation = _recommendations.First(r => r.Recipe.Id == Constants.ASPNET_CORE_BEANSTALK_RECIPE_ID);
var optionSetting = recommendation.Recipe.OptionSettings.First(x => x.Id.Equals("EnvironmentType"));
- _optionSettingHandler.SetOptionSettingValue(optionSetting, optionSetting.AllowedValues.First());
+ _optionSettingHandler.SetOptionSettingValue(recommendation, optionSetting, optionSetting.AllowedValues.First());
Assert.Equal(optionSetting.AllowedValues.First(), _optionSettingHandler.GetOptionSettingValue(recommendation, optionSetting));
}
@@ -70,7 +79,7 @@ public void SetOptionSettingTests_MappedValues()
var recommendation = _recommendations.First(r => r.Recipe.Id == Constants.ASPNET_CORE_BEANSTALK_RECIPE_ID);
var optionSetting = recommendation.Recipe.OptionSettings.First(x => x.Id.Equals("EnvironmentType"));
- Assert.Throws(() => _optionSettingHandler.SetOptionSettingValue(optionSetting, optionSetting.ValueMapping.Values.First()));
+ Assert.Throws(() => _optionSettingHandler.SetOptionSettingValue(recommendation, optionSetting, optionSetting.ValueMapping.Values.First()));
}
[Fact]
@@ -80,7 +89,7 @@ public void SetOptionSettingTests_KeyValueType()
var optionSetting = recommendation.Recipe.OptionSettings.First(x => x.Id.Equals("ElasticBeanstalkEnvironmentVariables"));
var values = new Dictionary() { { "key", "value" } };
- _optionSettingHandler.SetOptionSettingValue(optionSetting, values);
+ _optionSettingHandler.SetOptionSettingValue(recommendation, optionSetting, values);
Assert.Equal(values, _optionSettingHandler.GetOptionSettingValue>(recommendation, optionSetting));
}
@@ -93,7 +102,7 @@ public void SetOptionSettingTests_KeyValueType_String()
var optionSetting = recommendation.Recipe.OptionSettings.First(x => x.Id.Equals("ElasticBeanstalkEnvironmentVariables"));
var dictionary = new Dictionary() { { "key", "value" } };
var dictionaryString = JsonConvert.SerializeObject(dictionary);
- _optionSettingHandler.SetOptionSettingValue(optionSetting, dictionaryString);
+ _optionSettingHandler.SetOptionSettingValue(recommendation, optionSetting, dictionaryString);
Assert.Equal(dictionary, _optionSettingHandler.GetOptionSettingValue>(recommendation, optionSetting));
}
@@ -104,7 +113,46 @@ public void SetOptionSettingTests_KeyValueType_Error()
var recommendation = _recommendations.First(r => r.Recipe.Id == Constants.ASPNET_CORE_BEANSTALK_RECIPE_ID);
var optionSetting = recommendation.Recipe.OptionSettings.First(x => x.Id.Equals("ElasticBeanstalkEnvironmentVariables"));
- Assert.Throws(() => _optionSettingHandler.SetOptionSettingValue(optionSetting, "string"));
+ Assert.Throws(() => _optionSettingHandler.SetOptionSettingValue(recommendation, optionSetting, "string"));
+ }
+
+ ///
+ /// Verifies that calling SetOptionSettingValue for Docker-related settings
+ /// also sets the corresponding value in recommendation.DeploymentBundle
+ ///
+ [Fact]
+ public void DeploymentBundleWriteThrough_Docker()
+ {
+ var recommendation = _recommendations.First(r => r.Recipe.Id == Constants.ASPNET_CORE_APPRUNNER_ID);
+
+ var dockerExecutionDirectory = SystemIOUtilities.ResolvePath("WebAppNoDockerFile");
+ var dockerBuildArgs = "arg1=val1, arg2=val2";
+ _optionSettingHandler.SetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, "DockerExecutionDirectory"), dockerExecutionDirectory);
+ _optionSettingHandler.SetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, "DockerBuildArgs"), dockerBuildArgs);
+
+ Assert.Equal(dockerExecutionDirectory, recommendation.DeploymentBundle.DockerExecutionDirectory);
+ Assert.Equal(dockerBuildArgs, recommendation.DeploymentBundle.DockerBuildArgs);
+ }
+
+ ///
+ /// Verifies that calling SetOptionSettingValue for dotnet publish settings
+ /// also sets the corresponding value in recommendation.DeploymentBundle
+ ///
+ [Fact]
+ public void DeploymentBundleWriteThrough_Dotnet()
+ {
+ var recommendation = _recommendations.First(r => r.Recipe.Id == Constants.ASPNET_CORE_BEANSTALK_RECIPE_ID);
+
+ var dotnetBuildConfiguration = "Debug";
+ var dotnetPublishArgs = "--force --nologo";
+ var selfContainedBuild = true;
+ _optionSettingHandler.SetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, "DotnetBuildConfiguration"), dotnetBuildConfiguration);
+ _optionSettingHandler.SetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, "DotnetPublishArgs"), dotnetPublishArgs);
+ _optionSettingHandler.SetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, "SelfContainedBuild"), selfContainedBuild);
+
+ Assert.Equal(dotnetBuildConfiguration, recommendation.DeploymentBundle.DotnetPublishBuildConfiguration);
+ Assert.Equal(dotnetPublishArgs, recommendation.DeploymentBundle.DotnetPublishAdditionalBuildArguments);
+ Assert.True(recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild);
}
}
}
diff --git a/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/ExistingSecurityGroubsCommandTest.cs b/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/ExistingSecurityGroubsCommandTest.cs
index 4de5d590a..c08086b76 100644
--- a/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/ExistingSecurityGroubsCommandTest.cs
+++ b/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/ExistingSecurityGroubsCommandTest.cs
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r
// SPDX-License-Identifier: Apache-2.0
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -10,6 +11,7 @@
using AWS.Deploy.CLI.UnitTests.Utilities;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
using AWS.Deploy.Orchestration.Data;
using Moq;
@@ -22,12 +24,14 @@ public class ExistingSecurityGroubsCommandTest
private readonly Mock _mockAWSResourceQueryer;
private readonly IDirectoryManager _directoryManager;
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public ExistingSecurityGroubsCommandTest()
{
_mockAWSResourceQueryer = new Mock();
_directoryManager = new TestDirectoryManager();
- _optionSettingHandler = new OptionSettingHandler();
+ _serviceProvider = new Mock().Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
[Fact]
diff --git a/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/ExistingSubnetsCommandTest.cs b/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/ExistingSubnetsCommandTest.cs
index 7cea29afe..0a12fb394 100644
--- a/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/ExistingSubnetsCommandTest.cs
+++ b/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/ExistingSubnetsCommandTest.cs
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r
// SPDX-License-Identifier: Apache-2.0
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -10,6 +11,7 @@
using AWS.Deploy.CLI.UnitTests.Utilities;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
using AWS.Deploy.Orchestration.Data;
using Moq;
@@ -22,12 +24,14 @@ public class ExistingSubnetsCommandTest
private readonly Mock _mockAWSResourceQueryer;
private readonly IDirectoryManager _directoryManager;
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public ExistingSubnetsCommandTest()
{
_mockAWSResourceQueryer = new Mock();
_directoryManager = new TestDirectoryManager();
- _optionSettingHandler = new OptionSettingHandler();
+ _serviceProvider = new Mock().Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
[Fact]
diff --git a/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/ExistingVpcCommandTest.cs b/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/ExistingVpcCommandTest.cs
index f309f88b8..0366633b1 100644
--- a/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/ExistingVpcCommandTest.cs
+++ b/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/ExistingVpcCommandTest.cs
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r
// SPDX-License-Identifier: Apache-2.0
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -10,6 +11,7 @@
using AWS.Deploy.CLI.UnitTests.Utilities;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
using AWS.Deploy.Orchestration.Data;
using Moq;
@@ -22,12 +24,14 @@ public class ExistingVpcCommandTest
private readonly Mock _mockAWSResourceQueryer;
private readonly IDirectoryManager _directoryManager;
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public ExistingVpcCommandTest()
{
_mockAWSResourceQueryer = new Mock();
_directoryManager = new TestDirectoryManager();
- _optionSettingHandler = new OptionSettingHandler();
+ _serviceProvider = new Mock().Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
[Fact]
diff --git a/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/VPCConnectorCommandTest.cs b/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/VPCConnectorCommandTest.cs
index 374a97dd3..0499f9c7f 100644
--- a/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/VPCConnectorCommandTest.cs
+++ b/test/AWS.Deploy.CLI.UnitTests/TypeHintCommands/VPCConnectorCommandTest.cs
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r
// SPDX-License-Identifier: Apache-2.0
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -12,6 +13,7 @@
using AWS.Deploy.CLI.UnitTests.Utilities;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
using AWS.Deploy.Orchestration.Data;
using Moq;
@@ -25,13 +27,15 @@ public class VPCConnectorCommandTest
private readonly IDirectoryManager _directoryManager;
private readonly IToolInteractiveService _toolInteractiveService;
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public VPCConnectorCommandTest()
{
_mockAWSResourceQueryer = new Mock();
_directoryManager = new TestDirectoryManager();
_toolInteractiveService = new TestToolInteractiveServiceImpl();
- _optionSettingHandler = new OptionSettingHandler();
+ _serviceProvider = new Mock().Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
[Fact]
diff --git a/test/AWS.Deploy.CLI.UnitTests/TypeHintTests.cs b/test/AWS.Deploy.CLI.UnitTests/TypeHintTests.cs
index c32451d24..2b35cb96c 100644
--- a/test/AWS.Deploy.CLI.UnitTests/TypeHintTests.cs
+++ b/test/AWS.Deploy.CLI.UnitTests/TypeHintTests.cs
@@ -3,14 +3,8 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
-
-using Moq;
-using Xunit;
-
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.S3;
@@ -20,21 +14,26 @@
using Amazon.SQS;
using Amazon.SQS.Model;
using AWS.Deploy.CLI.Commands.TypeHints;
-using AWS.Deploy.Common;
-using AWS.Deploy.Orchestration.Data;
using AWS.Deploy.CLI.UnitTests.Utilities;
+using AWS.Deploy.Common;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration;
+using AWS.Deploy.Orchestration.Data;
+using Moq;
+using Xunit;
namespace AWS.Deploy.CLI.UnitTests
{
public class TypeHintTests
{
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public TypeHintTests()
{
- _optionSettingHandler = new OptionSettingHandler();
+ _serviceProvider = new Mock().Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
[Fact]
diff --git a/test/AWS.Deploy.Orchestration.UnitTests/CDK/CDKProjectHandlerTests.cs b/test/AWS.Deploy.Orchestration.UnitTests/CDK/CDKProjectHandlerTests.cs
index 73f1e3f24..af2726a03 100644
--- a/test/AWS.Deploy.Orchestration.UnitTests/CDK/CDKProjectHandlerTests.cs
+++ b/test/AWS.Deploy.Orchestration.UnitTests/CDK/CDKProjectHandlerTests.cs
@@ -12,16 +12,19 @@
using Amazon.CloudFormation.Model;
using System.Collections.Generic;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
namespace AWS.Deploy.Orchestration.UnitTests.CDK
{
public class CDKProjectHandlerTests
{
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public CDKProjectHandlerTests()
{
- _optionSettingHandler = new OptionSettingHandler();
+ _serviceProvider = new Mock().Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
[Fact]
diff --git a/test/AWS.Deploy.Orchestration.UnitTests/ElasticBeanstalkHandlerTests.cs b/test/AWS.Deploy.Orchestration.UnitTests/ElasticBeanstalkHandlerTests.cs
index 1f36625fc..aa6793395 100644
--- a/test/AWS.Deploy.Orchestration.UnitTests/ElasticBeanstalkHandlerTests.cs
+++ b/test/AWS.Deploy.Orchestration.UnitTests/ElasticBeanstalkHandlerTests.cs
@@ -11,6 +11,7 @@
using AWS.Deploy.Common;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
using AWS.Deploy.Orchestration.ServiceHandlers;
using AWS.Deploy.Orchestration.UnitTests.Utilities;
using AWS.Deploy.Recipes;
@@ -22,10 +23,12 @@ namespace AWS.Deploy.Orchestration.UnitTests
public class ElasticBeanstalkHandlerTests
{
private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly IServiceProvider _serviceProvider;
public ElasticBeanstalkHandlerTests()
{
- _optionSettingHandler = new OptionSettingHandler();
+ _serviceProvider = new Mock().Object;
+ _optionSettingHandler = new OptionSettingHandler(new ValidatorFactory(_serviceProvider));
}
[Fact]
@@ -87,10 +90,10 @@ public async Task GetAdditionSettingsTest_CustomValues()
new Mock().Object,
_optionSettingHandler);
- _optionSettingHandler.SetOptionSettingValue(_optionSettingHandler.GetOptionSetting(recommendation, Constants.ElasticBeanstalk.EnhancedHealthReportingOptionId), "basic");
- _optionSettingHandler.SetOptionSettingValue(_optionSettingHandler.GetOptionSetting(recommendation, Constants.ElasticBeanstalk.HealthCheckURLOptionId), "/url");
- _optionSettingHandler.SetOptionSettingValue(_optionSettingHandler.GetOptionSetting(recommendation, Constants.ElasticBeanstalk.ProxyOptionId), "none");
- _optionSettingHandler.SetOptionSettingValue(_optionSettingHandler.GetOptionSetting(recommendation, Constants.ElasticBeanstalk.XRayTracingOptionId), "true");
+ _optionSettingHandler.SetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, Constants.ElasticBeanstalk.EnhancedHealthReportingOptionId), "basic");
+ _optionSettingHandler.SetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, Constants.ElasticBeanstalk.HealthCheckURLOptionId), "/url");
+ _optionSettingHandler.SetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, Constants.ElasticBeanstalk.ProxyOptionId), "none");
+ _optionSettingHandler.SetOptionSettingValue(recommendation, _optionSettingHandler.GetOptionSetting(recommendation, Constants.ElasticBeanstalk.XRayTracingOptionId), "true");
// ACT
var optionSettings = elasticBeanstalkHandler.GetEnvironmentConfigurationSettings(recommendation);
diff --git a/test/AWS.Deploy.Orchestration.UnitTests/TestDirectoryManager.cs b/test/AWS.Deploy.Orchestration.UnitTests/TestDirectoryManager.cs
index 4e9019f1a..598398bd2 100644
--- a/test/AWS.Deploy.Orchestration.UnitTests/TestDirectoryManager.cs
+++ b/test/AWS.Deploy.Orchestration.UnitTests/TestDirectoryManager.cs
@@ -38,6 +38,9 @@ public bool Exists(string path)
return CreatedDirectories.Contains(path);
}
+ public bool Exists(string path, string relativeTo) =>
+ throw new NotImplementedException("If your test needs this method, you'll need to implement this.");
+
public string[] GetDirectories(string path, string searchPattern = null, SearchOption searchOption = SearchOption.TopDirectoryOnly) =>
throw new NotImplementedException("If your test needs this method, you'll need to implement this.");
diff --git a/test/AWS.Deploy.Orchestration.UnitTests/Utilities/CloudApplicationNameGeneratorTests.cs b/test/AWS.Deploy.Orchestration.UnitTests/Utilities/CloudApplicationNameGeneratorTests.cs
index d5f00e5e8..4ecb20e34 100644
--- a/test/AWS.Deploy.Orchestration.UnitTests/Utilities/CloudApplicationNameGeneratorTests.cs
+++ b/test/AWS.Deploy.Orchestration.UnitTests/Utilities/CloudApplicationNameGeneratorTests.cs
@@ -5,6 +5,7 @@
using System.Threading.Tasks;
using AWS.Deploy.Common;
using AWS.Deploy.Common.IO;
+using AWS.Deploy.Common.Recipes;
using AWS.Deploy.Orchestration.Utilities;
using Moq;
using Should;
@@ -38,11 +39,11 @@ public CloudApplicationNameGeneratorTests()
[InlineData("Valid")]
[InlineData("A21")]
[InlineData("Very-Long-With-Hyphens-And-Numbers")]
- public void ValidNamesAreValid(string name)
+ public void ValidNamesAreValid_WithRespectTo_Regex(string name)
{
- _cloudApplicationNameGenerator
- .IsValidName(name)
- .ShouldBeTrue();
+ var existingApplications = new List();
+ var validationResult = _cloudApplicationNameGenerator.IsValidName(name, existingApplications);
+ validationResult.IsValid.ShouldBeTrue();
}
[Theory]
@@ -50,11 +51,11 @@ public void ValidNamesAreValid(string name)
[InlineData("withSpecial!こんにちは世界Characters")]
[InlineData("With.Periods")]
[InlineData("With Spaces")]
- public void InvalidNamesAreInvalid(string name)
+ public void InvalidNamesAreInvalid_WithRespectTo_Regex(string name)
{
- _cloudApplicationNameGenerator
- .IsValidName(name)
- .ShouldBeFalse();
+ var existingApplications = new List();
+ var validationResult = _cloudApplicationNameGenerator.IsValidName(name, existingApplications);
+ validationResult.IsValid.ShouldBeFalse();
}
[Theory]
@@ -77,10 +78,10 @@ public async Task SuggestsValidName(string projectFile)
var recommendation = _cloudApplicationNameGenerator.GenerateValidName(projectDefinition, existingApplication);
// ACT
- var recommendationIsValid = _cloudApplicationNameGenerator.IsValidName(recommendation);
+ var validationResult = _cloudApplicationNameGenerator.IsValidName(recommendation, existingApplication);
// ASSERT
- recommendationIsValid.ShouldBeTrue();
+ validationResult.IsValid.ShouldBeTrue();
}
[Fact]
@@ -173,5 +174,51 @@ public async Task SuggestsValidNameAndRespectsExistingApplications_MultipleProje
// ASSERT
recommendation.ShouldEqual(expectedRecommendation);
}
+
+ [Theory]
+ [InlineData("application1", DeploymentTypes.CdkProject)]
+ [InlineData("application2", DeploymentTypes.CdkProject)]
+ [InlineData("application3", DeploymentTypes.BeanstalkEnvironment)]
+ public void InvalidNamesAreInvalid_WithRespectTo_ExistingApplications(string name, DeploymentTypes deploymentType)
+ {
+ // ARRANGE
+ var existingApplications = new List()
+ {
+ new CloudApplication("application1", "id1", CloudApplicationResourceType.CloudFormationStack, "recipe1"),
+ new CloudApplication("application2", "id2", CloudApplicationResourceType.CloudFormationStack, "recipe2"),
+ new CloudApplication("application3", "id3", CloudApplicationResourceType.BeanstalkEnvironment, "recipe3"),
+ new CloudApplication("application4", "id4", CloudApplicationResourceType.CloudFormationStack, "recipe1"),
+ };
+
+ // ACT
+ var validationResult = _cloudApplicationNameGenerator.IsValidName(name, existingApplications, deploymentType);
+
+ // ASSERT
+ validationResult.IsValid.ShouldBeFalse();
+ }
+
+ [Theory]
+ [InlineData("application", DeploymentTypes.CdkProject)]
+ [InlineData("application6", DeploymentTypes.CdkProject)]
+ [InlineData("application1", DeploymentTypes.BeanstalkEnvironment)]
+ [InlineData("application3", DeploymentTypes.CdkProject)]
+ public void ValidNamesAreValid_WithRespectTo_ExistingApplications(string name, DeploymentTypes deploymentType)
+ {
+ // ARRANGE
+ var existingApplications = new List()
+ {
+ new CloudApplication("application1", "id1", CloudApplicationResourceType.CloudFormationStack, "recipe1"),
+ new CloudApplication("application2", "id2", CloudApplicationResourceType.CloudFormationStack, "recipe2"),
+ new CloudApplication("application3", "id3", CloudApplicationResourceType.BeanstalkEnvironment, "recipe3"),
+ new CloudApplication("application4", "id4", CloudApplicationResourceType.CloudFormationStack, "recipe1"),
+ new CloudApplication("application5", "id4", CloudApplicationResourceType.BeanstalkEnvironment, "recipe3"),
+ };
+
+ // ACT
+ var validationResult = _cloudApplicationNameGenerator.IsValidName(name, existingApplications, deploymentType);
+
+ // ASSERT
+ validationResult.IsValid.ShouldBeTrue();
+ }
}
}
diff --git a/version.json b/version.json
index c64fac87c..4cf1ffd91 100644
--- a/version.json
+++ b/version.json
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
- "version": "0.44",
+ "version": "0.45",
"publicReleaseRefSpec": [
".*"
],