Skip to content

Commit

Permalink
Merge pull request #567 from aws/dev
Browse files Browse the repository at this point in the history
chore: release 0.45
  • Loading branch information
96malhar authored May 23, 2022
2 parents 53f7dea + 79def86 commit aacb712
Show file tree
Hide file tree
Showing 80 changed files with 1,667 additions and 523 deletions.
22 changes: 14 additions & 8 deletions docs/OptionSettingsItems-input-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
/// <summary>
/// One possibile solution if we need to create a Validator that needs
/// dependencies.
/// </summary>
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
}
}
```

Expand Down
9 changes: 7 additions & 2 deletions src/AWS.Deploy.CLI/Commands/CommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -101,7 +103,8 @@ public CommandFactory(
ILocalUserSettingsEngine localUserSettingsEngine,
ICDKVersionDetector cdkVersionDetector,
IAWSServiceHandler awsServiceHandler,
IOptionSettingHandler optionSettingHandler)
IOptionSettingHandler optionSettingHandler,
IValidatorFactory validatorFactory)
{
_serviceProvider = serviceProvider;
_toolInteractiveService = toolInteractiveService;
Expand Down Expand Up @@ -129,6 +132,7 @@ public CommandFactory(
_cdkVersionDetector = cdkVersionDetector;
_awsServiceHandler = awsServiceHandler;
_optionSettingHandler = optionSettingHandler;
_validatorFactory = validatorFactory;
}

public Command BuildRootCommand()
Expand Down Expand Up @@ -230,7 +234,8 @@ private Command BuildDeployCommand()
_directoryManager,
_fileManager,
_awsServiceHandler,
_optionSettingHandler);
_optionSettingHandler,
_validatorFactory);

var deploymentProjectPath = input.DeploymentProject ?? string.Empty;
if (!string.IsNullOrEmpty(deploymentProjectPath))
Expand Down
122 changes: 32 additions & 90 deletions src/AWS.Deploy.CLI/Commands/DeployCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -76,7 +77,8 @@ public DeployCommand(
IDirectoryManager directoryManager,
IFileManager fileManager,
IAWSServiceHandler awsServiceHandler,
IOptionSettingHandler optionSettingHandler)
IOptionSettingHandler optionSettingHandler,
IValidatorFactory validatorFactory)
{
_serviceProvider = serviceProvider;
_toolInteractiveService = toolInteractiveService;
Expand All @@ -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)
Expand Down Expand Up @@ -146,14 +149,12 @@ private void DisplayOutputResources(List<DisplayedResourceItem> 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.
/// </summary>
/// <param name="applicationName">The cloud application name provided via the --application-name CLI argument</param>
/// <param name="cloudApplicationName">The cloud application name provided via the --application-name CLI argument</param>
/// <param name="userDeploymentSettings">The deserialized object from the user provided config file.<see cref="UserDeploymentSettings"/></param>
/// <param name="deploymentProjectPath">The absolute or relative path of the CDK project that will be used for deployment</param>
/// <returns>A tuple consisting of the Orchestrator object, Selected Recommendation, Cloud Application metadata.</returns>
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,
Expand All @@ -180,8 +181,9 @@ private void DisplayOutputResources(List<DisplayedResourceItem> 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))
Expand Down Expand Up @@ -222,13 +224,20 @@ private void DisplayOutputResources(List<DisplayedResourceItem> 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);
Expand Down Expand Up @@ -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();

Expand All @@ -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<DockerExecutionDirectoryCommand>(_serviceProvider).OverrideValue(recommendation, settingValue.ToString() ?? "");
break;
case "DockerBuildArgs":
ActivatorUtilities.CreateInstance<DockerBuildArgsCommand>(_serviceProvider).OverrideValue(recommendation, settingValue.ToString() ?? "");
break;
case "DotnetBuildConfiguration":
ActivatorUtilities.CreateInstance<DotnetPublishBuildConfigurationCommand>(_serviceProvider).Overridevalue(recommendation, settingValue.ToString() ?? "");
break;
case "DotnetPublishArgs":
ActivatorUtilities.CreateInstance<DotnetPublishArgsCommand>(_serviceProvider).OverrideValue(recommendation, settingValue.ToString() ?? "");
break;
case "SelfContainedBuild":
ActivatorUtilities.CreateInstance<DotnetPublishSelfContainedBuildCommand>(_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<CloudApplication> deployedApplications)
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -887,7 +829,7 @@ private async Task ConfigureDeploymentFromCli(Recommendation recommendation, Opt
{
try
{
_optionSettingHandler.SetOptionSettingValue(setting, settingValue);
_optionSettingHandler.SetOptionSettingValue(recommendation, setting, settingValue);
}
catch (ValidationFailedException ex)
{
Expand Down
20 changes: 8 additions & 12 deletions src/AWS.Deploy.CLI/Commands/TypeHints/DockerBuildArgsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 "";
}
}
}
Loading

0 comments on commit aacb712

Please sign in to comment.