diff --git a/.gitignore b/.gitignore index 62806c3c5..d3f64fd23 100644 --- a/.gitignore +++ b/.gitignore @@ -447,6 +447,7 @@ $RECYCLE.BIN/ ## ## Visual Studio Code ## +.vscode/ .vscode/* !.vscode/settings.json !.vscode/tasks.json diff --git a/src/AWS.Deploy.CLI/AWSUtilities.cs b/src/AWS.Deploy.CLI/AWSUtilities.cs index 7fdbb327c..70478a196 100644 --- a/src/AWS.Deploy.CLI/AWSUtilities.cs +++ b/src/AWS.Deploy.CLI/AWSUtilities.cs @@ -14,8 +14,8 @@ namespace AWS.Deploy.CLI { public interface IAWSUtilities { - Task ResolveAWSCredentials(string profileName, string lastUsedProfileName); - string ResolveAWSRegion(string region, string lastRegionUsed); + Task ResolveAWSCredentials(string profileName, string? lastUsedProfileName); + string ResolveAWSRegion(string region, string? lastRegionUsed); } public class AWSUtilities : IAWSUtilities @@ -29,7 +29,7 @@ public AWSUtilities(IToolInteractiveService toolInteractiveService, IConsoleUtil _consoleUtilities = consoleUtilities; } - public async Task ResolveAWSCredentials(string profileName, string lastUsedProfileName) + public async Task ResolveAWSCredentials(string profileName, string? lastUsedProfileName) { @@ -113,7 +113,7 @@ private async Task CanLoadCredentials(AWSCredentials credentials) } } - public string ResolveAWSRegion(string region, string lastRegionUsed) + public string ResolveAWSRegion(string region, string? lastRegionUsed) { if (!string.IsNullOrEmpty(region)) { diff --git a/src/AWS.Deploy.CLI/Commands/CommandFactory.cs b/src/AWS.Deploy.CLI/Commands/CommandFactory.cs index 00caeee4c..eafcba296 100644 --- a/src/AWS.Deploy.CLI/Commands/CommandFactory.cs +++ b/src/AWS.Deploy.CLI/Commands/CommandFactory.cs @@ -133,14 +133,14 @@ private Command BuildDeployCommand() var callerIdentity = await _awsResourceQueryer.GetCallerIdentity(); - var session = new OrchestratorSession + var session = new OrchestratorSession( + projectDefinition, + awsCredentials, + awsRegion, + callerIdentity.Account) { - AWSProfileName = profile, - AWSCredentials = awsCredentials, - AWSRegion = awsRegion, - AWSAccountId = callerIdentity.Account, - ProjectDefinition = projectDefinition, - SystemCapabilities = systemCapabilities + SystemCapabilities = systemCapabilities, + AWSProfileName = profile }; var dockerEngine = new DockerEngine.DockerEngine(projectDefinition); diff --git a/src/AWS.Deploy.CLI/Commands/DeleteDeploymentCommand.cs b/src/AWS.Deploy.CLI/Commands/DeleteDeploymentCommand.cs index 99c7eb5f4..91cd8381b 100644 --- a/src/AWS.Deploy.CLI/Commands/DeleteDeploymentCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/DeleteDeploymentCommand.cs @@ -118,9 +118,9 @@ private async Task WaitForStackDelete(string stackName) throw new FailedToDeleteException($"Failed to delete {stackName} stack: {stack.StackStatus}"); } - private async Task StabilizeStack(string stackName) + private async Task StabilizeStack(string stackName) { - Stack stack; + Stack? stack; do { stack = await GetStackAsync(stackName); @@ -134,7 +134,7 @@ private async Task StabilizeStack(string stackName) return stack; } - private async Task GetStackAsync(string stackName) + private async Task GetStackAsync(string stackName) { try { diff --git a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs index d27d53dec..44b8dab8d 100644 --- a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs @@ -102,7 +102,7 @@ public async Task ExecuteAsync(string stackName, bool saveCdkProject) var deployedApplication = deployedApplications.FirstOrDefault(x => string.Equals(x.Name, cloudApplicationName)); - Recommendation selectedRecommendation = null; + Recommendation? selectedRecommendation = null; _toolInteractiveService.WriteLine(); @@ -148,6 +148,9 @@ public async Task ExecuteAsync(string stackName, bool saveCdkProject) // Apply the user enter project name to the recommendation so that any default settings based on project name are applied. selectedRecommendation.OverrideProjectName(cloudApplicationName); + if (_session.SystemCapabilities == null) + throw new SystemCapabilitiesNotProvidedException("The system capabilities were not provided."); + var systemCapabilities = await _session.SystemCapabilities; if (selectedRecommendation.Recipe.DeploymentType == DeploymentTypes.CdkProject && !systemCapabilities.NodeJsMinVersionInstalled) @@ -174,10 +177,7 @@ public async Task ExecuteAsync(string stackName, bool saveCdkProject) await ConfigureDeployment(selectedRecommendation, configurableOptionSettings, false); - var cloudApplication = new CloudApplication - { - Name = cloudApplicationName - }; + var cloudApplication = new CloudApplication(cloudApplicationName, string.Empty); if (!ConfirmDeployment(selectedRecommendation)) { @@ -232,7 +232,8 @@ private string AskUserForCloudApplicationName(ProjectDefinition project, List 0) { - var userInputConfig = new UserInputConfiguration + var userInputConfig = new UserInputConfiguration( + x => setting.ValueMapping.ContainsKey(x) ? setting.ValueMapping[x] : x, + x => x.Equals(currentValue)) { - DisplaySelector = x => setting.ValueMapping.ContainsKey(x) ? setting.ValueMapping[x] : x, - DefaultSelector = x => x.Equals(currentValue), CreateNew = false }; @@ -404,7 +405,7 @@ private async Task ConfigureDeployment(Recommendation recommendation, OptionSett { case OptionSettingValueType.String: case OptionSettingValueType.Int: - settingValue = _consoleUtilities.AskUserForValue(string.Empty, currentValue?.ToString(), allowEmpty: true, resetValue: recommendation.GetOptionSettingDefaultValue(setting)); + settingValue = _consoleUtilities.AskUserForValue(string.Empty, currentValue.ToString() ?? "", allowEmpty: true, resetValue: recommendation.GetOptionSettingDefaultValue(setting) ?? ""); break; case OptionSettingValueType.Bool: var answer = _consoleUtilities.AskYesNoQuestion(string.Empty, recommendation.GetOptionSettingValue(setting).ToString()); @@ -434,18 +435,18 @@ private async Task ConfigureDeployment(Recommendation recommendation, OptionSett /// This allows to use a generic implementation to display Object type option setting values without casting the response to /// the specific TypeHintResponse type. /// - private void DisplayValue(Recommendation recommendation, OptionSettingItem optionSetting, int optionSettingNumber, int optionSettingsCount, Type typeHintResponseType, DisplayOptionSettingsMode mode) + private void DisplayValue(Recommendation recommendation, OptionSettingItem optionSetting, int optionSettingNumber, int optionSettingsCount, Type? typeHintResponseType, DisplayOptionSettingsMode mode) { - object displayValue = null; - Dictionary objectValues = null; + object? displayValue = null; + Dictionary? objectValues = null; if (typeHintResponseType != null) { var methodInfo = typeof(Recommendation) - .GetMethod(nameof(Recommendation.GetOptionSettingValue), 1, new[] { typeof(OptionSettingItem), typeof(bool) }); + .GetMethod(nameof(Recommendation.GetOptionSettingValue), 1, new[] { typeof(OptionSettingItem) }); var genericMethodInfo = methodInfo?.MakeGenericMethod(typeHintResponseType); - var response = genericMethodInfo?.Invoke(recommendation, new object[] { optionSetting, false }); + var response = genericMethodInfo?.Invoke(recommendation, new object[] { optionSetting }); - displayValue = ((IDisplayable)response)?.ToDisplayString(); + displayValue = ((IDisplayable?)response)?.ToDisplayString(); } else { diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/BeanstalkApplicationCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/BeanstalkApplicationCommand.cs index 17461a9b0..3a8b00209 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/BeanstalkApplicationCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/BeanstalkApplicationCommand.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.Threading.Tasks; using Amazon.ElasticBeanstalk.Model; using AWS.Deploy.CLI.TypeHintResponses; @@ -27,21 +28,21 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt var applications = await _awsResourceQueryer.ListOfElasticBeanstalkApplications(); var currentTypeHintResponse = recommendation.GetOptionSettingValue(optionSetting); - var userInputConfiguration = new UserInputConfiguration + var userInputConfiguration = new UserInputConfiguration( + app => app.ApplicationName, + app => app.ApplicationName.Equals(currentTypeHintResponse?.ApplicationName), + currentTypeHintResponse.ApplicationName) { - DisplaySelector = app => app.ApplicationName, - DefaultSelector = app => app.ApplicationName.Equals(currentTypeHintResponse?.ApplicationName), AskNewName = true, - DefaultNewName = currentTypeHintResponse.ApplicationName }; var userResponse = _consoleUtilities.AskUserToChooseOrCreateNew(applications, "Select Elastic Beanstalk application to deploy to:", userInputConfiguration); - return new BeanstalkApplicationTypeHintResponse - { - CreateNew = userResponse.CreateNew, - ApplicationName = userResponse.SelectedOption?.ApplicationName ?? userResponse.NewName - }; + return new BeanstalkApplicationTypeHintResponse( + userResponse.CreateNew, + userResponse.SelectedOption?.ApplicationName ?? userResponse.NewName + ?? throw new UserPromptForNameReturnedNullException("The user response for a new application name was null.") + ); } } } diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/BeanstalkEnvironmentCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/BeanstalkEnvironmentCommand.cs index 9ede451a0..4f12dfb93 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/BeanstalkEnvironmentCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/BeanstalkEnvironmentCommand.cs @@ -32,8 +32,9 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt options: environments.Select(env => env.EnvironmentName), title: "Select Elastic Beanstalk environment to deploy to:", askNewName: true, - defaultNewName: currentValue.ToString()); - return userResponse.SelectedOption ?? userResponse.NewName; + defaultNewName: currentValue.ToString() ?? ""); + return userResponse.SelectedOption ?? userResponse.NewName + ?? throw new UserPromptForNameReturnedNullException("The user response for a new environment name was null."); } } } diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/DockerBuildArgsCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/DockerBuildArgsCommand.cs index 380b325e4..77ca5f5b7 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/DockerBuildArgsCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/DockerBuildArgsCommand.cs @@ -24,7 +24,7 @@ public Task Execute(Recommendation recommendation, OptionSettingItem opt string.Empty, recommendation.GetOptionSettingValue(optionSetting), allowEmpty: true, - resetValue: recommendation.GetOptionSettingDefaultValue(optionSetting), + resetValue: recommendation.GetOptionSettingDefaultValue(optionSetting) ?? "", // validators: buildArgs => ValidateBuildArgs(buildArgs)) .ToString() diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/DockerExecutionDirectoryCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/DockerExecutionDirectoryCommand.cs index 4f40f43ec..13b1bdbad 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/DockerExecutionDirectoryCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/DockerExecutionDirectoryCommand.cs @@ -24,7 +24,7 @@ public Task Execute(Recommendation recommendation, OptionSettingItem opt string.Empty, recommendation.GetOptionSettingValue(optionSetting), allowEmpty: true, - resetValue: recommendation.GetOptionSettingDefaultValue(optionSetting), + resetValue: recommendation.GetOptionSettingDefaultValue(optionSetting) ?? "", // validators: executionDirectory => ValidateExecutionDirectory(executionDirectory)); diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetBeanstalkPlatformArnCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetBeanstalkPlatformArnCommand.cs index 67ce5158a..cf138fe3b 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetBeanstalkPlatformArnCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetBeanstalkPlatformArnCommand.cs @@ -26,16 +26,16 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt var currentValue = recommendation.GetOptionSettingValue(optionSetting); var platformArns = await _awsResourceQueryer.GetElasticBeanstalkPlatformArns(); - var userInputConfiguration = new UserInputConfiguration + var userInputConfiguration = new UserInputConfiguration( + platform => $"{platform.PlatformBranchName} v{platform.PlatformVersion}", + platform => platform.PlatformArn.Equals(currentValue)) { - DisplaySelector = platform => $"{platform.PlatformBranchName} v{platform.PlatformVersion}", - DefaultSelector = platform => platform.PlatformArn.Equals(currentValue), CreateNew = false }; var userResponse = _consoleUtilities.AskUserToChooseOrCreateNew(platformArns, "Select the Platform to use:", userInputConfiguration); - return userResponse.SelectedOption?.PlatformArn; + return userResponse.SelectedOption?.PlatformArn!; } } } diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetPublishArgsCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetPublishArgsCommand.cs index 42d48832d..0e12c331c 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetPublishArgsCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetPublishArgsCommand.cs @@ -21,9 +21,9 @@ public Task Execute(Recommendation recommendation, OptionSettingItem opt var settingValue = _consoleUtilities .AskUserForValue( string.Empty, - recommendation.GetOptionSettingValue(optionSetting).ToString(), + recommendation.GetOptionSettingValue(optionSetting), allowEmpty: true, - resetValue: recommendation.GetOptionSettingDefaultValue(optionSetting), + resetValue: recommendation.GetOptionSettingDefaultValue(optionSetting) ?? "", // validators: publishArgs => (publishArgs.Contains("-o ") || publishArgs.Contains("--output ")) diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetPublishBuildConfigurationCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetPublishBuildConfigurationCommand.cs index d885e924f..6bfbd0168 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetPublishBuildConfigurationCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/DotnetPublishBuildConfigurationCommand.cs @@ -23,7 +23,7 @@ public Task Execute(Recommendation recommendation, OptionSettingItem opt string.Empty, recommendation.GetOptionSettingValue(optionSetting), allowEmpty: false, - resetValue: recommendation.GetOptionSettingDefaultValue(optionSetting)); + resetValue: recommendation.GetOptionSettingDefaultValue(optionSetting) ?? ""); recommendation.DeploymentBundle.DotnetPublishBuildConfiguration = settingValue; return Task.FromResult(settingValue); } diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/EC2KeyPairCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/EC2KeyPairCommand.cs index e342343ea..51fdde121 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/EC2KeyPairCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/EC2KeyPairCommand.cs @@ -27,10 +27,11 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt var currentValue = recommendation.GetOptionSettingValue(optionSetting); var keyPairs = await _awsResourceQueryer.ListOfEC2KeyPairs(); - var userInputConfiguration = new UserInputConfiguration + var userInputConfiguration = new UserInputConfiguration( + kp => kp.KeyName, + kp => kp.KeyName.Equals(currentValue) + ) { - DisplaySelector = kp => kp.KeyName, - DefaultSelector = kp => kp.KeyName.Equals(currentValue), AskNewName = true, EmptyOption = true, CurrentValue = currentValue @@ -49,7 +50,8 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt } else { - settingValue = userResponse.SelectedOption?.KeyName ?? userResponse.NewName; + settingValue = userResponse.SelectedOption?.KeyName ?? userResponse.NewName ?? + throw new UserPromptForNameReturnedNullException("The user prompt for a new EC2 Key Pair name was null or empty."); } if (userResponse.CreateNew && !string.IsNullOrEmpty(userResponse.NewName)) @@ -73,7 +75,7 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt break; } - return settingValue; + return settingValue ?? ""; } } } diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/ECSClusterCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/ECSClusterCommand.cs index 345cb6cbb..d4399e1c2 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/ECSClusterCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/ECSClusterCommand.cs @@ -26,22 +26,20 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt var clusters = await _awsResourceQueryer.ListOfECSClusters(); var currentTypeHintResponse = recommendation.GetOptionSettingValue(optionSetting); - var userInputConfiguration = new UserInputConfiguration + var userInputConfiguration = new UserInputConfiguration( + cluster => cluster.ClusterName, + cluster => cluster.ClusterArn.Equals(currentTypeHintResponse?.ClusterArn), + currentTypeHintResponse.NewClusterName) { - DisplaySelector = cluster => cluster.ClusterName, - DefaultSelector = cluster => cluster.ClusterArn.Equals(currentTypeHintResponse?.ClusterArn), - AskNewName = true, - DefaultNewName = currentTypeHintResponse.NewClusterName + AskNewName = true }; var userResponse = _consoleUtilities.AskUserToChooseOrCreateNew(clusters, "Select ECS cluster to deploy to:", userInputConfiguration); - return new ECSClusterTypeHintResponse - { - CreateNew = userResponse.CreateNew, - ClusterArn = userResponse.SelectedOption?.ClusterArn ?? string.Empty, - NewClusterName = userResponse.NewName - }; + return new ECSClusterTypeHintResponse( + userResponse.CreateNew, + userResponse.SelectedOption?.ClusterArn ?? string.Empty, + userResponse.NewName ?? string.Empty); } } } diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/IAMRoleCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/IAMRoleCommand.cs index cb1fecc8b..a5e8ca622 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/IAMRoleCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/IAMRoleCommand.cs @@ -29,11 +29,9 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt var existingRoles = await _awsResourceQueryer.ListOfIAMRoles(typeHintData?.ServicePrincipal); var currentTypeHintResponse = recommendation.GetOptionSettingValue(optionSetting); - var userInputConfiguration = new UserInputConfiguration - { - DisplaySelector = role => role.RoleName, - DefaultSelector = role => currentTypeHintResponse.RoleArn?.Equals(role.Arn) ?? false, - }; + var userInputConfiguration = new UserInputConfiguration( + role => role.RoleName, + role => currentTypeHintResponse.RoleArn?.Equals(role.Arn) ?? false); var userResponse = _consoleUtilities.AskUserToChooseOrCreateNew(existingRoles ,"Select an IAM role", userInputConfiguration); diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs index 5bfe830ce..9e8fd6d74 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs @@ -19,7 +19,7 @@ public interface ITypeHintCommand public interface ITypeHintCommandFactory { - ITypeHintCommand GetCommand(OptionSettingTypeHint typeHint); + ITypeHintCommand? GetCommand(OptionSettingTypeHint typeHint); } /// @@ -48,7 +48,7 @@ public TypeHintCommandFactory(IToolInteractiveService toolInteractiveService, IA }; } - public ITypeHintCommand GetCommand(OptionSettingTypeHint typeHint) + public ITypeHintCommand? GetCommand(OptionSettingTypeHint typeHint) { if (!_commands.ContainsKey(typeHint)) { diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/VpcCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/VpcCommand.cs index 014c86705..804c2b07c 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/VpcCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/VpcCommand.cs @@ -29,9 +29,8 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt var vpcs = await _awsResourceQueryer.GetListOfVpcs(); - var userInputConfig = new UserInputConfiguration - { - DisplaySelector = vpc => + var userInputConfig = new UserInputConfiguration( + vpc => { var name = vpc.Tags?.FirstOrDefault(x => x.Key == "Name")?.Value ?? string.Empty; var namePart = @@ -46,23 +45,21 @@ public async Task Execute(Recommendation recommendation, OptionSettingIt return $"{vpc.VpcId}{namePart}{isDefaultPart}"; }, - DefaultSelector = vpc => + vpc => !string.IsNullOrEmpty(currentVpcTypeHintResponse?.VpcId) ? vpc.VpcId == currentVpcTypeHintResponse.VpcId - : vpc.IsDefault - }; + : vpc.IsDefault); var userResponse = _consoleUtilities.AskUserToChooseOrCreateNew( vpcs, "Select a VPC", userInputConfig); - return new VpcTypeHintResponse - { - IsDefault = userResponse.SelectedOption?.IsDefault == true, - CreateNew = userResponse.CreateNew, - VpcId = userResponse.SelectedOption?.VpcId ?? "" - }; + return new VpcTypeHintResponse( + userResponse.SelectedOption?.IsDefault == true, + userResponse.CreateNew, + userResponse.SelectedOption?.VpcId ?? "" + ); } } } diff --git a/src/AWS.Deploy.CLI/ConsoleUtilities.cs b/src/AWS.Deploy.CLI/ConsoleUtilities.cs index 9159d7a3f..e794b46db 100644 --- a/src/AWS.Deploy.CLI/ConsoleUtilities.cs +++ b/src/AWS.Deploy.CLI/ConsoleUtilities.cs @@ -19,7 +19,7 @@ public enum YesNo public interface IConsoleUtilities { Recommendation AskToChooseRecommendation(IList recommendations); - string AskUserToChoose(IList values, string title, string defaultValue); + string AskUserToChoose(IList values, string title, string? defaultValue); T AskUserToChoose(IList options, string title, T defaultValue) where T : IUserInputOption; void DisplayRow((string, int)[] row); @@ -27,7 +27,7 @@ T AskUserToChoose(IList options, string title, T defaultValue) UserResponse AskUserToChooseOrCreateNew(IEnumerable options, string title, UserInputConfiguration userInputConfiguration); string AskUserForValue(string message, string defaultValue, bool allowEmpty, string resetValue = "", params Func[] validators); string AskForEC2KeyPairSaveDirectory(string projectPath); - YesNo AskYesNoQuestion(string question, string defaultValue); + YesNo AskYesNoQuestion(string question, string? defaultValue); YesNo AskYesNoQuestion(string question, YesNo? defaultValue = default); void DisplayValues(Dictionary objectValues, string indent); } @@ -73,7 +73,7 @@ public Recommendation AskToChooseRecommendation(IList recommenda return ReadOptionFromUser(recommendations, 1); } - public string AskUserToChoose(IList values, string title, string defaultValue) + public string AskUserToChoose(IList values, string title, string? defaultValue) { var options = new List(); foreach (var value in values) @@ -81,10 +81,12 @@ public string AskUserToChoose(IList values, string title, string default options.Add(new UserInputOption(value)); } - return AskUserToChoose(options, title, new UserInputOption(defaultValue))?.Name; + UserInputOption? defaultOption = defaultValue != null ? new UserInputOption(defaultValue) : null; + + return AskUserToChoose(options, title, defaultOption).Name; } - public T AskUserToChoose(IList options, string title, T defaultValue) + public T AskUserToChoose(IList options, string title, T? defaultValue) where T : IUserInputOption { if (!string.IsNullOrEmpty(title)) @@ -170,12 +172,12 @@ public void DisplayRow((string, int)[] row) public UserResponse AskUserToChooseOrCreateNew(IEnumerable options, string title, bool askNewName = true, string defaultNewName = "", bool canBeEmpty = false) { - var configuration = new UserInputConfiguration + var configuration = new UserInputConfiguration( + option => option, + option => option.Contains(option), + defaultNewName) { - DisplaySelector = option => option, - DefaultSelector = option => option.Contains(option), AskNewName = askNewName, - DefaultNewName = defaultNewName, CanBeEmpty = canBeEmpty }; @@ -257,7 +259,7 @@ public string AskUserForValue(string message, string defaultValue, bool allowEmp prompt += "): "; _interactiveService.WriteLine(prompt); - string userValue = null; + string? userValue = null; while (true) { var line = _interactiveService.ReadLine()?.Trim() ?? ""; @@ -334,7 +336,7 @@ public string AskForEC2KeyPairSaveDirectory(string projectPath) } } - public YesNo AskYesNoQuestion(string question, string defaultValue) + public YesNo AskYesNoQuestion(string question, string? defaultValue) { if (bool.TryParse(defaultValue, out var result)) return AskYesNoQuestion(question, result ? YesNo.Yes : YesNo.No); diff --git a/src/AWS.Deploy.CLI/Exceptions.cs b/src/AWS.Deploy.CLI/Exceptions.cs index 620d5d021..7d9570539 100644 --- a/src/AWS.Deploy.CLI/Exceptions.cs +++ b/src/AWS.Deploy.CLI/Exceptions.cs @@ -12,7 +12,7 @@ namespace AWS.Deploy.CLI [AWSDeploymentExpectedException] public class NoAWSCredentialsFoundException : Exception { - public NoAWSCredentialsFoundException(string message, Exception innerException = null) : base(message, innerException) { } + public NoAWSCredentialsFoundException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -22,7 +22,7 @@ public NoAWSCredentialsFoundException(string message, Exception innerException = [AWSDeploymentExpectedException ] public class FailedToDeleteException : Exception { - public FailedToDeleteException(string message, Exception innerException = null) : base(message, innerException) { } + public FailedToDeleteException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -32,6 +32,33 @@ public FailedToDeleteException(string message, Exception innerException = null) [AWSDeploymentExpectedException] public class FailedToFindDeployableTargetException : Exception { - public FailedToFindDeployableTargetException(string message, Exception innerException = null) : base(message, innerException) { } + public FailedToFindDeployableTargetException(string message, Exception? innerException = null) : base(message, innerException) { } + } + + /// + /// Throw if docker info failed to return output. + /// + [AWSDeploymentExpectedException] + public class DockerInfoException : Exception + { + public DockerInfoException(string message, Exception? innerException = null) : base(message, innerException) { } + } + + /// + /// Throw if prompting the user for a name returns a null value. + /// + [AWSDeploymentExpectedException] + public class UserPromptForNameReturnedNullException : Exception + { + public UserPromptForNameReturnedNullException(string message, Exception? innerException = null) : base(message, innerException) { } + } + + /// + /// Throw if the system capabilities were not provided. + /// + [AWSDeploymentExpectedException] + public class SystemCapabilitiesNotProvidedException : Exception + { + public SystemCapabilitiesNotProvidedException(string message, Exception? innerException = null) : base(message, innerException) { } } } diff --git a/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs b/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs index 3cd4d25a0..b5863f270 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs @@ -62,18 +62,20 @@ IHubContext hubContext [Authorize] public async Task StartDeploymentSession(StartDeploymentSessionInput input) { - var output = new StartDeploymentSessionOutput - { - SessionId = Guid.NewGuid().ToString() - }; + var output = new StartDeploymentSessionOutput( + Guid.NewGuid().ToString() + ); - var state = new SessionState - { - SessionId = output.SessionId, - ProjectPath = input.ProjectPath, - AWSRegion = input.AWSRegion - }; - state.ProjectDefinition = await _projectParserUtility.Parse(state.ProjectPath); + var serviceProvider = CreateSessionServiceProvider(output.SessionId, input.AWSRegion); + var awsResourceQueryer = serviceProvider.GetRequiredService(); + + var state = new SessionState( + output.SessionId, + input.ProjectPath, + input.AWSRegion, + (await awsResourceQueryer.GetCallerIdentity()).Account, + await _projectParserUtility.Parse(input.ProjectPath) + ); _stateServer.Save(output.SessionId, state); @@ -115,12 +117,11 @@ public async Task GetRecommendations(string sessionId) state.NewRecommendations = await orchestrator.GenerateDeploymentRecommendations(); foreach (var recommendation in state.NewRecommendations) { - output.Recommendations.Add(new RecommendationSummary - { - Name = recommendation.Name, - Description = recommendation.Description, - RecipeId = recommendation.Recipe.Id - }); + output.Recommendations.Add(new RecommendationSummary( + recommendation.Recipe.Id, + recommendation.Name, + recommendation.Description + )); } return Ok(output); @@ -155,11 +156,9 @@ public async Task GetExistingDeployments(string sessionId) foreach(var deployment in state.ExistingDeployments) { - output.ExistingDeployments.Add(new ExistingDeploymentSummary - { - Name = deployment.Name, - RecipeId = deployment.RecipeId - }); + output.ExistingDeployments.Add(new ExistingDeploymentSummary( + deployment.Name, + deployment.RecipeId)); } return Ok(output); @@ -179,7 +178,8 @@ public async Task SetDeploymentTarget(string sessionId, [FromBody return NotFound($"Session ID {sessionId} not found."); } - if(!string.IsNullOrEmpty(input.NewDeploymentRecipeId)) + if(!string.IsNullOrEmpty(input.NewDeploymentRecipeId) && + !string.IsNullOrEmpty(input.NewDeploymentName)) { state.SelectedRecommendation = state.NewRecommendations.FirstOrDefault(x => string.Equals(input.NewDeploymentRecipeId, x.Recipe.Id)); if(state.SelectedRecommendation == null) @@ -221,9 +221,9 @@ public async Task SetDeploymentTarget(string sessionId, [FromBody /// Begin execution of the deployment. /// [HttpPost("session//execute")] - [SwaggerOperation(OperationId = "StartDeployment")] + [SwaggerOperation(OperationId = "StartDeployment")] [Authorize] - public async Task StartDeployment(string sessionId) + public IActionResult StartDeployment(string sessionId) { var state = _stateServer.Get(sessionId); if (state == null) @@ -232,11 +232,12 @@ public async Task StartDeployment(string sessionId) } var serviceProvider = CreateSessionServiceProvider(state); - var awsResourceQueryer = serviceProvider.GetRequiredService(); - state.AWSAccountId = (await awsResourceQueryer.GetCallerIdentity()).Account; var orchestrator = CreateOrchestrator(state, serviceProvider); + if (state.SelectedRecommendation == null) + throw new SelectedRecommendationIsNullException("The selected recommendation is null or invalid."); + var task = new DeployRecommendationTask(orchestrator, state.ApplicationDetails, state.SelectedRecommendation); state.DeploymentTask = task.Execute(); @@ -274,7 +275,12 @@ public IActionResult GetDeploymentStatus(string sessionId) private IServiceProvider CreateSessionServiceProvider(SessionState state) { - var interactiveServices = new SessionOrchestratorInteractiveService(state.SessionId, _hubContext); + return CreateSessionServiceProvider(state.SessionId, state.AWSRegion); + } + + private IServiceProvider CreateSessionServiceProvider(string sessionId, string awsRegion) + { + var interactiveServices = new SessionOrchestratorInteractiveService(sessionId, _hubContext); var services = new ServiceCollection(); services.AddSingleton(interactiveServices); services.AddSingleton(services => new CommandLineWrapper(interactiveServices, true)); @@ -286,26 +292,25 @@ private IServiceProvider CreateSessionServiceProvider(SessionState state) awsClientFactory.ConfigureAWSOptions(awsOptions => { awsOptions.Credentials = awsCredentials; - awsOptions.Region = RegionEndpoint.GetBySystemName(state.AWSRegion); + awsOptions.Region = RegionEndpoint.GetBySystemName(awsRegion); }); return serviceProvider; } - private Orchestrator CreateOrchestrator(SessionState state, IServiceProvider serviceProvider = null, AWSCredentials awsCredentials = null) + private Orchestrator CreateOrchestrator(SessionState state, IServiceProvider? serviceProvider = null, AWSCredentials? awsCredentials = null) { if(serviceProvider == null) { serviceProvider = CreateSessionServiceProvider(state); } - var session = new OrchestratorSession - { - AWSRegion = state.AWSRegion, - AWSAccountId = state.AWSAccountId, - ProjectDefinition = state.ProjectDefinition, - AWSCredentials = awsCredentials ?? HttpContext.User.ToAWSCredentials() - }; + var session = new OrchestratorSession( + state.ProjectDefinition, + awsCredentials ?? HttpContext.User.ToAWSCredentials() ?? + throw new FailedToRetrieveAWSCredentialsException("The tool was not able to retrieve the AWS Credentials."), + state.AWSRegion, + state.AWSAccountId); return new Orchestrator( session, diff --git a/src/AWS.Deploy.CLI/ServerMode/Exceptions.cs b/src/AWS.Deploy.CLI/ServerMode/Exceptions.cs new file mode 100644 index 000000000..9841054dc --- /dev/null +++ b/src/AWS.Deploy.CLI/ServerMode/Exceptions.cs @@ -0,0 +1,26 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r +// SPDX-License-Identifier: Apache-2.0 + +using System; +using AWS.Deploy.Common; + +namespace AWS.Deploy.CLI.ServerMode +{ + /// + /// Throw if the selected recommendation is null. + /// + [AWSDeploymentExpectedException] + public class SelectedRecommendationIsNullException : Exception + { + public SelectedRecommendationIsNullException(string message, Exception? innerException = null) : base(message, innerException) { } + } + + /// + /// Throw if the tool was not able to retrieve the AWS Credentials. + /// + [AWSDeploymentExpectedException] + public class FailedToRetrieveAWSCredentialsException : Exception + { + public FailedToRetrieveAWSCredentialsException(string message, Exception? innerException = null) : base(message, innerException) { } + } +} diff --git a/src/AWS.Deploy.CLI/ServerMode/ExtensionMethods.cs b/src/AWS.Deploy.CLI/ServerMode/ExtensionMethods.cs index 50a76102b..ba4673764 100644 --- a/src/AWS.Deploy.CLI/ServerMode/ExtensionMethods.cs +++ b/src/AWS.Deploy.CLI/ServerMode/ExtensionMethods.cs @@ -16,7 +16,7 @@ public static class ExtensionMethods /// Create an AWSCredentials object from the key information set as claims on the current request's ClaimsPrincipal. /// /// - public static AWSCredentials ToAWSCredentials(this ClaimsPrincipal user) + public static AWSCredentials? ToAWSCredentials(this ClaimsPrincipal user) { var awsAccessKeyId = user.Claims.FirstOrDefault(x => string.Equals(x.Type, AwsCredentialsAuthenticationHandler.ClaimAwsAccessKeyId))?.Value; var awsSecretKey = user.Claims.FirstOrDefault(x => string.Equals(x.Type, AwsCredentialsAuthenticationHandler.ClaimAwsSecretKey))?.Value; diff --git a/src/AWS.Deploy.CLI/ServerMode/Models/ExistingDeploymentSummary.cs b/src/AWS.Deploy.CLI/ServerMode/Models/ExistingDeploymentSummary.cs index 7a1798ceb..d4f938929 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Models/ExistingDeploymentSummary.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Models/ExistingDeploymentSummary.cs @@ -13,5 +13,14 @@ public class ExistingDeploymentSummary public string Name { get; set; } public string RecipeId { get; set; } + + public ExistingDeploymentSummary( + string name, + string recipeId + ) + { + Name = name; + RecipeId = recipeId; + } } } diff --git a/src/AWS.Deploy.CLI/ServerMode/Models/RecommendationSummary.cs b/src/AWS.Deploy.CLI/ServerMode/Models/RecommendationSummary.cs index f72751a54..bdbc2acc9 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Models/RecommendationSummary.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Models/RecommendationSummary.cs @@ -12,5 +12,16 @@ public class RecommendationSummary public string RecipeId { get; set; } public string Name { get; set; } public string Description { get; set; } + + public RecommendationSummary( + string recipeId, + string name, + string description + ) + { + RecipeId = recipeId; + Name = name; + Description = description; + } } } diff --git a/src/AWS.Deploy.CLI/ServerMode/Models/SetDeploymentTargetInput.cs b/src/AWS.Deploy.CLI/ServerMode/Models/SetDeploymentTargetInput.cs index d429ff9a4..1b24d0cf4 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Models/SetDeploymentTargetInput.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Models/SetDeploymentTargetInput.cs @@ -9,10 +9,10 @@ namespace AWS.Deploy.CLI.ServerMode.Models { public class SetDeploymentTargetInput { - public string NewDeploymentName { get; set; } + public string? NewDeploymentName { get; set; } - public string NewDeploymentRecipeId { get; set; } + public string? NewDeploymentRecipeId { get; set; } - public string ExistingDeploymentName { get; set; } + public string? ExistingDeploymentName { get; set; } } } diff --git a/src/AWS.Deploy.CLI/ServerMode/Models/StartDeploymentSessionInput.cs b/src/AWS.Deploy.CLI/ServerMode/Models/StartDeploymentSessionInput.cs index 67acb09df..b17fb2a9a 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Models/StartDeploymentSessionInput.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Models/StartDeploymentSessionInput.cs @@ -11,5 +11,13 @@ public class StartDeploymentSessionInput { public string AWSRegion { get; set; } public string ProjectPath { get; set; } + + public StartDeploymentSessionInput( + string awsRegion, + string projectPath) + { + AWSRegion = awsRegion; + ProjectPath = projectPath; + } } } diff --git a/src/AWS.Deploy.CLI/ServerMode/Models/StartDeploymentSessionOutput.cs b/src/AWS.Deploy.CLI/ServerMode/Models/StartDeploymentSessionOutput.cs index 5d3aa4f86..158673485 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Models/StartDeploymentSessionOutput.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Models/StartDeploymentSessionOutput.cs @@ -11,6 +11,11 @@ public class StartDeploymentSessionOutput { public string SessionId { get; set; } - public string DefaultDeploymentName { get; set; } + public string? DefaultDeploymentName { get; set; } + + public StartDeploymentSessionOutput(string sessionId) + { + SessionId = sessionId; + } } } diff --git a/src/AWS.Deploy.CLI/ServerMode/Services/IDeploymentSessionStateServer.cs b/src/AWS.Deploy.CLI/ServerMode/Services/IDeploymentSessionStateServer.cs index fcaf8343e..cfec8f2a0 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Services/IDeploymentSessionStateServer.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Services/IDeploymentSessionStateServer.cs @@ -9,7 +9,7 @@ namespace AWS.Deploy.CLI.ServerMode.Services { public interface IDeploymentSessionStateServer { - SessionState Get(string id); + SessionState? Get(string id); void Save(string id, SessionState state); diff --git a/src/AWS.Deploy.CLI/ServerMode/Services/InMemoryDeploymentSessionStateServer.cs b/src/AWS.Deploy.CLI/ServerMode/Services/InMemoryDeploymentSessionStateServer.cs index 14da4a407..28066a1f3 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Services/InMemoryDeploymentSessionStateServer.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Services/InMemoryDeploymentSessionStateServer.cs @@ -12,7 +12,7 @@ public class InMemoryDeploymentSessionStateServer : IDeploymentSessionStateServe { private readonly IDictionary _store = new ConcurrentDictionary(); - public SessionState Get(string id) + public SessionState? Get(string id) { if(_store.TryGetValue(id, out var state)) { diff --git a/src/AWS.Deploy.CLI/ServerMode/SessionState.cs b/src/AWS.Deploy.CLI/ServerMode/SessionState.cs index fa91b2524..c7296f9bf 100644 --- a/src/AWS.Deploy.CLI/ServerMode/SessionState.cs +++ b/src/AWS.Deploy.CLI/ServerMode/SessionState.cs @@ -22,14 +22,29 @@ public class SessionState public ProjectDefinition ProjectDefinition { get; set; } - public IList NewRecommendations { get; set; } - - public IList ExistingDeployments { get; set; } - - public Recommendation SelectedRecommendation { get; set; } - - public CloudApplication ApplicationDetails { get; } = new CloudApplication(); - - public Task DeploymentTask { get; set; } + public IList? NewRecommendations { get; set; } + + public IList? ExistingDeployments { get; set; } + + public Recommendation? SelectedRecommendation { get; set; } + + public CloudApplication ApplicationDetails { get; } = new CloudApplication(string.Empty, string.Empty); + + public Task? DeploymentTask { get; set; } + + public SessionState( + string sessionId, + string projectPath, + string awsRegion, + string awsAccountId, + ProjectDefinition projectDefinition + ) + { + SessionId = sessionId; + ProjectPath = projectPath; + AWSRegion = awsRegion; + AWSAccountId = awsAccountId; + ProjectDefinition = projectDefinition; + } } } diff --git a/src/AWS.Deploy.CLI/SystemCapabilityEvaluator.cs b/src/AWS.Deploy.CLI/SystemCapabilityEvaluator.cs index ece2453de..412c0fafe 100644 --- a/src/AWS.Deploy.CLI/SystemCapabilityEvaluator.cs +++ b/src/AWS.Deploy.CLI/SystemCapabilityEvaluator.cs @@ -30,11 +30,7 @@ public async Task Evaluate() var dockerTask = HasDockerInstalledAndRunning(); var nodeTask = HasMinVersionNodeJs(); - var capabilities = new SystemCapabilities - { - DockerInfo = await dockerTask, - NodeJsMinVersionInstalled = await nodeTask, - }; + var capabilities = new SystemCapabilities(await nodeTask, await dockerTask); return capabilities; } @@ -51,14 +47,14 @@ await _commandLineWrapper.Run( onComplete: proc => { processExitCode = proc.ExitCode; - containerType = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? proc.StandardOut.TrimEnd('\n') : "linux"; + containerType = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? + proc.StandardOut?.TrimEnd('\n') ?? + throw new DockerInfoException("Failed to check if Docker is running in Windows or Linux container mode.") : + "linux"; }); - var dockerInfo = new DockerInfo - { - DockerInstalled = processExitCode == 0, - DockerContainerType = containerType - }; + var dockerInfo = new DockerInfo(processExitCode == 0, containerType); + return dockerInfo; } @@ -71,7 +67,7 @@ private async Task HasMinVersionNodeJs() // run node --version to get the version var result = await _commandLineWrapper.TryRunWithResult("node --version"); - var versionString = result.StandardOut; + var versionString = result.StandardOut ?? ""; if (versionString.StartsWith("v", StringComparison.OrdinalIgnoreCase)) versionString = versionString.Substring(1, versionString.Length - 1); diff --git a/src/AWS.Deploy.CLI/TypeHintResponses/BeanstalkApplicationTypeHintResponse.cs b/src/AWS.Deploy.CLI/TypeHintResponses/BeanstalkApplicationTypeHintResponse.cs index 43d66e01e..a09452f0d 100644 --- a/src/AWS.Deploy.CLI/TypeHintResponses/BeanstalkApplicationTypeHintResponse.cs +++ b/src/AWS.Deploy.CLI/TypeHintResponses/BeanstalkApplicationTypeHintResponse.cs @@ -14,6 +14,14 @@ public class BeanstalkApplicationTypeHintResponse : IDisplayable public bool CreateNew { get; set; } public string ApplicationName { get; set; } + public BeanstalkApplicationTypeHintResponse( + bool createNew, + string applicationName) + { + CreateNew = createNew; + ApplicationName = applicationName; + } + public string ToDisplayString() => ApplicationName; } } diff --git a/src/AWS.Deploy.CLI/TypeHintResponses/ECSClusterTypeHintResponse.cs b/src/AWS.Deploy.CLI/TypeHintResponses/ECSClusterTypeHintResponse.cs index 65c2383c3..427c226a6 100644 --- a/src/AWS.Deploy.CLI/TypeHintResponses/ECSClusterTypeHintResponse.cs +++ b/src/AWS.Deploy.CLI/TypeHintResponses/ECSClusterTypeHintResponse.cs @@ -13,6 +13,16 @@ public class ECSClusterTypeHintResponse : IDisplayable public string ClusterArn { get; set; } public string NewClusterName { get; set; } + public ECSClusterTypeHintResponse( + bool createNew, + string clusterArn, + string newClusterName) + { + CreateNew = createNew; + ClusterArn = clusterArn; + NewClusterName = newClusterName; + } + public string ToDisplayString() { if (CreateNew) diff --git a/src/AWS.Deploy.CLI/TypeHintResponses/IAMRoleTypeHintResponse.cs b/src/AWS.Deploy.CLI/TypeHintResponses/IAMRoleTypeHintResponse.cs index b4768f812..f826cc70c 100644 --- a/src/AWS.Deploy.CLI/TypeHintResponses/IAMRoleTypeHintResponse.cs +++ b/src/AWS.Deploy.CLI/TypeHintResponses/IAMRoleTypeHintResponse.cs @@ -11,9 +11,9 @@ namespace AWS.Deploy.CLI.TypeHintResponses /// public class IAMRoleTypeHintResponse : IDisplayable { - public string RoleArn { get; set; } + public string? RoleArn { get; set; } public bool CreateNew { get; set; } - public string ToDisplayString() => CreateNew ? Constants.CREATE_NEW_LABEL : RoleArn; + public string ToDisplayString() => CreateNew ? Constants.CREATE_NEW_LABEL : RoleArn ?? ""; } } diff --git a/src/AWS.Deploy.CLI/TypeHintResponses/VpcTypeHintResponse.cs b/src/AWS.Deploy.CLI/TypeHintResponses/VpcTypeHintResponse.cs index 7aa8fa54b..62d7f51bc 100644 --- a/src/AWS.Deploy.CLI/TypeHintResponses/VpcTypeHintResponse.cs +++ b/src/AWS.Deploy.CLI/TypeHintResponses/VpcTypeHintResponse.cs @@ -18,11 +18,21 @@ public class VpcTypeHintResponse : IDisplayable public bool CreateNew { get;set; } public string VpcId { get; set; } + public VpcTypeHintResponse( + bool isDefault, + bool createNew, + string vpcId) + { + IsDefault = isDefault; + CreateNew = createNew; + VpcId = vpcId; + } + public string ToDisplayString() { if (CreateNew) return Constants.CREATE_NEW_LABEL; - + return $"{VpcId}{(IsDefault ? Constants.DEFAULT_LABEL : "")}"; } } diff --git a/src/AWS.Deploy.CLI/UserInputConfiguration.cs b/src/AWS.Deploy.CLI/UserInputConfiguration.cs index 8cf8c9d62..97ce1e8c5 100644 --- a/src/AWS.Deploy.CLI/UserInputConfiguration.cs +++ b/src/AWS.Deploy.CLI/UserInputConfiguration.cs @@ -27,7 +27,7 @@ public class UserInputConfiguration /// /// The current value for the option setting. /// - public object CurrentValue; + public object? CurrentValue; /// /// If true, ask for the new name @@ -61,5 +61,15 @@ public class UserInputConfiguration /// then an "Empty" option will be added to the list of valid options. /// public bool EmptyOption { get; set; } + + public UserInputConfiguration( + Func displaySelector, + Func defaultSelector, + string defaultNewName = "") + { + DisplaySelector = displaySelector; + DefaultSelector = defaultSelector; + DefaultNewName = defaultNewName; + } } } diff --git a/src/AWS.Deploy.CLI/UserResponse.cs b/src/AWS.Deploy.CLI/UserResponse.cs index 662362cdb..d15f9c033 100644 --- a/src/AWS.Deploy.CLI/UserResponse.cs +++ b/src/AWS.Deploy.CLI/UserResponse.cs @@ -19,12 +19,12 @@ public class UserResponse /// If set, the user has chosen to create a new resource with a custom name. /// must be true. /// - public string NewName { get; set; } + public string? NewName { get; set; } /// /// If set, customer has chosen an existing option from the list of options shown. /// - public T SelectedOption { get; set; } + public T? SelectedOption { get; set; } /// /// If set, customer has chosen empty option. diff --git a/src/AWS.Deploy.CLI/Utilities/CommandLineWrapper.cs b/src/AWS.Deploy.CLI/Utilities/CommandLineWrapper.cs index e13cee394..b2ad75bea 100644 --- a/src/AWS.Deploy.CLI/Utilities/CommandLineWrapper.cs +++ b/src/AWS.Deploy.CLI/Utilities/CommandLineWrapper.cs @@ -19,7 +19,7 @@ public class CommandLineWrapper : ICommandLineWrapper { private readonly IOrchestratorInteractiveService _interactiveService; private readonly bool _useSeparateWindow; - private Action _processStartInfoAction; + private Action? _processStartInfoAction; public CommandLineWrapper( IOrchestratorInteractiveService interactiveService) @@ -40,9 +40,9 @@ public async Task Run( string command, string workingDirectory = "", bool streamOutputToInteractiveService = true, - Action onComplete = null, + Action? onComplete = null, bool redirectIO = true, - IDictionary environmentVariables = null, + IDictionary? environmentVariables = null, CancellationToken cancelToken = default) { StringBuilder strOutput = new StringBuilder(); @@ -119,7 +119,7 @@ public async Task Run( } } - private static void UpdateEnvironmentVariables(ProcessStartInfo processStartInfo, IDictionary environmentVariables) + private static void UpdateEnvironmentVariables(ProcessStartInfo processStartInfo, IDictionary? environmentVariables) { if (environmentVariables == null) { @@ -162,7 +162,7 @@ private static string BuildAWSExecutionEnvValue(ProcessStartInfo processStartInf return awsExecutionEnvBuilder.ToString(); } - public void ConfigureProcess(Action processStartInfoAction) + public void ConfigureProcess(Action? processStartInfoAction) { _processStartInfoAction = processStartInfoAction; } @@ -170,10 +170,10 @@ public void ConfigureProcess(Action processStartInfoAction) private string GetSystemShell() { if (TryGetEnvironmentVariable("COMSPEC", out var comspec)) - return comspec; + return comspec!; if (TryGetEnvironmentVariable("SHELL", out var shell)) - return shell; + return shell!; // fall back to defaults return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) @@ -181,7 +181,7 @@ private string GetSystemShell() : "/bin/sh"; } - private bool TryGetEnvironmentVariable(string variable, out string value) + private bool TryGetEnvironmentVariable(string variable, out string? value) { value = Environment.GetEnvironmentVariable(variable); diff --git a/src/AWS.Deploy.Common/CloudApplication.cs b/src/AWS.Deploy.Common/CloudApplication.cs index 1771dc8ce..0cc55ccff 100644 --- a/src/AWS.Deploy.Common/CloudApplication.cs +++ b/src/AWS.Deploy.Common/CloudApplication.cs @@ -32,5 +32,11 @@ public class CloudApplication /// /// public override string ToString() => Name; + + public CloudApplication(string name, string recipeId) + { + Name = name; + RecipeId = recipeId; + } } } diff --git a/src/AWS.Deploy.Common/CloudApplicationMetadata.cs b/src/AWS.Deploy.Common/CloudApplicationMetadata.cs index 488f5d7b2..d611f6f8e 100644 --- a/src/AWS.Deploy.Common/CloudApplicationMetadata.cs +++ b/src/AWS.Deploy.Common/CloudApplicationMetadata.cs @@ -26,5 +26,11 @@ public class CloudApplicationMetadata /// All of the settings configured for the deployment of the application with the recipe. /// public IDictionary Settings { get; set; } = new Dictionary(); + + public CloudApplicationMetadata(string recipeId, string recipeVersion) + { + RecipeId = recipeId; + RecipeVersion = recipeVersion; + } } } diff --git a/src/AWS.Deploy.Common/DefaultAWSClientFactory.cs b/src/AWS.Deploy.Common/DefaultAWSClientFactory.cs index 7f4b72be8..4312363e5 100644 --- a/src/AWS.Deploy.Common/DefaultAWSClientFactory.cs +++ b/src/AWS.Deploy.Common/DefaultAWSClientFactory.cs @@ -10,7 +10,7 @@ namespace AWS.Deploy.Common { public class DefaultAWSClientFactory : IAWSClientFactory { - private Action _awsOptionsAction; + private Action? _awsOptionsAction; public void ConfigureAWSOptions(Action awsOptionsAction) { diff --git a/src/AWS.Deploy.Common/DeploymentBundles/DeploymentBundleDefinition.cs b/src/AWS.Deploy.Common/DeploymentBundles/DeploymentBundleDefinition.cs index f8a662fc1..ec36d59f0 100644 --- a/src/AWS.Deploy.Common/DeploymentBundles/DeploymentBundleDefinition.cs +++ b/src/AWS.Deploy.Common/DeploymentBundles/DeploymentBundleDefinition.cs @@ -17,5 +17,11 @@ public class DeploymentBundleDefinition public DeploymentBundleTypes Type { get; set; } public List Parameters { get; set; } + + public DeploymentBundleDefinition(DeploymentBundleTypes type, List parameters) + { + Type = type; + Parameters = parameters; + } } } diff --git a/src/AWS.Deploy.Common/Exceptions.cs b/src/AWS.Deploy.Common/Exceptions.cs index b724ab96c..577845bdd 100644 --- a/src/AWS.Deploy.Common/Exceptions.cs +++ b/src/AWS.Deploy.Common/Exceptions.cs @@ -24,7 +24,7 @@ public ProjectFileNotFoundException(string projectPath) [AWSDeploymentExpectedException] public class InvalidCliArgumentException : Exception { - public InvalidCliArgumentException(string message, Exception innerException = null) : base(message, innerException) { } + public InvalidCliArgumentException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -33,7 +33,16 @@ public InvalidCliArgumentException(string message, Exception innerException = nu [AWSDeploymentExpectedException] public class InvalidRecipeDefinitionException : Exception { - public InvalidRecipeDefinitionException(string message, Exception innerException = null) : base(message, innerException) { } + public InvalidRecipeDefinitionException(string message, Exception? innerException = null) : base(message, innerException) { } + } + + /// + /// Throw if the user attempts to deploy a but the project definition is invalid + /// + [AWSDeploymentExpectedException] + public class InvalidProjectDefinitionException : Exception + { + public InvalidProjectDefinitionException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -44,7 +53,7 @@ public InvalidRecipeDefinitionException(string message, Exception innerException [AWSDeploymentExpectedException] public class MissingNodeJsException : Exception { - public MissingNodeJsException(string message, Exception innerException = null) : base(message, innerException) { } + public MissingNodeJsException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -55,7 +64,7 @@ public MissingNodeJsException(string message, Exception innerException = null) : [AWSDeploymentExpectedException] public class MissingDockerException : Exception { - public MissingDockerException(string message, Exception innerException = null) : base(message, innerException) { } + public MissingDockerException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -66,7 +75,7 @@ public MissingDockerException(string message, Exception innerException = null) : [AWSDeploymentExpectedException] public class DockerContainerTypeException : Exception { - public DockerContainerTypeException(string message, Exception innerException = null) : base(message, innerException) { } + public DockerContainerTypeException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -76,7 +85,7 @@ public DockerContainerTypeException(string message, Exception innerException = n [AWSDeploymentExpectedException] public class FailedToGenerateAnyRecommendations : Exception { - public FailedToGenerateAnyRecommendations(string message, Exception innerException = null) : base(message, innerException) { } + public FailedToGenerateAnyRecommendations(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -86,7 +95,7 @@ public FailedToGenerateAnyRecommendations(string message, Exception innerExcepti [AWSDeploymentExpectedException] public class InvalidOverrideValueException : Exception { - public InvalidOverrideValueException(string message, Exception innerException = null) : base(message, innerException) { } + public InvalidOverrideValueException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -95,9 +104,9 @@ public InvalidOverrideValueException(string message, Exception innerException = [AWSDeploymentExpectedException] public class ParsingExistingCloudApplicationMetadataException : Exception { - public ParsingExistingCloudApplicationMetadataException(string message, Exception innerException = null) : base(message, innerException) { } + public ParsingExistingCloudApplicationMetadataException(string message, Exception? innerException = null) : base(message, innerException) { } } - + /// /// Throw if Orchestrator is unable to create /// the deployment bundle. @@ -105,7 +114,16 @@ public ParsingExistingCloudApplicationMetadataException(string message, Exceptio [AWSDeploymentExpectedException] public class FailedToCreateDeploymentBundleException : Exception { - public FailedToCreateDeploymentBundleException(string message, Exception innerException = null) : base(message, innerException) { } + public FailedToCreateDeploymentBundleException(string message, Exception? innerException = null) : base(message, innerException) { } + } + + /// + /// Throw if Option Setting Item does not exist + /// + [AWSDeploymentExpectedException] + public class OptionSettingItemDoesNotExistException : Exception + { + public OptionSettingItemDoesNotExistException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -130,7 +148,7 @@ public static bool IsAWSDeploymentExpectedException(this Exception e) => null != e?.GetType() .GetCustomAttribute(typeof(AWSDeploymentExpectedExceptionAttribute), inherit: true); - public static string PrettyPrint(this Exception e) + public static string PrettyPrint(this Exception? e) { if (null == e) return string.Empty; diff --git a/src/AWS.Deploy.Common/IO/DirectoryManager.cs b/src/AWS.Deploy.Common/IO/DirectoryManager.cs index 31de6b032..c2572a31f 100644 --- a/src/AWS.Deploy.Common/IO/DirectoryManager.cs +++ b/src/AWS.Deploy.Common/IO/DirectoryManager.cs @@ -9,15 +9,15 @@ public interface IDirectoryManager { DirectoryInfo CreateDirectory(string path); bool Exists(string path); - string[] GetFiles(string projectPath, string searchPattern = null); + string[] GetFiles(string projectPath, string? searchPattern = null); } public class DirectoryManager : IDirectoryManager { public DirectoryInfo CreateDirectory(string path) => Directory.CreateDirectory(path); - + public bool Exists(string path) => Directory.Exists(path); - public string[] GetFiles(string path, string searchPattern = null) => Directory.GetFiles(path, searchPattern ?? "*"); + public string[] GetFiles(string path, string? searchPattern = null) => Directory.GetFiles(path, searchPattern ?? "*"); } } diff --git a/src/AWS.Deploy.Common/IUserInputOption.cs b/src/AWS.Deploy.Common/IUserInputOption.cs index de86a504b..1bfc74548 100644 --- a/src/AWS.Deploy.Common/IUserInputOption.cs +++ b/src/AWS.Deploy.Common/IUserInputOption.cs @@ -6,6 +6,6 @@ namespace AWS.Deploy.Common public interface IUserInputOption { string Name { get; } - string Description { get; } + string? Description { get; } } } diff --git a/src/AWS.Deploy.Common/ProjectDefinition.cs b/src/AWS.Deploy.Common/ProjectDefinition.cs index 23444b130..f853bcec8 100644 --- a/src/AWS.Deploy.Common/ProjectDefinition.cs +++ b/src/AWS.Deploy.Common/ProjectDefinition.cs @@ -28,7 +28,7 @@ public class ProjectDefinition /// The Solution file path of the project. /// public string ProjectSolutionPath { get;set; } - + /// /// Value of the Sdk property of the root project element in a .csproj /// @@ -37,26 +37,42 @@ public class ProjectDefinition /// /// Value of the TargetFramework property of the project /// - public string TargetFramework { get; set; } + public string? TargetFramework { get; set; } /// /// Value of the AssemblyName property of the project /// - public string AssemblyName { get; set; } + public string? AssemblyName { get; set; } /// /// True if we found a docker file corresponding to the .csproj /// public bool HasDockerFile => CheckIfDockerFileExists(ProjectPath); - public string GetMSPropertyValue(string propertyName) + public ProjectDefinition( + XmlDocument contents, + string projectPath, + string projectSolutionPath, + string sdkType) + { + Contents = contents; + ProjectPath = projectPath; + ProjectSolutionPath = projectSolutionPath; + SdkType = sdkType; + } + + public string? GetMSPropertyValue(string? propertyName) { + if (string.IsNullOrEmpty(propertyName)) + return null; var propertyValue = Contents.SelectSingleNode($"//PropertyGroup/{propertyName}")?.InnerText; return propertyValue; } - public string GetPackageReferenceVersion(string packageName) + public string? GetPackageReferenceVersion(string? packageName) { + if (string.IsNullOrEmpty(packageName)) + return null; var packageReference = Contents.SelectSingleNode($"//ItemGroup/PackageReference[@Include='{packageName}']") as XmlElement; return packageReference?.GetAttribute("Version"); } diff --git a/src/AWS.Deploy.Common/ProjectDefinitionParser.cs b/src/AWS.Deploy.Common/ProjectDefinitionParser.cs index 283b29892..534cdddb4 100644 --- a/src/AWS.Deploy.Common/ProjectDefinitionParser.cs +++ b/src/AWS.Deploy.Common/ProjectDefinitionParser.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.IO; using System.Linq; using System.Threading.Tasks; @@ -59,22 +60,21 @@ public async Task Parse(string projectPath) var xmlProjectFile = new XmlDocument(); xmlProjectFile.LoadXml(await _fileManager.ReadAllTextAsync(projectPath)); - var projectDefinition = new ProjectDefinition - { - Contents = xmlProjectFile, - ProjectPath = projectPath, - ProjectSolutionPath = await GetProjectSolutionFile(projectPath) - }; - + var projectDefinition = new ProjectDefinition( + xmlProjectFile, + projectPath, + await GetProjectSolutionFile(projectPath), + xmlProjectFile.DocumentElement?.Attributes["Sdk"]?.Value ?? + throw new InvalidProjectDefinitionException( + "The project file that is being referenced does not contain and 'Sdk' attribute.") + ); + var targetFramework = xmlProjectFile.GetElementsByTagName("TargetFramework"); if (targetFramework.Count > 0) { projectDefinition.TargetFramework = targetFramework[0].InnerText; } - - projectDefinition.SdkType = xmlProjectFile.DocumentElement.Attributes["Sdk"]?.Value; - var assemblyName = xmlProjectFile.GetElementsByTagName("AssemblyName"); if (assemblyName.Count > 0) { @@ -91,7 +91,7 @@ public async Task Parse(string projectPath) private async Task GetProjectSolutionFile(string projectPath) { var projectDirectory = Directory.GetParent(projectPath); - + while (projectDirectory != null) { var files = _directoryManager.GetFiles(projectDirectory.FullName, "*.sln"); diff --git a/src/AWS.Deploy.Common/Recipes/AvailableRuleItem.cs b/src/AWS.Deploy.Common/Recipes/AvailableRuleItem.cs deleted file mode 100644 index bd418f2ef..000000000 --- a/src/AWS.Deploy.Common/Recipes/AvailableRuleItem.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -using System.Collections.Generic; - -namespace AWS.Deploy.Common.Recipes -{ - /// - /// Container for the types of rules that can be checked. - /// - public class AvailableRuleItem - { - /// - /// The value for the `Sdk` attribute of the project file. - /// An example of this is checking to see if the project is a web project by seeing if the value is "Microsoft.NET.Sdk.Web" - /// - public string SdkType { get; set; } - - /// - /// Check to see if the project has certain files. - /// An example of this is checking to see if a project has a Dockerfile - /// - public IList HasFiles { get; set; } = new List(); - - /// - /// Check to see if an specific property exists in a PropertyGroup of the project file. - /// An example of this is checking to see of the AWSProjectType property exists. - /// - public string MSPropertyExists { get; set; } - - /// - /// Checks to see if the value of a property in a PropertyGroup of the project file containers one of the allowed values. - /// An example of this is checking to see of the TargetFramework is netcoreapp3.1. - /// - public MSPropertyRule MSProperty { get; set; } - } -} diff --git a/src/AWS.Deploy.Common/Recipes/DeploymentConfirmationType.cs b/src/AWS.Deploy.Common/Recipes/DeploymentConfirmationType.cs index b5fd012cf..d16586606 100644 --- a/src/AWS.Deploy.Common/Recipes/DeploymentConfirmationType.cs +++ b/src/AWS.Deploy.Common/Recipes/DeploymentConfirmationType.cs @@ -10,5 +10,11 @@ namespace AWS.Deploy.Common.Recipes public class DeploymentConfirmationType { public string DefaultMessage { get; set; } + + public DeploymentConfirmationType( + string defaultMessage) + { + DefaultMessage = defaultMessage; + } } } diff --git a/src/AWS.Deploy.Common/Recipes/MSPropertyRule.cs b/src/AWS.Deploy.Common/Recipes/MSPropertyRule.cs deleted file mode 100644 index 589631c38..000000000 --- a/src/AWS.Deploy.Common/Recipes/MSPropertyRule.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -using System.Collections.Generic; - -namespace AWS.Deploy.Common.Recipes -{ - /// - /// Container for the MSPropertyRule conditions - /// - public class MSPropertyRule - { - /// - /// The name of the property in a PropertyGroup to check. - /// - public string Name { get; set; } - - /// - /// The list of allowed values for the property. - /// - public IList AllowedValues { get; set; } = new List(); - } -} diff --git a/src/AWS.Deploy.Common/Recipes/OptionSettingItem.ValueOverride.cs b/src/AWS.Deploy.Common/Recipes/OptionSettingItem.ValueOverride.cs index 7daf84ffc..197a82077 100644 --- a/src/AWS.Deploy.Common/Recipes/OptionSettingItem.ValueOverride.cs +++ b/src/AWS.Deploy.Common/Recipes/OptionSettingItem.ValueOverride.cs @@ -10,20 +10,16 @@ namespace AWS.Deploy.Common.Recipes /// , and methods public partial class OptionSettingItem { - private object _valueOverride; + private object? _valueOverride = null; - public T GetValue(IDictionary replacementTokens, bool ignoreDefaultValue = false, IDictionary displayableOptionSettings = null) + public T GetValue(IDictionary replacementTokens, IDictionary? displayableOptionSettings = null) { - var value = GetValue(replacementTokens, ignoreDefaultValue, displayableOptionSettings); - if (value == null) - { - return default; - } + var value = GetValue(replacementTokens, displayableOptionSettings); return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value)); } - public object GetValue(IDictionary replacementTokens, bool ignoreDefaultValue = false, IDictionary displayableOptionSettings = null) + public object GetValue(IDictionary replacementTokens, IDictionary? displayableOptionSettings = null) { if (_valueOverride != null) { @@ -35,7 +31,7 @@ public object GetValue(IDictionary replacementTokens, bool ignor var objectValue = new Dictionary(); foreach (var childOptionSetting in ChildOptionSettings) { - var childValue = childOptionSetting.GetValue(replacementTokens, ignoreDefaultValue); + var childValue = childOptionSetting.GetValue(replacementTokens); if ( displayableOptionSettings != null && @@ -45,22 +41,14 @@ public object GetValue(IDictionary replacementTokens, bool ignor continue; } - if (childValue != null) - { - objectValue[childOptionSetting.Id] = childValue; - } + objectValue[childOptionSetting.Id] = childValue; } - return objectValue.Any() ? objectValue : null; - } - - if (ignoreDefaultValue) - { - return null; + return objectValue; } if (DefaultValue == null) { - return null; + return string.Empty; } if (DefaultValue is string defaultValueString) @@ -71,7 +59,7 @@ public object GetValue(IDictionary replacementTokens, bool ignor return DefaultValue; } - public T GetDefaultValue(IDictionary replacementTokens) + public T? GetDefaultValue(IDictionary replacementTokens) { var value = GetDefaultValue(replacementTokens); if (value == null) @@ -82,7 +70,7 @@ public T GetDefaultValue(IDictionary replacementTokens) return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value)); } - public object GetDefaultValue(IDictionary replacementTokens) + public object? GetDefaultValue(IDictionary replacementTokens) { if (DefaultValue == null) { diff --git a/src/AWS.Deploy.Common/Recipes/OptionSettingItem.cs b/src/AWS.Deploy.Common/Recipes/OptionSettingItem.cs index 26defbc6f..b177c4e50 100644 --- a/src/AWS.Deploy.Common/Recipes/OptionSettingItem.cs +++ b/src/AWS.Deploy.Common/Recipes/OptionSettingItem.cs @@ -22,7 +22,7 @@ public partial class OptionSettingItem /// The id of the parent option setting. This is used for tooling that wants to look up the existing resources for a setting based on the TypeHint but needs /// to know the parent AWS resource. For example if listing the available Beanstalk environments the listing should be for the environments of the Beanstalk application. /// - public string ParentSettingId { get; set; } + public string? ParentSettingId { get; set; } /// /// The display friendly name of the setting. @@ -49,7 +49,7 @@ public partial class OptionSettingItem /// /// The default value used for the recipe if the user doesn't override the value. /// - public object DefaultValue { get; set; } + public object? DefaultValue { get; set; } /// /// UI can use this to reduce the amount of settings to show to the user when confirming the recommendation. This can make it so the user sees only the most important settings @@ -89,12 +89,19 @@ public partial class OptionSettingItem /// public Dictionary TypeHintData { get; set; } = new (); + public OptionSettingItem(string id, string name, string description) + { + Id = id; + Name = name; + Description = description; + } + /// /// Helper method to get strongly type . /// /// Type of the type hint data /// Returns strongly type type hint data. Returns default value if is empty. - public T GetTypeHintData() + public T? GetTypeHintData() { if (!TypeHintData.Any()) { diff --git a/src/AWS.Deploy.Common/Recipes/PropertyDependency.cs b/src/AWS.Deploy.Common/Recipes/PropertyDependency.cs index 0a2595d6b..99ed68d3f 100644 --- a/src/AWS.Deploy.Common/Recipes/PropertyDependency.cs +++ b/src/AWS.Deploy.Common/Recipes/PropertyDependency.cs @@ -10,5 +10,13 @@ public class PropertyDependency { public string Id { get; set; } public object Value { get; set; } + + public PropertyDependency( + string id, + object value) + { + Id = id; + Value = value; + } } } diff --git a/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs b/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs index 840b5540a..d55dc0c02 100644 --- a/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs +++ b/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs @@ -34,7 +34,7 @@ public class RecipeDefinition /// A runtime property set when the recipe definition is loaded to the location of the definition. This property is used to find /// other assets like the CDK template project in relation to the recipe definition. /// - public string RecipePath { get; set; } + public string? RecipePath { get; set; } /// /// The name of the AWS service the recipe deploys to. This is used for display purposes back to the user to inform then what AWS service the project @@ -45,7 +45,7 @@ public class RecipeDefinition /// /// Confirmation messages to display to the user before performing deployment. /// - public DeploymentConfirmationType DeploymentConfirmation { get; set; } + public DeploymentConfirmationType? DeploymentConfirmation { get; set; } /// /// The type of deployment to perform. This controls what other tool to use to perform the deployment. For example a value of `CdkProject` means that CDK should @@ -83,6 +83,27 @@ public class RecipeDefinition /// public int RecipePriority { get; set; } + public RecipeDefinition( + string id, + string version, + string name, + DeploymentTypes deploymentType, + DeploymentBundleTypes deploymentBundle, + string cdkProjectTemplate, + string cdkProjectTemplateId, + string description, + string targetService) + { + Id = id; + Version = version; + Name = name; + DeploymentType = deploymentType; + DeploymentBundle = deploymentBundle; + CdkProjectTemplate = cdkProjectTemplate; + CdkProjectTemplateId = cdkProjectTemplateId; + Description = description; + TargetService = targetService; + } public override string ToString() { return $"{Name} ({Id})"; diff --git a/src/AWS.Deploy.Common/Recipes/RecommendationRuleItem.cs b/src/AWS.Deploy.Common/Recipes/RecommendationRuleItem.cs index 30bef55ef..0e720a1d0 100644 --- a/src/AWS.Deploy.Common/Recipes/RecommendationRuleItem.cs +++ b/src/AWS.Deploy.Common/Recipes/RecommendationRuleItem.cs @@ -19,6 +19,6 @@ public class RecommendationRuleItem /// The effect of the rule based on whether the test pass or not. If the effect is not defined /// the effect is the Include option matches the result of the test passing. /// - public RuleEffect Effect { get; set; } + public RuleEffect? Effect { get; set; } } } diff --git a/src/AWS.Deploy.Common/Recipes/RuleCondition.cs b/src/AWS.Deploy.Common/Recipes/RuleCondition.cs index 3a104100e..717a563f3 100644 --- a/src/AWS.Deploy.Common/Recipes/RuleCondition.cs +++ b/src/AWS.Deploy.Common/Recipes/RuleCondition.cs @@ -13,12 +13,12 @@ public class RuleCondition /// /// The value to check for. Used by the MSProjectSdkAttribute test /// - public string Value { get; set; } + public string? Value { get; set; } /// /// The name of the ms property for tests. Used by the MSProperty and MSPropertyExists /// - public string PropertyName { get; set; } + public string? PropertyName { get; set; } /// /// The list of allowed values to check for. Used by the MSProperty test @@ -28,11 +28,11 @@ public class RuleCondition /// /// The name of file to search for. Used by the FileExists test. /// - public string FileName { get; set; } + public string? FileName { get; set; } /// /// The name of a NuGet package for tests. Used to see if projects are taking dependencies on specific packages. /// - public string NuGetPackageName { get; set; } + public string? NuGetPackageName { get; set; } } } diff --git a/src/AWS.Deploy.Common/Recipes/RuleEffect.cs b/src/AWS.Deploy.Common/Recipes/RuleEffect.cs index 196860de1..fdf580837 100644 --- a/src/AWS.Deploy.Common/Recipes/RuleEffect.cs +++ b/src/AWS.Deploy.Common/Recipes/RuleEffect.cs @@ -11,12 +11,12 @@ public class RuleEffect /// /// The effects to run if all the test pass. /// - public EffectOptions Pass { get; set; } + public EffectOptions? Pass { get; set; } /// /// The effects to run if all the test fail. /// - public EffectOptions Fail { get; set; } + public EffectOptions? Fail { get; set; } } } diff --git a/src/AWS.Deploy.Common/Recipes/RuleTest.cs b/src/AWS.Deploy.Common/Recipes/RuleTest.cs index ee6230763..c050ca794 100644 --- a/src/AWS.Deploy.Common/Recipes/RuleTest.cs +++ b/src/AWS.Deploy.Common/Recipes/RuleTest.cs @@ -17,5 +17,13 @@ public class RuleTest /// The conditions for the tests /// public RuleCondition Condition { get; set; } + + public RuleTest( + string type, + RuleCondition condition) + { + Type = type; + Condition = condition; + } } } diff --git a/src/AWS.Deploy.Common/Recommendation.cs b/src/AWS.Deploy.Common/Recommendation.cs index 4c166305a..b44a6022c 100644 --- a/src/AWS.Deploy.Common/Recommendation.cs +++ b/src/AWS.Deploy.Common/Recommendation.cs @@ -57,7 +57,7 @@ public void OverrideProjectName(string name) _replacementTokens[REPLACE_TOKEN_PROJECT_NAME] = name; } - public void ApplyPreviousSettings(IDictionary previousSettings) + public void ApplyPreviousSettings(IDictionary? previousSettings) { if (previousSettings == null) return; @@ -80,17 +80,20 @@ private void ApplyPreviousSettings(IEnumerable optionSettings /// /// Interactively traverses given json path and returns target option setting. - /// Returns null if there is no that matches /> + /// Throws exception if there is no that matches /> /// /// /// Dot (.) separated key values string pointing to an option setting. /// Read more /// - /// Option setting at the json path. Returns null if, there doesn't exist an option setting. - public OptionSettingItem GetOptionSetting(string jsonPath) + /// Option setting at the json path. Throws if there doesn't exist an option setting. + public OptionSettingItem GetOptionSetting(string? jsonPath) { + if (string.IsNullOrEmpty(jsonPath)) + throw new OptionSettingItemDoesNotExistException("The Option Setting Item you are looking for does not exist."); + var ids = jsonPath.Split('.'); - OptionSettingItem optionSetting = null; + OptionSettingItem? optionSetting = null; foreach (var id in ids) { @@ -98,14 +101,14 @@ public OptionSettingItem GetOptionSetting(string jsonPath) optionSetting = optionSettings.FirstOrDefault(os => os.Id.Equals(id)); if (optionSetting == null) { - return null; + throw new OptionSettingItemDoesNotExistException("The Option Setting Item you are looking for does not exist."); } } - return optionSetting; + return optionSetting!; } - public T GetOptionSettingValue(OptionSettingItem optionSetting, bool ignoreDefaultValue = false) + public T GetOptionSettingValue(OptionSettingItem optionSetting) { var displayableOptionSettings = new Dictionary(); if (optionSetting.Type == OptionSettingValueType.Object) @@ -115,10 +118,10 @@ public T GetOptionSettingValue(OptionSettingItem optionSetting, bool ignoreDe displayableOptionSettings.Add(childOptionSetting.Id, IsOptionSettingDisplayable(childOptionSetting)); } } - return optionSetting.GetValue(_replacementTokens, ignoreDefaultValue, displayableOptionSettings); + return optionSetting.GetValue(_replacementTokens, displayableOptionSettings); } - public object GetOptionSettingValue(OptionSettingItem optionSetting, bool ignoreDefaultValue = false) + public object GetOptionSettingValue(OptionSettingItem optionSetting) { var displayableOptionSettings = new Dictionary(); if (optionSetting.Type == OptionSettingValueType.Object) @@ -128,15 +131,15 @@ public object GetOptionSettingValue(OptionSettingItem optionSetting, bool ignore displayableOptionSettings.Add(childOptionSetting.Id, IsOptionSettingDisplayable(childOptionSetting)); } } - return optionSetting.GetValue(_replacementTokens, ignoreDefaultValue, displayableOptionSettings); + return optionSetting.GetValue(_replacementTokens, displayableOptionSettings); } - public T GetOptionSettingDefaultValue(OptionSettingItem optionSetting) + public T? GetOptionSettingDefaultValue(OptionSettingItem optionSetting) { return optionSetting.GetDefaultValue(_replacementTokens); } - public object GetOptionSettingDefaultValue(OptionSettingItem optionSetting) + public object? GetOptionSettingDefaultValue(OptionSettingItem optionSetting) { return optionSetting.GetDefaultValue(_replacementTokens); } @@ -157,7 +160,11 @@ public bool IsOptionSettingDisplayable(OptionSettingItem optionSetting) foreach (var dependency in optionSetting.DependsOn) { var dependsOnOptionSetting = GetOptionSetting(dependency.Id); - if (dependsOnOptionSetting != null && !GetOptionSettingValue(dependsOnOptionSetting).Equals(dependency.Value)) + var dependsOnOptionSettingValue = GetOptionSettingValue(dependsOnOptionSetting); + if ( + dependsOnOptionSetting != null && + dependsOnOptionSettingValue != null && + !dependsOnOptionSettingValue.Equals(dependency.Value)) { return false; } diff --git a/src/AWS.Deploy.Common/TypeHintData/IAMRoleTypeHintData.cs b/src/AWS.Deploy.Common/TypeHintData/IAMRoleTypeHintData.cs index e233c50b5..cbf25c1d8 100644 --- a/src/AWS.Deploy.Common/TypeHintData/IAMRoleTypeHintData.cs +++ b/src/AWS.Deploy.Common/TypeHintData/IAMRoleTypeHintData.cs @@ -14,5 +14,10 @@ public class IAMRoleTypeHintData /// ServicePrincipal to filter IAM roles. /// public string ServicePrincipal { get; set; } + + public IAMRoleTypeHintData(string servicePrincipal) + { + ServicePrincipal = servicePrincipal; + } } } diff --git a/src/AWS.Deploy.Common/UserInputOption.cs b/src/AWS.Deploy.Common/UserInputOption.cs index a3a45737c..357fc4605 100644 --- a/src/AWS.Deploy.Common/UserInputOption.cs +++ b/src/AWS.Deploy.Common/UserInputOption.cs @@ -12,6 +12,6 @@ public UserInputOption(string value) public string Name { get; set; } - public string Description { get; set; } + public string? Description { get; set; } } } diff --git a/src/AWS.Deploy.DockerEngine/DockerEngine.cs b/src/AWS.Deploy.DockerEngine/DockerEngine.cs index f99dd230d..4c360adfb 100644 --- a/src/AWS.Deploy.DockerEngine/DockerEngine.cs +++ b/src/AWS.Deploy.DockerEngine/DockerEngine.cs @@ -57,7 +57,7 @@ public void GenerateDockerFile() } var dockerFile = new DockerFile(imageMapping, projectFileName, _project.AssemblyName); - var projectDirectory = Path.GetDirectoryName(_projectPath); + var projectDirectory = Path.GetDirectoryName(_projectPath) ?? ""; var projectList = GetProjectList(); dockerFile.WriteDockerFile(projectDirectory, projectList); } @@ -65,7 +65,7 @@ public void GenerateDockerFile() /// /// Retrieves a list of projects from a solution file /// - private List GetProjectsFromSolutionFile(string solutionFile) + private List? GetProjectsFromSolutionFile(string solutionFile) { var projectFileName = Path.GetFileName(_projectPath); if (string.IsNullOrWhiteSpace(solutionFile) || @@ -94,7 +94,7 @@ private List GetProjectsFromSolutionFile(string solutionFile) /// /// Finds the project solution file (if one exists) and retrieves a list of projects that are part of one solution /// - private List GetProjectList() + private List? GetProjectList() { var projectDirectory = Directory.GetParent(_projectPath); @@ -142,20 +142,20 @@ public void DetermineDockerExecutionDirectory(Recommendation recommendation) if (string.IsNullOrEmpty(recommendation.DeploymentBundle.DockerExecutionDirectory)) { var projectFilename = Path.GetFileName(recommendation.ProjectPath); - var dockerFilePath = Path.Combine(Path.GetDirectoryName(recommendation.ProjectPath), "Dockerfile"); + var dockerFilePath = Path.Combine(Path.GetDirectoryName(recommendation.ProjectPath) ?? "", "Dockerfile"); if (File.Exists(dockerFilePath)) { using (var stream = File.OpenRead(dockerFilePath)) using (var reader = new StreamReader(stream)) { - string line; + string? line; while ((line = reader.ReadLine()) != null) { var noSpaceLine = line.Replace(" ", ""); if (noSpaceLine.StartsWith("COPY") && (noSpaceLine.EndsWith(".sln./") || (projectFilename != null && noSpaceLine.Contains("/" + projectFilename)))) { - recommendation.DeploymentBundle.DockerExecutionDirectory = Path.GetDirectoryName(recommendation.ProjectDefinition.ProjectSolutionPath); + recommendation.DeploymentBundle.DockerExecutionDirectory = Path.GetDirectoryName(recommendation.ProjectDefinition.ProjectSolutionPath) ?? ""; } } } diff --git a/src/AWS.Deploy.DockerEngine/DockerFile.cs b/src/AWS.Deploy.DockerEngine/DockerFile.cs index e24bb3e20..3e77aa651 100644 --- a/src/AWS.Deploy.DockerEngine/DockerFile.cs +++ b/src/AWS.Deploy.DockerEngine/DockerFile.cs @@ -19,7 +19,7 @@ public class DockerFile private readonly string _projectName; private readonly string _assemblyName; - public DockerFile(ImageMapping imageMapping, string projectName, string assemblyName) + public DockerFile(ImageMapping imageMapping, string projectName, string? assemblyName) { if (imageMapping == null) { @@ -44,7 +44,7 @@ public DockerFile(ImageMapping imageMapping, string projectName, string assembly /// /// Writes a docker file based on project information /// - public void WriteDockerFile(string projectDirectory, List projectList) + public void WriteDockerFile(string projectDirectory, List? projectList) { var dockerFileTemplate = ProjectUtilities.ReadTemplate(); var projects = ""; diff --git a/src/AWS.Deploy.DockerEngine/ImageDefinition.cs b/src/AWS.Deploy.DockerEngine/ImageDefinition.cs index f405677c2..6d2b1133f 100644 --- a/src/AWS.Deploy.DockerEngine/ImageDefinition.cs +++ b/src/AWS.Deploy.DockerEngine/ImageDefinition.cs @@ -10,6 +10,12 @@ public class ImageDefinition public string SdkType { get; set; } public List ImageMapping { get; set; } + public ImageDefinition(string sdkType, List imageMapping) + { + SdkType = sdkType; + ImageMapping = imageMapping; + } + public override string ToString() { return $"Image Definition for {SdkType}"; @@ -22,6 +28,13 @@ public class ImageMapping public string BaseImage { get; set; } public string BuildImage { get; set; } + public ImageMapping(string targetFramework, string baseImage, string buildImage) + { + TargetFramework = targetFramework; + BaseImage = baseImage; + BuildImage = buildImage; + } + public override string ToString() { return $"Image Mapping for {TargetFramework}"; diff --git a/src/AWS.Deploy.Orchestration/CDK/CDKInstaller.cs b/src/AWS.Deploy.Orchestration/CDK/CDKInstaller.cs index 115c223e6..662c47b66 100644 --- a/src/AWS.Deploy.Orchestration/CDK/CDKInstaller.cs +++ b/src/AWS.Deploy.Orchestration/CDK/CDKInstaller.cs @@ -80,7 +80,7 @@ private async Task> GetVersion(string workingDirectory, bo * C:\Users\user\AppData\Roaming\npm * `-- aws-cdk@0.0.0 */ - var standardOut = result.StandardOut; + var standardOut = result.StandardOut ?? ""; var lines = standardOut.Split('\n'); // Environment.NewLine doesn't work here. if (lines.Length < 2) { diff --git a/src/AWS.Deploy.Orchestration/CDK/TryGetResult.cs b/src/AWS.Deploy.Orchestration/CDK/TryGetResult.cs index c50f56b9b..4a7f722e3 100644 --- a/src/AWS.Deploy.Orchestration/CDK/TryGetResult.cs +++ b/src/AWS.Deploy.Orchestration/CDK/TryGetResult.cs @@ -14,9 +14,9 @@ public class TryGetResult { public bool Success { get; } - public TResult Result { get; } + public TResult? Result { get; } - public TryGetResult(TResult result, bool success) + public TryGetResult(TResult? result, bool success) { Result = result; Success = success; diff --git a/src/AWS.Deploy.Orchestration/CdkAppSettingsSerializer.cs b/src/AWS.Deploy.Orchestration/CdkAppSettingsSerializer.cs index 83a50e4e6..1e6841c4d 100644 --- a/src/AWS.Deploy.Orchestration/CdkAppSettingsSerializer.cs +++ b/src/AWS.Deploy.Orchestration/CdkAppSettingsSerializer.cs @@ -14,24 +14,27 @@ public class CdkAppSettingsSerializer public string Build(CloudApplication cloudApplication, Recommendation recommendation) { // General Settings - var appSettingsContainer = new RecipeConfiguration>() + var appSettingsContainer = new RecipeConfiguration>( + cloudApplication.StackName, + new FileInfo(recommendation.ProjectPath).Directory.FullName, + recommendation.Recipe.Id, + recommendation.Recipe.Version, + new () + ) { - StackName = cloudApplication.StackName, - ProjectPath = new FileInfo(recommendation.ProjectPath).Directory.FullName, ECRRepositoryName = recommendation.DeploymentBundle.ECRRepositoryName ?? "", ECRImageTag = recommendation.DeploymentBundle.ECRImageTag ?? "", DotnetPublishZipPath = recommendation.DeploymentBundle.DotnetPublishZipPath ?? "", - DotnetPublishOutputDirectory = recommendation.DeploymentBundle.DotnetPublishOutputDirectory ?? "", - Settings = new Dictionary() + DotnetPublishOutputDirectory = recommendation.DeploymentBundle.DotnetPublishOutputDirectory ?? "" }; - appSettingsContainer.RecipeId = recommendation.Recipe.Id; - appSettingsContainer.RecipeVersion = recommendation.Recipe.Version; - // Option Settings foreach (var optionSetting in recommendation.Recipe.OptionSettings) { - appSettingsContainer.Settings[optionSetting.Id] = recommendation.GetOptionSettingValue(optionSetting); + var optionSettingValue = recommendation.GetOptionSettingValue(optionSetting); + + if (optionSettingValue != null) + appSettingsContainer.Settings[optionSetting.Id] = optionSettingValue; } return JsonConvert.SerializeObject(appSettingsContainer, Formatting.Indented); diff --git a/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs b/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs index 97d8170da..0114a8f21 100644 --- a/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs +++ b/src/AWS.Deploy.Orchestration/Data/AWSResourceQueryer.cs @@ -26,10 +26,10 @@ public interface IAWSResourceQueryer { Task> ListOfECSClusters(); Task> ListOfElasticBeanstalkApplications(); - Task> ListOfElasticBeanstalkEnvironments(string applicationName); + Task> ListOfElasticBeanstalkEnvironments(string? applicationName); Task> ListOfEC2KeyPairs(); Task CreateEC2KeyPair(string keyName, string saveLocation); - Task> ListOfIAMRoles(string servicePrincipal); + Task> ListOfIAMRoles(string? servicePrincipal); Task> GetListOfVpcs(); Task> GetElasticBeanstalkPlatformArns(); Task GetLatestElasticBeanstalkPlatformArn(); @@ -73,10 +73,14 @@ public async Task> ListOfElasticBeanstalkApplicatio return applications.Applications; } - public async Task> ListOfElasticBeanstalkEnvironments(string applicationName) + public async Task> ListOfElasticBeanstalkEnvironments(string? applicationName) { var beanstalkClient = _awsClientFactory.GetAWSClient(); var environments = new List(); + + if (string.IsNullOrEmpty(applicationName)) + return environments; + var request = new DescribeEnvironmentsRequest { ApplicationName = applicationName @@ -115,7 +119,7 @@ public async Task CreateEC2KeyPair(string keyName, string saveLocation) return response.KeyPair.KeyName; } - public async Task> ListOfIAMRoles(string servicePrincipal) + public async Task> ListOfIAMRoles(string? servicePrincipal) { var identityManagementServiceClient = _awsClientFactory.GetAWSClient(); @@ -132,9 +136,11 @@ public async Task> ListOfIAMRoles(string servicePrincipal) return roles; } - private static bool AssumeRoleServicePrincipalSelector(Role role, string servicePrincipal) + private static bool AssumeRoleServicePrincipalSelector(Role role, string? servicePrincipal) { - return !string.IsNullOrEmpty(role.AssumeRolePolicyDocument) && role.AssumeRolePolicyDocument.Contains(servicePrincipal); + return !string.IsNullOrEmpty(role.AssumeRolePolicyDocument) && + !string.IsNullOrEmpty(servicePrincipal) && + role.AssumeRolePolicyDocument.Contains(servicePrincipal); } public async Task> GetListOfVpcs() diff --git a/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs b/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs index f06a61238..790fa2021 100644 --- a/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs +++ b/src/AWS.Deploy.Orchestration/DeploymentBundleHandler.cs @@ -62,7 +62,7 @@ public async Task BuildDockerImage(CloudApplication cloudApplication, Re var result = await _commandLineWrapper.TryRunWithResult(dockerBuildCommand, dockerExecutionDirectory, redirectIO: false); if (result.ExitCode != 0) { - throw new DockerBuildFailedException(result.StandardError); + throw new DockerBuildFailedException(result.StandardError ?? ""); } return imageTag; @@ -117,7 +117,7 @@ public async Task CreateDotnetPublishZip(Recommendation recommendation) var result = await _commandLineWrapper.TryRunWithResult(publishCommand, redirectIO: false); if (result.ExitCode != 0) { - throw new DotnetPublishFailedException(result.StandardError); + throw new DotnetPublishFailedException(result.StandardError ?? ""); } var zipFilePath = $"{publishDirectoryInfo.FullName}.zip"; diff --git a/src/AWS.Deploy.Orchestration/Exceptions.cs b/src/AWS.Deploy.Orchestration/Exceptions.cs index 29c05177c..fde01b2f2 100644 --- a/src/AWS.Deploy.Orchestration/Exceptions.cs +++ b/src/AWS.Deploy.Orchestration/Exceptions.cs @@ -9,7 +9,7 @@ namespace AWS.Deploy.Orchestration [AWSDeploymentExpectedException] public class TemplateGenerationFailedException : Exception { - public TemplateGenerationFailedException(string message, Exception innerException = null) : base(message, innerException) { } + public TemplateGenerationFailedException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -18,7 +18,7 @@ public TemplateGenerationFailedException(string message, Exception innerExceptio [AWSDeploymentExpectedException] public class DefaultTemplateInstallationFailedException : Exception { - public DefaultTemplateInstallationFailedException(string message, Exception innerException = null) : base(message, innerException) { } + public DefaultTemplateInstallationFailedException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -27,7 +27,7 @@ public DefaultTemplateInstallationFailedException(string message, Exception inne [AWSDeploymentExpectedException] public class RunCommandFailedException : Exception { - public RunCommandFailedException(string message, Exception innerException = null) : base(message, innerException) { } + public RunCommandFailedException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -36,7 +36,7 @@ public RunCommandFailedException(string message, Exception innerException = null [AWSDeploymentExpectedException] public class PackageJsonFileException : Exception { - public PackageJsonFileException(string message, Exception innerException = null) : base(message, innerException) { } + public PackageJsonFileException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -45,7 +45,7 @@ public PackageJsonFileException(string message, Exception innerException = null) [AWSDeploymentExpectedException] public class DockerBuildFailedException : Exception { - public DockerBuildFailedException(string message, Exception innerException = null) : base(message, innerException) { } + public DockerBuildFailedException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -54,7 +54,7 @@ public DockerBuildFailedException(string message, Exception innerException = nul [AWSDeploymentExpectedException] public class NPMCommandFailedException : Exception { - public NPMCommandFailedException(string message, Exception innerException = null) : base(message, innerException) { } + public NPMCommandFailedException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -63,7 +63,7 @@ public NPMCommandFailedException(string message, Exception innerException = null [AWSDeploymentExpectedException] public class DockerLoginFailedException : Exception { - public DockerLoginFailedException(string message, Exception innerException = null) : base(message, innerException) { } + public DockerLoginFailedException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -72,7 +72,7 @@ public DockerLoginFailedException(string message, Exception innerException = nul [AWSDeploymentExpectedException] public class DockerTagFailedException : Exception { - public DockerTagFailedException(string message, Exception innerException = null) : base(message, innerException) { } + public DockerTagFailedException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -81,7 +81,7 @@ public DockerTagFailedException(string message, Exception innerException = null) [AWSDeploymentExpectedException] public class DockerPushFailedException : Exception { - public DockerPushFailedException(string message, Exception innerException = null) : base(message, innerException) { } + public DockerPushFailedException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -90,7 +90,7 @@ public DockerPushFailedException(string message, Exception innerException = null [AWSDeploymentExpectedException] public class NoDeploymentBundleDefinitionsFoundException : Exception { - public NoDeploymentBundleDefinitionsFoundException(string message, Exception innerException = null) : base(message, innerException) { } + public NoDeploymentBundleDefinitionsFoundException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -99,7 +99,7 @@ public NoDeploymentBundleDefinitionsFoundException(string message, Exception inn [AWSDeploymentExpectedException] public class DotnetPublishFailedException : Exception { - public DotnetPublishFailedException(string message, Exception innerException = null) : base(message, innerException) { } + public DotnetPublishFailedException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -108,7 +108,7 @@ public DotnetPublishFailedException(string message, Exception innerException = n [AWSDeploymentExpectedException] public class FailedToCreateZipFileException : Exception { - public FailedToCreateZipFileException(string message, Exception innerException = null) : base(message, innerException) { } + public FailedToCreateZipFileException(string message, Exception? innerException = null) : base(message, innerException) { } } /// @@ -117,6 +117,15 @@ public FailedToCreateZipFileException(string message, Exception innerException = [AWSDeploymentExpectedException] public class FailedToGenerateDockerFileException : Exception { - public FailedToGenerateDockerFileException(string message, Exception innerException = null) : base(message, innerException) { } + public FailedToGenerateDockerFileException(string message, Exception? innerException = null) : base(message, innerException) { } + } + + /// + /// Exception thrown if RecipePath contains an invalid path + /// + [AWSDeploymentExpectedException] + public class InvalidRecipePathException : Exception + { + public InvalidRecipePathException(string message, Exception? innerException = null) : base(message, innerException) { } } } diff --git a/src/AWS.Deploy.Orchestration/OrchestratorSession.cs b/src/AWS.Deploy.Orchestration/OrchestratorSession.cs index 623c965b1..da746061e 100644 --- a/src/AWS.Deploy.Orchestration/OrchestratorSession.cs +++ b/src/AWS.Deploy.Orchestration/OrchestratorSession.cs @@ -10,7 +10,7 @@ namespace AWS.Deploy.Orchestration public class OrchestratorSession { public ProjectDefinition ProjectDefinition { get; set; } - public string AWSProfileName { get; set; } + public string? AWSProfileName { get; set; } public AWSCredentials AWSCredentials { get; set; } public string AWSRegion { get; set; } /// @@ -19,7 +19,19 @@ public class OrchestratorSession /// /// It's safe to repeatedly await this property; evaluation will only be done once. /// - public Task SystemCapabilities { get; set; } + public Task? SystemCapabilities { get; set; } public string AWSAccountId { get; set; } + + public OrchestratorSession( + ProjectDefinition projectDefinition, + AWSCredentials awsCredentials, + string awsRegion, + string awsAccountId) + { + ProjectDefinition = projectDefinition; + AWSCredentials = awsCredentials; + AWSRegion = awsRegion; + AWSAccountId = awsAccountId; + } } } diff --git a/src/AWS.Deploy.Orchestration/PreviousDeploymentSettings.cs b/src/AWS.Deploy.Orchestration/PreviousDeploymentSettings.cs index 881ee674d..673c13d58 100644 --- a/src/AWS.Deploy.Orchestration/PreviousDeploymentSettings.cs +++ b/src/AWS.Deploy.Orchestration/PreviousDeploymentSettings.cs @@ -10,10 +10,10 @@ public class PreviousDeploymentSettings { public const string DEFAULT_FILE_NAME = "aws-netsuite-deployment.json"; - public string Profile { get; set; } - public string Region { get; set; } + public string? Profile { get; set; } + public string? Region { get; set; } - public static PreviousDeploymentSettings ReadSettings(string projectPath, string configFile) + public static PreviousDeploymentSettings ReadSettings(string projectPath, string? configFile) { var fullPath = GetFullConfigFilePath(projectPath, configFile); if (!File.Exists(fullPath)) @@ -38,7 +38,7 @@ public void SaveSettings(string filePath) File.WriteAllText(filePath, json); } - public static string GetFullConfigFilePath(string projectPath, string configFile) + public static string GetFullConfigFilePath(string projectPath, string? configFile) { var fullPath = string.IsNullOrEmpty(configFile) ? Path.Combine(projectPath, DEFAULT_FILE_NAME) : Path.Combine(projectPath, configFile); return fullPath; diff --git a/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationEngine.cs b/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationEngine.cs index d05e57e08..f0809b24f 100644 --- a/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationEngine.cs +++ b/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationEngine.cs @@ -43,7 +43,7 @@ public RecommendationEngine(IEnumerable recipeDefinitionPaths, Orchestra } } - public async Task> ComputeRecommendations(Dictionary additionalReplacements = null) + public async Task> ComputeRecommendations(Dictionary? additionalReplacements = null) { additionalReplacements ??= new Dictionary(); @@ -82,7 +82,7 @@ public async Task EvaluateRules(IList rules var availableTests = RecommendationTestFactory.LoadAvailableTests(); var results = new RulesResult {Include = true }; - foreach (var rule in rules) + foreach (var rule in rules!) { var allTestPass = true; foreach (var test in rule.Tests) @@ -92,12 +92,11 @@ public async Task EvaluateRules(IList rules throw new InvalidRecipeDefinitionException($"Invalid test type [{test.Type}] found in rule."); } - var input = new RecommendationTestInput - { - Test = test, - ProjectDefinition = _orchestratorSession.ProjectDefinition, - Session = _orchestratorSession - }; + var input = new RecommendationTestInput( + test, + _orchestratorSession.ProjectDefinition, + _orchestratorSession); + allTestPass &= await testInstance.Execute(input); if (!allTestPass) @@ -120,7 +119,7 @@ public async Task EvaluateRules(IList rules return results; } - public bool ShouldInclude(RuleEffect effect, bool testPass) + public bool ShouldInclude(RuleEffect? effect, bool testPass) { // Get either the pass or fail effect options. var effectOptions = GetEffectOptions(effect, testPass); @@ -139,7 +138,7 @@ public bool ShouldInclude(RuleEffect effect, bool testPass) return testPass; } - private EffectOptions GetEffectOptions(RuleEffect effect, bool testPass) + private EffectOptions? GetEffectOptions(RuleEffect? effect, bool testPass) { if (effect == null) { diff --git a/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationTestFactory.cs b/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationTestFactory.cs index 1621c7d20..ce5e0479f 100644 --- a/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationTestFactory.cs +++ b/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationTestFactory.cs @@ -17,6 +17,8 @@ public static IDictionary LoadAvailableTests() .GetTypes() .Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(BaseRecommendationTest))) .Select(x => Activator.CreateInstance(x) as BaseRecommendationTest) + .Where(x => x != null) + .Select(x => x!) .ToDictionary(x => x.Name); } } diff --git a/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationTestInput.cs b/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationTestInput.cs index 8c6a6e280..2390234e3 100644 --- a/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationTestInput.cs +++ b/src/AWS.Deploy.Orchestration/RecommendationEngine/RecommendationTestInput.cs @@ -26,5 +26,15 @@ public class RecommendationTestInput /// potential tests to check for AWS resources in the account being deployed to. /// public OrchestratorSession Session { get; set; } + + public RecommendationTestInput( + RuleTest test, + ProjectDefinition projectDefinition, + OrchestratorSession session) + { + Test = test; + ProjectDefinition = projectDefinition; + Session = session; + } } } diff --git a/src/AWS.Deploy.Orchestration/SystemCapabilities.cs b/src/AWS.Deploy.Orchestration/SystemCapabilities.cs index 65b5c2734..c59d67106 100644 --- a/src/AWS.Deploy.Orchestration/SystemCapabilities.cs +++ b/src/AWS.Deploy.Orchestration/SystemCapabilities.cs @@ -4,11 +4,27 @@ public class SystemCapabilities { public bool NodeJsMinVersionInstalled { get; set; } public DockerInfo DockerInfo { get; set; } + + public SystemCapabilities( + bool nodeJsMinVersionInstalled, + DockerInfo dockerInfo) + { + NodeJsMinVersionInstalled = nodeJsMinVersionInstalled; + DockerInfo = dockerInfo; + } } public class DockerInfo { public bool DockerInstalled { get; set; } public string DockerContainerType { get; set; } + + public DockerInfo( + bool dockerInstalled, + string dockerContainerType) + { + DockerInstalled = dockerInstalled; + DockerContainerType = dockerContainerType; + } } } diff --git a/src/AWS.Deploy.Orchestration/TemplateEngine.cs b/src/AWS.Deploy.Orchestration/TemplateEngine.cs index fbca84a13..8dfb84da4 100644 --- a/src/AWS.Deploy.Orchestration/TemplateEngine.cs +++ b/src/AWS.Deploy.Orchestration/TemplateEngine.cs @@ -34,7 +34,10 @@ public TemplateEngine() public async Task GenerateCDKProjectFromTemplate(Recommendation recommendation, OrchestratorSession session, string outputDirectory) { //The location of the base template that will be installed into the templating engine - var cdkProjectTemplateDirectory = Path.Combine(Path.GetDirectoryName(recommendation.Recipe.RecipePath), recommendation.Recipe.CdkProjectTemplate); + var cdkProjectTemplateDirectory = Path.Combine( + Path.GetDirectoryName(recommendation.Recipe.RecipePath) ?? + throw new InvalidRecipePathException($"The following RecipePath is invalid as we could not retrieve the parent directory: {recommendation.Recipe.RecipePath}"), + recommendation.Recipe.CdkProjectTemplate); //Installing the base template into the templating engine to make it available for generation InstallTemplates(cdkProjectTemplateDirectory); @@ -64,8 +67,7 @@ public async Task GenerateCDKProjectFromTemplate(Recommendation recommendation, foreach(var option in recommendation.Recipe.OptionSettings) { var currentValue = recommendation.GetOptionSettingValue(option); - if (currentValue != null) - templateParameters[option.Id] = currentValue.ToString(); + templateParameters[option.Id] = currentValue?.ToString() ?? ""; } try diff --git a/src/AWS.Deploy.Orchestration/Utilities/CloudApplicationNameGenerator.cs b/src/AWS.Deploy.Orchestration/Utilities/CloudApplicationNameGenerator.cs index 117b51c37..b2082b634 100644 --- a/src/AWS.Deploy.Orchestration/Utilities/CloudApplicationNameGenerator.cs +++ b/src/AWS.Deploy.Orchestration/Utilities/CloudApplicationNameGenerator.cs @@ -33,7 +33,7 @@ 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 + /// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-using-console-create-stack-parameters.html /// private readonly Regex _validatorRegex = new ("^[a-zA-Z][a-zA-Z0-9-]{0,127}$", RegexOptions.Compiled); @@ -49,9 +49,9 @@ public string GenerateValidName(ProjectDefinition target, List var recommendedPrefix = "deployment"; if (_fileManager.Exists(target.ProjectPath)) - recommendedPrefix = Path.GetFileNameWithoutExtension(target.ProjectPath); + recommendedPrefix = Path.GetFileNameWithoutExtension(target.ProjectPath) ?? ""; else if (_directoryManager.Exists(target.ProjectPath)) - recommendedPrefix = Path.GetDirectoryName(target.ProjectPath); + recommendedPrefix = Path.GetDirectoryName(target.ProjectPath) ?? ""; // sanitize recommendation recommendedPrefix = diff --git a/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs b/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs index a1672ddc2..6e6f98d7f 100644 --- a/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs +++ b/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs @@ -20,7 +20,7 @@ public interface IDeployedApplicationQueryer /// If has any values that only existing applications that were deployed with any of the recipes /// identified by the recommendations will be returned. /// - Task> GetExistingDeployedApplications(IList compatibleRecommendations = null); + Task> GetExistingDeployedApplications(IList? compatibleRecommendations = null); } public class DeployedApplicationQueryer : IDeployedApplicationQueryer @@ -32,7 +32,7 @@ public DeployedApplicationQueryer(IAWSResourceQueryer awsResourceQueryer) _awsResourceQueryer = awsResourceQueryer; } - public async Task> GetExistingDeployedApplications(IList compatibleRecommendations = null) + public async Task> GetExistingDeployedApplications(IList? compatibleRecommendations = null) { var stacks = await _awsResourceQueryer.GetCloudFormationStacks(); var apps = new List(); @@ -73,11 +73,7 @@ public async Task> GetExistingDeployedApplications(IList< continue; } - apps.Add(new CloudApplication - { - Name = stack.StackName, - RecipeId = recipeId - }); + apps.Add(new CloudApplication(stack.StackName, recipeId)); } return apps; diff --git a/src/AWS.Deploy.Orchestration/Utilities/ICommandLineWrapper.cs b/src/AWS.Deploy.Orchestration/Utilities/ICommandLineWrapper.cs index 640e35574..4d62aac8f 100644 --- a/src/AWS.Deploy.Orchestration/Utilities/ICommandLineWrapper.cs +++ b/src/AWS.Deploy.Orchestration/Utilities/ICommandLineWrapper.cs @@ -48,9 +48,9 @@ public Task Run( string command, string workingDirectory = "", bool streamOutputToInteractiveService = true, - Action onComplete = null, + Action? onComplete = null, bool redirectIO = true, - IDictionary environmentVariables = null, + IDictionary? environmentVariables = null, CancellationToken cancelToken = default); /// @@ -102,7 +102,7 @@ public static async Task TryRunWithResult( string workingDirectory = "", bool streamOutputToInteractiveService = false, bool redirectIO = true, - IDictionary environmentVariables = null, + IDictionary? environmentVariables = null, CancellationToken cancelToken = default) { var result = new TryRunResult(); @@ -131,12 +131,12 @@ public class TryRunResult /// /// Fully read /// - public string StandardOut { get; set; } + public string? StandardOut { get; set; } /// /// Fully read /// - public string StandardError { get; set; } + public string? StandardError { get; set; } /// /// Fully read diff --git a/src/AWS.Deploy.Orchestration/Utilities/TemplateMetadataReader.cs b/src/AWS.Deploy.Orchestration/Utilities/TemplateMetadataReader.cs index 08028c0b5..661c6b0c3 100644 --- a/src/AWS.Deploy.Orchestration/Utilities/TemplateMetadataReader.cs +++ b/src/AWS.Deploy.Orchestration/Utilities/TemplateMetadataReader.cs @@ -64,12 +64,15 @@ private static CloudApplicationMetadata ReadSettings(string templateBody) var root = (YamlMappingNode)yamlMetadata.Documents[0].RootNode; var metadataNode = (YamlMappingNode)root.Children[new YamlScalarNode("Metadata")]; - var cloudApplicationMetadata = new CloudApplicationMetadata(); - cloudApplicationMetadata.RecipeId = ((YamlScalarNode)metadataNode.Children[new YamlScalarNode(CloudFormationIdentifierConstants.STACK_METADATA_RECIPE_ID)]).Value; - cloudApplicationMetadata.RecipeVersion = ((YamlScalarNode)metadataNode.Children[new YamlScalarNode(CloudFormationIdentifierConstants.STACK_METADATA_RECIPE_VERSION)]).Value; + var cloudApplicationMetadata = new CloudApplicationMetadata( + ((YamlScalarNode)metadataNode.Children[new YamlScalarNode(CloudFormationIdentifierConstants.STACK_METADATA_RECIPE_ID)]).Value ?? + throw new Exception("Error parsing existing application's metadata to retrieve Recipe ID."), + ((YamlScalarNode)metadataNode.Children[new YamlScalarNode(CloudFormationIdentifierConstants.STACK_METADATA_RECIPE_VERSION)]).Value ?? + throw new Exception("Error parsing existing application's metadata to retrieve Recipe Version.") + ); var jsonString = ((YamlScalarNode)metadataNode.Children[new YamlScalarNode(CloudFormationIdentifierConstants.STACK_METADATA_SETTINGS)]).Value; - cloudApplicationMetadata.Settings = JsonConvert.DeserializeObject>(jsonString); + cloudApplicationMetadata.Settings = JsonConvert.DeserializeObject>(jsonString ?? ""); return cloudApplicationMetadata; } @@ -89,7 +92,7 @@ private static string ExtractMetadataSection(string templateBody) var builder = new StringBuilder(); bool inMetadata = false; using var reader = new StringReader(templateBody); - string line; + string? line; while((line = reader.ReadLine()) != null) { if(!inMetadata) diff --git a/src/AWS.Deploy.Orchestration/Utilities/ZipFileManager.cs b/src/AWS.Deploy.Orchestration/Utilities/ZipFileManager.cs index f985d4598..836f313d2 100644 --- a/src/AWS.Deploy.Orchestration/Utilities/ZipFileManager.cs +++ b/src/AWS.Deploy.Orchestration/Utilities/ZipFileManager.cs @@ -92,7 +92,7 @@ private IDictionary GetFilesToIncludeInArchive(string publishLoc /// /// The command to search for in the path /// The full path to the command if found otherwise it will return null - private string FindExecutableInPath(string command) + private string? FindExecutableInPath(string command) { if (File.Exists(command)) return Path.GetFullPath(command); @@ -106,7 +106,7 @@ private string FindExecutableInPath(string command) return x; }; - var envPath = Environment.GetEnvironmentVariable("PATH"); + var envPath = Environment.GetEnvironmentVariable("PATH") ?? ""; foreach (var path in envPath.Split(Path.PathSeparator)) { try diff --git a/src/AWS.Deploy.Recipes.CDK.Common/Exceptions.cs b/src/AWS.Deploy.Recipes.CDK.Common/Exceptions.cs index 391d41f26..d594d4b2a 100644 --- a/src/AWS.Deploy.Recipes.CDK.Common/Exceptions.cs +++ b/src/AWS.Deploy.Recipes.CDK.Common/Exceptions.cs @@ -13,4 +13,14 @@ public InvalidAWSDeployToolSettingsException(string message) : base(message) { } } + + /// + /// The exception is thrown if an invalid configuration setting is passed to the CDK template project. + /// + public class InvalidOrMissingConfigurationException : Exception + { + public InvalidOrMissingConfigurationException(string message) : base(message) + { + } + } } diff --git a/src/AWS.Deploy.Recipes.CDK.Common/RecipeConfiguration.cs b/src/AWS.Deploy.Recipes.CDK.Common/RecipeConfiguration.cs index d91a775f7..b536ccf9c 100644 --- a/src/AWS.Deploy.Recipes.CDK.Common/RecipeConfiguration.cs +++ b/src/AWS.Deploy.Recipes.CDK.Common/RecipeConfiguration.cs @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +using System.Text.Json.Serialization; + namespace AWS.Deploy.Recipes.CDK.Common { /// @@ -22,22 +24,22 @@ public class RecipeConfiguration /// /// The ECR Repository Name where the docker image will be pushed to. /// - public string ECRRepositoryName { get; set; } + public string? ECRRepositoryName { get; set; } /// /// The ECR Image Tag of the docker image. /// - public string ECRImageTag { get; set; } + public string? ECRImageTag { get; set; } /// /// The path of the zip file containing the assemblies produced by the dotnet publish command. /// - public string DotnetPublishZipPath { get; set; } + public string? DotnetPublishZipPath { get; set; } /// /// The directory containing the assemblies produced by the dotnet publish command. /// - public string DotnetPublishOutputDirectory { get; set; } + public string? DotnetPublishOutputDirectory { get; set; } /// /// The ID of the recipe being used to deploy the application. @@ -53,5 +55,24 @@ public class RecipeConfiguration /// The configured settings made by the frontend. These are recipe specific and defined in the recipe's definition. /// public T Settings { get; set; } + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public RecipeConfiguration() + { + + } +#nullable restore warnings + + public RecipeConfiguration(string stackName, string projectPath, string recipeId, string recipeVersion, T settings) + { + StackName = stackName; + ProjectPath = projectPath; + RecipeId = recipeId; + RecipeVersion = recipeVersion; + Settings = settings; + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/AppStack.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/AppStack.cs index daff42ad3..3f44f813d 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/AppStack.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/AppStack.cs @@ -16,7 +16,7 @@ namespace AspNetAppEcsFargate { public class AppStack : Stack { - internal AppStack(Construct scope, RecipeConfiguration recipeConfiguration, IStackProps props = null) + internal AppStack(Construct scope, RecipeConfiguration recipeConfiguration, IStackProps? props = null) : base(scope, recipeConfiguration.StackName, props) { var settings = recipeConfiguration.Settings; @@ -74,6 +74,9 @@ internal AppStack(Construct scope, RecipeConfiguration recipeConf } else { + if (string.IsNullOrEmpty(settings.ApplicationIAMRole.RoleArn)) + throw new InvalidOrMissingConfigurationException("The provided Application IAM Role ARN is null or empty."); + taskRole = Role.FromRoleArn(this, "TaskRole", settings.ApplicationIAMRole.RoleArn, new FromRoleArnOptions { Mutable = false }); @@ -86,6 +89,9 @@ internal AppStack(Construct scope, RecipeConfiguration recipeConf MemoryLimitMiB = settings.TaskMemory }); + if (string.IsNullOrEmpty(recipeConfiguration.ECRRepositoryName)) + throw new InvalidOrMissingConfigurationException("The provided ECR Repository Name is null or empty."); + var ecrRepository = Repository.FromRepositoryName(this, "ECRRepository", recipeConfiguration.ECRRepositoryName); var container = taskDefinition.AddContainer("Container", new ContainerDefinitionOptions { diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/Configuration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/Configuration.cs index 1a04a9e83..fe0f52e47 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/Configuration.cs @@ -40,5 +40,30 @@ public class Configuration /// public double? TaskMemory { get; set; } + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public Configuration() + { + + } +#nullable restore warnings + + public Configuration( + IAMRoleConfiguration applicationIAMRole, + string ecsServiceName, + ECSClusterConfiguration ecsCluster, + VpcConfiguration vpc, + string additionalECSServiceSecurityGroups + ) + { + ApplicationIAMRole = applicationIAMRole; + ECSServiceName = ecsServiceName; + ECSCluster = ecsCluster; + Vpc = vpc; + AdditionalECSServiceSecurityGroups = additionalECSServiceSecurityGroups; + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/ECSClusterConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/ECSClusterConfiguration.cs index 39e4d18a9..2a17fb460 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/ECSClusterConfiguration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/ECSClusterConfiguration.cs @@ -21,5 +21,25 @@ public class ECSClusterConfiguration /// then create a new ECS Cluster with the name /// public string NewClusterName { get; set; } + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public ECSClusterConfiguration() + { + + } +#nullable restore warnings + + public ECSClusterConfiguration( + bool createNew, + string clusterArn, + string newClusterName) + { + CreateNew = createNew; + ClusterArn = clusterArn; + NewClusterName = newClusterName; + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/IAMRoleConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/IAMRoleConfiguration.cs index 96f77ddb2..716698f6c 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/IAMRoleConfiguration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/IAMRoleConfiguration.cs @@ -14,6 +14,6 @@ public class IAMRoleConfiguration /// If is false, /// then use an existing IAM role by referencing through /// - public string RoleArn { get; set; } + public string? RoleArn { get; set; } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/VpcConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/VpcConfiguration.cs index 498f97cfb..b4d001f9b 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/VpcConfiguration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Configurations/VpcConfiguration.cs @@ -20,5 +20,25 @@ public class VpcConfiguration /// then use an existing VPC by referencing through /// public string VpcId { get; set; } + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public VpcConfiguration() + { + + } +#nullable restore warnings + + public VpcConfiguration( + bool isDefault, + bool createNew, + string vpcId) + { + IsDefault = isDefault; + CreateNew = createNew; + VpcId = vpcId; + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Program.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Program.cs index 422f47f25..fda8af359 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Program.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Program.cs @@ -1,10 +1,12 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +using System; using Amazon.CDK; using AWS.Deploy.Recipes.CDK.Common; using AspNetAppEcsFargate.Configurations; using Microsoft.Extensions.Configuration; +using Environment = Amazon.CDK.Environment; namespace AspNetAppEcsFargate { diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/AppStack.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/AppStack.cs index 943a89294..0213792ee 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/AppStack.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/AppStack.cs @@ -16,17 +16,20 @@ public class AppStack : Stack private const string ENVIRONMENTTYPE_SINGLEINSTANCE = "SingleInstance"; private const string ENVIRONMENTTYPE_LOADBALANCED = "LoadBalanced"; - internal AppStack(Construct scope, RecipeConfiguration recipeConfiguration, IStackProps props = null) + internal AppStack(Construct scope, RecipeConfiguration recipeConfiguration, IStackProps? props = null) : base(scope, recipeConfiguration.StackName, props) { var settings = recipeConfiguration.Settings; + if (string.IsNullOrEmpty(recipeConfiguration.DotnetPublishZipPath)) + throw new InvalidOrMissingConfigurationException("The provided path containing the dotnet publish zip file is null or empty."); + var asset = new Asset(this, "Asset", new AssetProps { Path = recipeConfiguration.DotnetPublishZipPath }); - CfnApplication application = null; + CfnApplication? application = null; // Create an app version from the S3 asset defined above // The S3 "putObject" will occur first before CF generates the template @@ -67,6 +70,9 @@ internal AppStack(Construct scope, RecipeConfiguration recipeConf } else { + if (string.IsNullOrEmpty(settings.ApplicationIAMRole.RoleArn)) + throw new InvalidOrMissingConfigurationException("The provided Application IAM Role ARN is null or empty."); + role = Role.FromRoleArn(this, "Role", settings.ApplicationIAMRole.RoleArn); } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Configurations/BeanstalkApplicationConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Configurations/BeanstalkApplicationConfiguration.cs index 556d3e963..eb538dd8e 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Configurations/BeanstalkApplicationConfiguration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Configurations/BeanstalkApplicationConfiguration.cs @@ -7,5 +7,23 @@ public class BeanstalkApplicationConfiguration { public bool CreateNew { get; set; } public string ApplicationName { get; set; } + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public BeanstalkApplicationConfiguration() + { + + } +#nullable restore warnings + + public BeanstalkApplicationConfiguration( + bool createNew, + string applicationName) + { + CreateNew = createNew; + ApplicationName = applicationName; + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Configurations/Configuration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Configurations/Configuration.cs index ea30a5cc3..26cfd4cd9 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Configurations/Configuration.cs @@ -49,5 +49,37 @@ public class Configuration /// Specifies whether to enable or disable Managed Platform Updates. /// public ElasticBeanstalkManagedPlatformUpdatesConfiguration ElasticBeanstalkManagedPlatformUpdates { get; set; } + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public Configuration() + { + + } +#nullable restore warnings + + public Configuration( + IAMRoleConfiguration applicationIAMRole, + string instanceType, + string environmentName, + BeanstalkApplicationConfiguration beanstalkApplication, + string elasticBeanstalkPlatformArn, + string ec2KeyPair, + ElasticBeanstalkManagedPlatformUpdatesConfiguration elasticBeanstalkManagedPlatformUpdates, + string environmentType = "SingleInstance", + string loadBalancerType = "application") + { + ApplicationIAMRole = applicationIAMRole; + InstanceType = instanceType; + EnvironmentName = environmentName; + BeanstalkApplication = beanstalkApplication; + ElasticBeanstalkPlatformArn = elasticBeanstalkPlatformArn; + EC2KeyPair = ec2KeyPair; + ElasticBeanstalkManagedPlatformUpdates = elasticBeanstalkManagedPlatformUpdates; + EnvironmentType = environmentType; + LoadBalancerType = loadBalancerType; + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Configurations/IAMRoleConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Configurations/IAMRoleConfiguration.cs index de77cd64d..1ba65575d 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Configurations/IAMRoleConfiguration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppElasticBeanstalkLinux/Configurations/IAMRoleConfiguration.cs @@ -14,6 +14,6 @@ public class IAMRoleConfiguration /// If is false, /// then use an existing IAM role by referencing through /// - public string RoleArn { get; set; } + public string? RoleArn { get; set; } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/AppStack.cs b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/AppStack.cs index 1b31bfaac..fffbf3f03 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/AppStack.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/AppStack.cs @@ -10,7 +10,7 @@ namespace BlazorWasm { public class AppStack : Stack { - internal AppStack(Construct scope, RecipeConfiguration recipeConfiguration, IStackProps props = null) + internal AppStack(Construct scope, RecipeConfiguration recipeConfiguration, IStackProps? props = null) : base(scope, recipeConfiguration.StackName, props) { var bucketProps = new BucketProps @@ -44,6 +44,9 @@ internal AppStack(Construct scope, RecipeConfiguration recipeConf var bucket = new Bucket(this, "BlazorHost", bucketProps); + if (string.IsNullOrEmpty(recipeConfiguration.DotnetPublishOutputDirectory)) + throw new InvalidOrMissingConfigurationException("The provided path containing the dotnet publish output is null or empty."); + new BucketDeployment(this, "BlazorDeployment", new BucketDeploymentProps { Sources = new ISource[] { Source.Asset(Path.Combine(recipeConfiguration.DotnetPublishOutputDirectory, "wwwroot")) }, diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Configurations/Configuration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Configurations/Configuration.cs index b6356af7a..c43fafb57 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Configurations/Configuration.cs @@ -21,5 +21,24 @@ public class Configuration /// S3 since no S3 object exists at that resource path. /// public bool Redirect404ToRoot { get; set; } = true; + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public Configuration() + { + + } +#nullable restore warnings + + public Configuration( + string indexDocument, + string errorDocument + ) + { + IndexDocument = indexDocument; + ErrorDocument = errorDocument; + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/AppStack.cs b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/AppStack.cs index cedee1b2f..ad3c319e5 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/AppStack.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/AppStack.cs @@ -18,7 +18,7 @@ namespace ConsoleAppECSFargateScheduleTask { public class AppStack : Stack { - internal AppStack(Construct scope, RecipeConfiguration recipeConfiguration, IStackProps props = null) + internal AppStack(Construct scope, RecipeConfiguration recipeConfiguration, IStackProps? props = null) : base(scope, recipeConfiguration.StackName, props) { var settings = recipeConfiguration.Settings; @@ -76,6 +76,9 @@ internal AppStack(Construct scope, RecipeConfiguration recipeConf } else { + if (string.IsNullOrEmpty(settings.ApplicationIAMRole.RoleArn)) + throw new InvalidOrMissingConfigurationException("The provided Application IAM Role ARN is null or empty."); + taskRole = Role.FromRoleArn(this, "TaskRole", settings.ApplicationIAMRole.RoleArn, new FromRoleArnOptions { Mutable = false }); @@ -93,6 +96,9 @@ internal AppStack(Construct scope, RecipeConfiguration recipeConf StreamPrefix = recipeConfiguration.StackName }); + if (string.IsNullOrEmpty(recipeConfiguration.ECRRepositoryName)) + throw new InvalidOrMissingConfigurationException("The provided ECR Repository Name is null or empty."); + var ecrRepository = Repository.FromRepositoryName(this, "ECRRepository", recipeConfiguration.ECRRepositoryName); taskDefinition.AddContainer("Container", new ContainerDefinitionOptions { @@ -100,7 +106,7 @@ internal AppStack(Construct scope, RecipeConfiguration recipeConf Logging = logging }); - SubnetSelection subnetSelection = null; + SubnetSelection? subnetSelection = null; if (settings.Vpc.IsDefault) { subnetSelection = new SubnetSelection diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/Configuration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/Configuration.cs index d48445207..31e58575d 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/Configuration.cs @@ -30,5 +30,28 @@ public class Configuration /// public double? TaskMemory { get; set; } + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public Configuration() + { + + } +#nullable restore warnings + + public Configuration( + IAMRoleConfiguration applicationIAMRole, + string schedule, + ECSClusterConfiguration ecsCluster, + VpcConfiguration vpc + ) + { + ApplicationIAMRole = applicationIAMRole; + Schedule = schedule; + ECSCluster = ecsCluster; + Vpc = vpc; + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/ECSClusterConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/ECSClusterConfiguration.cs index 5693b94ab..9a5fc2fe2 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/ECSClusterConfiguration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/ECSClusterConfiguration.cs @@ -21,5 +21,25 @@ public class ECSClusterConfiguration /// then create a new ECS Cluster with the name /// public string NewClusterName { get; set; } + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public ECSClusterConfiguration() + { + + } +#nullable restore warnings + + public ECSClusterConfiguration( + bool createNew, + string clusterArn, + string newClusterName) + { + CreateNew = createNew; + ClusterArn = clusterArn; + NewClusterName = newClusterName; + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/IAMRoleConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/IAMRoleConfiguration.cs index 12bee1ac7..cac48de07 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/IAMRoleConfiguration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/IAMRoleConfiguration.cs @@ -14,6 +14,6 @@ public class IAMRoleConfiguration /// If is false, /// then use an existing IAM role by referencing through /// - public string RoleArn { get; set; } + public string? RoleArn { get; set; } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/VpcConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/VpcConfiguration.cs index 55a7becda..21f039a6b 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/VpcConfiguration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateScheduleTask/Configurations/VpcConfiguration.cs @@ -20,5 +20,25 @@ public class VpcConfiguration /// then use an existing VPC by referencing through /// public string VpcId { get; set; } + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public VpcConfiguration() + { + + } +#nullable restore warnings + + public VpcConfiguration( + bool isDefault, + bool createNew, + string vpcId) + { + IsDefault = isDefault; + CreateNew = createNew; + VpcId = vpcId; + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/AppStack.cs b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/AppStack.cs index fdf2855bc..f95a81596 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/AppStack.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/AppStack.cs @@ -14,7 +14,7 @@ namespace ConsoleAppEcsFargateService { public class AppStack : Stack { - internal AppStack(Construct scope, RecipeConfiguration recipeConfiguration, IStackProps props = null) + internal AppStack(Construct scope, RecipeConfiguration recipeConfiguration, IStackProps? props = null) : base(scope, recipeConfiguration.StackName, props) { var settings = recipeConfiguration.Settings; @@ -72,6 +72,9 @@ internal AppStack(Construct scope, RecipeConfiguration recipeConf } else { + if (string.IsNullOrEmpty(settings.ApplicationIAMRole.RoleArn)) + throw new InvalidOrMissingConfigurationException("The provided Application IAM Role ARN is null or empty."); + taskRole = Role.FromRoleArn(this, "TaskRole", settings.ApplicationIAMRole.RoleArn, new FromRoleArnOptions { Mutable = false }); @@ -89,6 +92,9 @@ internal AppStack(Construct scope, RecipeConfiguration recipeConf StreamPrefix = recipeConfiguration.StackName }); + if (string.IsNullOrEmpty(recipeConfiguration.ECRRepositoryName)) + throw new InvalidOrMissingConfigurationException("The provided ECR Repository Name is null or empty."); + var ecrRepository = Repository.FromRepositoryName(this, "ECRRepository", recipeConfiguration.ECRRepositoryName); taskDefinition.AddContainer("Container", new ContainerDefinitionOptions { diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/Configuration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/Configuration.cs index 0cde4d477..c441d5f01 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/Configuration.cs @@ -35,5 +35,28 @@ public class Configuration /// public double? TaskMemory { get; set; } + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public Configuration() + { + + } +#nullable restore warnings + + public Configuration( + IAMRoleConfiguration applicationIAMRole, + ECSClusterConfiguration ecsCluster, + VpcConfiguration vpc, + string ecsServiceSecurityGroups + ) + { + ApplicationIAMRole = applicationIAMRole; + ECSCluster = ecsCluster; + Vpc = vpc; + ECSServiceSecurityGroups = ecsServiceSecurityGroups; + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/ECSClusterConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/ECSClusterConfiguration.cs index b0c8f0703..16d77904b 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/ECSClusterConfiguration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/ECSClusterConfiguration.cs @@ -21,5 +21,25 @@ public class ECSClusterConfiguration /// then create a new ECS Cluster with the name /// public string NewClusterName { get; set; } + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public ECSClusterConfiguration() + { + + } +#nullable restore warnings + + public ECSClusterConfiguration( + bool createNew, + string clusterArn, + string newClusterName) + { + CreateNew = createNew; + ClusterArn = clusterArn; + NewClusterName = newClusterName; + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/IAMRoleConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/IAMRoleConfiguration.cs index 4a6dd8027..e5f675765 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/IAMRoleConfiguration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/IAMRoleConfiguration.cs @@ -14,6 +14,6 @@ public class IAMRoleConfiguration /// If is false, /// then use an existing IAM role by referencing through /// - public string RoleArn { get; set; } + public string? RoleArn { get; set; } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/VpcConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/VpcConfiguration.cs index 88978fb85..b9cf93141 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/VpcConfiguration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/ConsoleAppECSFargateService/Configurations/VpcConfiguration.cs @@ -20,5 +20,25 @@ public class VpcConfiguration /// then use an existing VPC by referencing through /// public string VpcId { get; set; } + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public VpcConfiguration() + { + + } +#nullable restore warnings + + public VpcConfiguration( + bool isDefault, + bool createNew, + string vpcId) + { + IsDefault = isDefault; + CreateNew = createNew; + VpcId = vpcId; + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/Directory.Build.props b/src/AWS.Deploy.Recipes/CdkTemplates/Directory.Build.props new file mode 100644 index 000000000..9d1a00ade --- /dev/null +++ b/src/AWS.Deploy.Recipes/CdkTemplates/Directory.Build.props @@ -0,0 +1,8 @@ + + + + true + 9 + enable + + diff --git a/src/AWS.Deploy.ServerMode.Client/DeploymentCommunicationClient.cs b/src/AWS.Deploy.ServerMode.Client/DeploymentCommunicationClient.cs index 2e0e9dec4..d59d94d30 100644 --- a/src/AWS.Deploy.ServerMode.Client/DeploymentCommunicationClient.cs +++ b/src/AWS.Deploy.ServerMode.Client/DeploymentCommunicationClient.cs @@ -17,11 +17,11 @@ public class DeploymentCommunicationClient : IDisposable private bool _initialized = false; private readonly HubConnection _connection; - public Action ReceiveLogDebugLine { get; set; } - public Action ReceiveLogErrorMessageLine { get; set; } - public Action ReceiveLogMessageLineAction { get; set; } + public Action? ReceiveLogDebugLine { get; set; } + public Action? ReceiveLogErrorMessageLine { get; set; } + public Action? ReceiveLogMessageLineAction { get; set; } - public Action ReceiveLogAllLogAction { get; set; } + public Action? ReceiveLogAllLogAction { get; set; } public DeploymentCommunicationClient(string baseUrl) { diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 89ef1c2f3..f046cc158 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -8,5 +8,7 @@ true + 9 + enable - \ No newline at end of file + diff --git a/test/AWS.Deploy.CLI.UnitTests/ApplyPreviousSettingsTests.cs b/test/AWS.Deploy.CLI.UnitTests/ApplyPreviousSettingsTests.cs index 775b194a1..c7b3f2184 100644 --- a/test/AWS.Deploy.CLI.UnitTests/ApplyPreviousSettingsTests.cs +++ b/test/AWS.Deploy.CLI.UnitTests/ApplyPreviousSettingsTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Amazon.Runtime; using AWS.Deploy.CLI.TypeHintResponses; using AWS.Deploy.CLI.UnitTests.Utilities; using AWS.Deploy.Common; @@ -12,6 +13,7 @@ using AWS.Deploy.Recipes; using AWS.Deploy.Orchestration; using AWS.Deploy.Orchestration.RecommendationEngine; +using Moq; using Newtonsoft.Json; using Xunit; using Assert = Should.Core.Assertions.Assert; @@ -25,10 +27,18 @@ private async Task BuildRecommendationEngine(string testPr var fullPath = SystemIOUtilities.ResolvePath(testProjectName); var parser = new ProjectDefinitionParser(new FileManager(), new DirectoryManager()); - - var session = new OrchestratorSession + var awsCredentials = new Mock(); + var systemCapabilities = new Mock( + It.IsAny(), + It.IsAny()); + var session = new OrchestratorSession( + await parser.Parse(fullPath), + awsCredentials.Object, + "us-west-2", + "123456789012") { - ProjectDefinition = await parser.Parse(fullPath) + SystemCapabilities = Task.FromResult(systemCapabilities.Object), + AWSProfileName = "default" }; return new RecommendationEngine(new[] { RecipeLocator.FindRecipeDefinitionsPath() }, session); @@ -67,8 +77,8 @@ public async Task ApplyApplicationIAMRolePreviousSettings(bool createNew, string } [Theory] - [InlineData(true, false, null)] - [InlineData(false, true, null)] + [InlineData(true, false, "")] + [InlineData(false, true, "")] [InlineData(false, false, "vpc_id")] public async Task ApplyVpcPreviousSettings(bool isDefault, bool createNew, string vpcId) { @@ -78,7 +88,7 @@ public async Task ApplyVpcPreviousSettings(bool isDefault, bool createNew, strin var fargateRecommendation = recommendations.First(r => r.Recipe.Id == Constants.ASPNET_CORE_ASPNET_CORE_FARGATE_RECIPE_ID); - var vpcIdValue = vpcId == null ? "null" : $"\"{vpcId}\""; + var vpcIdValue = string.IsNullOrEmpty(vpcId) ? "\"\"" : $"\"{vpcId}\""; var serializedSettings = @$" {{ diff --git a/test/AWS.Deploy.CLI.UnitTests/ConsoleUtilitiesTests.cs b/test/AWS.Deploy.CLI.UnitTests/ConsoleUtilitiesTests.cs index a7100cd46..b78a104fc 100644 --- a/test/AWS.Deploy.CLI.UnitTests/ConsoleUtilitiesTests.cs +++ b/test/AWS.Deploy.CLI.UnitTests/ConsoleUtilitiesTests.cs @@ -36,12 +36,12 @@ public void AskUserToChooseOrCreateNew() "CustomNewIdentifier" }); var consoleUtilities = new ConsoleUtilities(interactiveServices); - var userInputConfiguration = new UserInputConfiguration + var userInputConfiguration = new UserInputConfiguration( + option => option.DisplayName, + option => option.Identifier.Equals("Identifier2"), + "NewIdentifier") { - DisplaySelector = option => option.DisplayName, - DefaultSelector = option => option.Identifier.Equals("Identifier2"), - AskNewName = true, - DefaultNewName = "NewIdentifier" + AskNewName = true }; var userResponse = consoleUtilities.AskUserToChooseOrCreateNew(_options, "Title", userInputConfiguration); @@ -67,12 +67,12 @@ public void AskUserToChooseOrCreateNewPickExisting() "1" }); var consoleUtilities = new ConsoleUtilities(interactiveServices); - var userInputConfiguration = new UserInputConfiguration + var userInputConfiguration = new UserInputConfiguration( + option => option.DisplayName, + option => option.Identifier.Equals("Identifier2"), + "NewIdentifier") { - DisplaySelector = option => option.DisplayName, - DefaultSelector = option => option.Identifier.Equals("Identifier2"), - AskNewName = true, - DefaultNewName = "NewIdentifier" + AskNewName = true }; var userResponse = consoleUtilities.AskUserToChooseOrCreateNew(_options, "Title", userInputConfiguration); diff --git a/test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs b/test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs index 714d051c6..98ea1cff0 100644 --- a/test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs +++ b/test/AWS.Deploy.CLI.UnitTests/DeploymentBundleHandlerTests.cs @@ -1,10 +1,12 @@ // 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.Linq; using System.Threading.Tasks; +using Amazon.Runtime; using AWS.Deploy.CLI.Common.UnitTests.IO; using AWS.Deploy.CLI.UnitTests.Utilities; using AWS.Deploy.Common; @@ -24,18 +26,30 @@ public class DeploymentBundleHandlerTests private readonly TestToolCommandLineWrapper _commandLineWrapper; private readonly TestDirectoryManager _directoryManager; private readonly ProjectDefinitionParser _projectDefinitionParser; + private readonly RecipeDefinition _recipeDefinition; public DeploymentBundleHandlerTests() { var awsResourceQueryer = new TestToolAWSResourceQueryer(); var interactiveService = new TestToolOrchestratorInteractiveService(); var zipFileManager = new TestZipFileManager(); - + _commandLineWrapper = new TestToolCommandLineWrapper(); _directoryManager = new TestDirectoryManager(); _projectDefinitionParser = new ProjectDefinitionParser(new FileManager(), new DirectoryManager()); - + _deploymentBundleHandler = new DeploymentBundleHandler(_commandLineWrapper, awsResourceQueryer, interactiveService, _directoryManager, zipFileManager); + + _recipeDefinition = new Mock( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()).Object; } [Fact] @@ -43,10 +57,10 @@ public async Task BuildDockerImage_DockerExecutionDirectoryNotSet() { var projectPath = SystemIOUtilities.ResolvePath("ConsoleAppTask"); var project = await _projectDefinitionParser.Parse(projectPath); - var recipeDefinition = new Mock(); - var recommendation = new Recommendation(recipeDefinition.Object, project, 100, new Dictionary()); - var cloudApplication = new CloudApplication { Name = "ConsoleAppTask" }; + var recommendation = new Recommendation(_recipeDefinition, project, 100, new Dictionary()); + + var cloudApplication = new CloudApplication("ConsoleAppTask", String.Empty); var result = await _deploymentBundleHandler.BuildDockerImage(cloudApplication, recommendation); var dockerFile = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(recommendation.ProjectPath)), "Dockerfile"); @@ -63,12 +77,11 @@ public async Task BuildDockerImage_DockerExecutionDirectorySet() { var projectPath = SystemIOUtilities.ResolvePath("ConsoleAppTask"); var project = await _projectDefinitionParser.Parse(projectPath); - var recipeDefinition = new Mock(); - var recommendation = new Recommendation(recipeDefinition.Object, project, 100, new Dictionary()); + var recommendation = new Recommendation(_recipeDefinition, project, 100, new Dictionary()); recommendation.DeploymentBundle.DockerExecutionDirectory = projectPath; - var cloudApplication = new CloudApplication { Name = "ConsoleAppTask" }; + var cloudApplication = new CloudApplication("ConsoleAppTask", String.Empty); var result = await _deploymentBundleHandler.BuildDockerImage(cloudApplication, recommendation); var dockerFile = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(recommendation.ProjectPath)), "Dockerfile"); @@ -84,10 +97,9 @@ public async Task PushDockerImage_RepositoryNameCheck() { var projectPath = SystemIOUtilities.ResolvePath("ConsoleAppTask"); var project = await _projectDefinitionParser.Parse(projectPath); - var recipeDefinition = new Mock(); - var recommendation = new Recommendation(recipeDefinition.Object, project, 100, new Dictionary()); + var recommendation = new Recommendation(_recipeDefinition, project, 100, new Dictionary()); - var cloudApplication = new CloudApplication { Name = "ConsoleAppTask" }; + var cloudApplication = new CloudApplication("ConsoleAppTask", String.Empty); await _deploymentBundleHandler.PushDockerImageToECR(cloudApplication, recommendation, "ConsoleAppTask:latest"); Assert.Equal(cloudApplication.StackName.ToLower(), recommendation.DeploymentBundle.ECRRepositoryName); @@ -98,8 +110,7 @@ public async Task CreateDotnetPublishZip_NotSelfContained() { var projectPath = SystemIOUtilities.ResolvePath("ConsoleAppTask"); var project = await _projectDefinitionParser.Parse(projectPath); - var recipeDefinition = new Mock(); - var recommendation = new Recommendation(recipeDefinition.Object, project, 100, new Dictionary()); + var recommendation = new Recommendation(_recipeDefinition, project, 100, new Dictionary()); recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild = false; recommendation.DeploymentBundle.DotnetPublishBuildConfiguration = "Release"; @@ -122,8 +133,7 @@ public async Task CreateDotnetPublishZip_SelfContained() { var projectPath = SystemIOUtilities.ResolvePath("ConsoleAppTask"); var project = await _projectDefinitionParser.Parse(projectPath); - var recipeDefinition = new Mock(); - var recommendation = new Recommendation(recipeDefinition.Object, project, 100, new Dictionary()); + var recommendation = new Recommendation(_recipeDefinition, project, 100, new Dictionary()); recommendation.DeploymentBundle.DotnetPublishSelfContainedBuild = true; recommendation.DeploymentBundle.DotnetPublishBuildConfiguration = "Release"; @@ -147,10 +157,18 @@ private async Task BuildRecommendationEngine(string testPr var fullPath = SystemIOUtilities.ResolvePath(testProjectName); var parser = new ProjectDefinitionParser(new FileManager(), new DirectoryManager()); - - var session = new OrchestratorSession + var awsCredentials = new Mock(); + var systemCapabilities = new Mock( + It.IsAny(), + It.IsAny()); + var session = new OrchestratorSession( + await parser.Parse(fullPath), + awsCredentials.Object, + "us-west-2", + "123456789012") { - ProjectDefinition = await parser.Parse(fullPath) + SystemCapabilities = Task.FromResult(systemCapabilities.Object), + AWSProfileName = "default" }; return new RecommendationEngine(new[] { RecipeLocator.FindRecipeDefinitionsPath() }, session); @@ -159,43 +177,31 @@ private async Task BuildRecommendationEngine(string testPr [Fact] public async Task DockerExecutionDirectory_SolutionLevel() { - var projectPath = SystemIOUtilities.ResolvePath(Path.Combine("docker", "WebAppWithSolutionParentLevel", "WebAppWithSolutionParentLevel")); - - var session = new OrchestratorSession - { - ProjectDefinition = await _projectDefinitionParser.Parse(projectPath) - }; - - var engine = new RecommendationEngine(new[] { RecipeLocator.FindRecipeDefinitionsPath() }, session); + var projectPath = Path.Combine("docker", "WebAppWithSolutionParentLevel", "WebAppWithSolutionParentLevel"); + var engine = await BuildRecommendationEngine(projectPath); var recommendations = await engine.ComputeRecommendations(); var recommendation = recommendations.FirstOrDefault(x => x.Recipe.DeploymentBundle.Equals(DeploymentBundleTypes.Container)); - var cloudApplication = new CloudApplication { Name = "WebAppWithSolutionParentLevel" }; + var cloudApplication = new CloudApplication("WebAppWithSolutionParentLevel", String.Empty); var result = await _deploymentBundleHandler.BuildDockerImage(cloudApplication, recommendation); - Assert.Equal(Directory.GetParent(projectPath).FullName, recommendation.DeploymentBundle.DockerExecutionDirectory); + Assert.Equal(Directory.GetParent(SystemIOUtilities.ResolvePath(projectPath)).FullName, recommendation.DeploymentBundle.DockerExecutionDirectory); } [Fact] public async Task DockerExecutionDirectory_DockerfileLevel() { - var projectPath = SystemIOUtilities.ResolvePath(Path.Combine("docker", "WebAppNoSolution")); - - var session = new OrchestratorSession - { - ProjectDefinition = await _projectDefinitionParser.Parse(projectPath) - }; - - var engine = new RecommendationEngine(new[] { RecipeLocator.FindRecipeDefinitionsPath() }, session); + var projectPath = Path.Combine("docker", "WebAppNoSolution"); + var engine = await BuildRecommendationEngine(projectPath); var recommendations = await engine.ComputeRecommendations(); var recommendation = recommendations.FirstOrDefault(x => x.Recipe.DeploymentBundle.Equals(DeploymentBundleTypes.Container)); - var cloudApplication = new CloudApplication { Name = "WebAppNoSolution" }; + var cloudApplication = new CloudApplication("WebAppNoSolution", String.Empty); var result = await _deploymentBundleHandler.BuildDockerImage(cloudApplication, recommendation); - Assert.Equal(Path.GetFullPath(projectPath), recommendation.DeploymentBundle.DockerExecutionDirectory); + Assert.Equal(Path.GetFullPath(SystemIOUtilities.ResolvePath(projectPath)), recommendation.DeploymentBundle.DockerExecutionDirectory); } } } diff --git a/test/AWS.Deploy.CLI.UnitTests/GetOptionSettingTests.cs b/test/AWS.Deploy.CLI.UnitTests/GetOptionSettingTests.cs index 274dbb88b..897cd3320 100644 --- a/test/AWS.Deploy.CLI.UnitTests/GetOptionSettingTests.cs +++ b/test/AWS.Deploy.CLI.UnitTests/GetOptionSettingTests.cs @@ -4,12 +4,14 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Amazon.Runtime; using AWS.Deploy.CLI.UnitTests.Utilities; using AWS.Deploy.Common; using AWS.Deploy.Common.IO; using AWS.Deploy.Orchestration; using AWS.Deploy.Orchestration.RecommendationEngine; using AWS.Deploy.Recipes; +using Moq; using Xunit; namespace AWS.Deploy.CLI.UnitTests @@ -21,10 +23,18 @@ private async Task BuildRecommendationEngine(string testPr var fullPath = SystemIOUtilities.ResolvePath(testProjectName); var parser = new ProjectDefinitionParser(new FileManager(), new DirectoryManager()); - - var session = new OrchestratorSession + var awsCredentials = new Mock(); + var systemCapabilities = new Mock( + It.IsAny(), + It.IsAny()); + var session = new OrchestratorSession( + await parser.Parse(fullPath), + awsCredentials.Object, + "us-west-2", + "123456789012") { - ProjectDefinition = await parser.Parse(fullPath) + SystemCapabilities = Task.FromResult(systemCapabilities.Object), + AWSProfileName = "default" }; return new RecommendationEngine(new[] { RecipeLocator.FindRecipeDefinitionsPath() }, session); @@ -37,7 +47,7 @@ public async Task GetOptionSettingTests_OptionSettingExists(string jsonPath, str var engine = await BuildRecommendationEngine("WebAppNoDockerFile"); var recommendations = await engine.ComputeRecommendations(); - + var beanstalkRecommendation = recommendations.First(r => r.Recipe.Id == Constants.ASPNET_CORE_BEANSTALK_RECIPE_ID); var optionSetting = beanstalkRecommendation.GetOptionSetting(jsonPath); @@ -56,9 +66,7 @@ public async Task GetOptionSettingTests_OptionSettingDoesNotExist(string jsonPat var beanstalkRecommendation = recommendations.First(r => r.Recipe.Id == Constants.ASPNET_CORE_BEANSTALK_RECIPE_ID); - var optionSetting = beanstalkRecommendation.GetOptionSetting(jsonPath); - - Assert.Null(optionSetting); + Assert.Throws(() => beanstalkRecommendation.GetOptionSetting(jsonPath)); } [Theory] diff --git a/test/AWS.Deploy.CLI.UnitTests/RecommendationTests.cs b/test/AWS.Deploy.CLI.UnitTests/RecommendationTests.cs index 25a9b8d57..f569d40d7 100644 --- a/test/AWS.Deploy.CLI.UnitTests/RecommendationTests.cs +++ b/test/AWS.Deploy.CLI.UnitTests/RecommendationTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Amazon.Runtime; using AWS.Deploy.CLI.TypeHintResponses; using AWS.Deploy.CLI.UnitTests.Utilities; using AWS.Deploy.Common; @@ -12,6 +13,7 @@ using AWS.Deploy.Orchestration; using AWS.Deploy.Orchestration.RecommendationEngine; using AWS.Deploy.Recipes; +using Moq; using Should; using Xunit; @@ -19,18 +21,27 @@ namespace AWS.Deploy.CLI.UnitTests { public class RecommendationTests { + private OrchestratorSession _session; private async Task BuildRecommendationEngine(string testProjectName) { var fullPath = SystemIOUtilities.ResolvePath(testProjectName); var parser = new ProjectDefinitionParser(new FileManager(), new DirectoryManager()); - - var session = new OrchestratorSession + var awsCredentials = new Mock(); + var systemCapabilities = new Mock( + It.IsAny(), + It.IsAny()); + _session = new OrchestratorSession( + await parser.Parse(fullPath), + awsCredentials.Object, + "us-west-2", + "123456789012") { - ProjectDefinition = await parser.Parse(fullPath) + SystemCapabilities = Task.FromResult(systemCapabilities.Object), + AWSProfileName = "default" }; - return new RecommendationEngine(new[] { RecipeLocator.FindRecipeDefinitionsPath() }, session); + return new RecommendationEngine(new[] { RecipeLocator.FindRecipeDefinitionsPath() }, _session); } [Fact] @@ -110,7 +121,7 @@ public async Task ValueMappingWithDefaultValue() 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")); - Assert.Equal("SingleInstance", beanstalkRecommendation.GetOptionSettingValue(environmentTypeOptionSetting, false)); + Assert.Equal("SingleInstance", beanstalkRecommendation.GetOptionSettingValue(environmentTypeOptionSetting)); } [Fact] @@ -122,7 +133,7 @@ public async Task ResetOptionSettingValue_Int() }); var consoleUtilities = new ConsoleUtilities(interactiveServices); - + var engine = await BuildRecommendationEngine("WebAppNoDockerFile"); var recommendations = await engine.ComputeRecommendations(); @@ -135,7 +146,7 @@ public async Task ResetOptionSettingValue_Int() desiredCountOptionSetting.SetValueOverride(2); Assert.Equal(2, fargateRecommendation.GetOptionSettingValue(desiredCountOptionSetting)); - + desiredCountOptionSetting.SetValueOverride(consoleUtilities.AskUserForValue("Title", "2", true, originalDefaultValue.ToString())); Assert.Equal(originalDefaultValue, fargateRecommendation.GetOptionSettingValue(desiredCountOptionSetting)); @@ -178,7 +189,7 @@ public async Task ObjectMappingWithDefaultValue() 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")); - var iamRoleTypeHintResponse = beanstalkRecommendation.GetOptionSettingValue(applicationIAMRoleOptionSetting, false); + var iamRoleTypeHintResponse = beanstalkRecommendation.GetOptionSettingValue(applicationIAMRoleOptionSetting); Assert.Null(iamRoleTypeHintResponse.RoleArn); Assert.True(iamRoleTypeHintResponse.CreateNew); @@ -194,7 +205,7 @@ public async Task ObjectMappingWithoutDefaultValue() 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")); - Assert.Null(beanstalkRecommendation.GetOptionSettingValue(applicationIAMRoleOptionSetting, true)); + Assert.Null(beanstalkRecommendation.GetOptionSettingDefaultValue(applicationIAMRoleOptionSetting)); } [Fact] @@ -208,7 +219,7 @@ public async Task ValueMappingSetWithValue() var environmentTypeOptionSetting = beanstalkRecommendation.Recipe.OptionSettings.First(optionSetting => optionSetting.Id.Equals("EnvironmentType")); environmentTypeOptionSetting.SetValueOverride("LoadBalanced"); - Assert.Equal("LoadBalanced", beanstalkRecommendation.GetOptionSettingValue(environmentTypeOptionSetting, false)); + Assert.Equal("LoadBalanced", beanstalkRecommendation.GetOptionSettingValue(environmentTypeOptionSetting)); } [Fact] @@ -223,7 +234,7 @@ public async Task ObjectMappingSetWithValue() applicationIAMRoleOptionSetting.SetValueOverride(new IAMRoleTypeHintResponse {CreateNew = false, RoleArn = "role_arn"}); - var iamRoleTypeHintResponse = beanstalkRecommendation.GetOptionSettingValue(applicationIAMRoleOptionSetting, false); + var iamRoleTypeHintResponse = beanstalkRecommendation.GetOptionSettingValue(applicationIAMRoleOptionSetting); Assert.Equal("role_arn", iamRoleTypeHintResponse.RoleArn); Assert.False(iamRoleTypeHintResponse.CreateNew); @@ -248,7 +259,16 @@ public async Task ApplyProjectNameToSettings() [MemberData(nameof(ShouldIncludeTestCases))] public void ShouldIncludeTests(RuleEffect effect, bool testPass, bool expectedResult) { - var engine = new RecommendationEngine(new[] { RecipeLocator.FindRecipeDefinitionsPath() }, new OrchestratorSession()); + var awsCredentials = new Mock(); + var session = new OrchestratorSession( + null, + awsCredentials.Object, + "us-west-2", + "123456789012") + { + AWSProfileName = "default" + }; + var engine = new RecommendationEngine(new[] { RecipeLocator.FindRecipeDefinitionsPath() }, session); Assert.Equal(expectedResult, engine.ShouldInclude(effect, testPass)); } @@ -344,36 +364,30 @@ public void LoadAvailableRecommendationTests() public async Task PackageReferenceTest() { var projectPath = SystemIOUtilities.ResolvePath("MessageProcessingApp"); - + var projectDefinition = await new ProjectDefinitionParser(new FileManager(), new DirectoryManager()).Parse(projectPath); var test = new NuGetPackageReferenceTest(); - Assert.True(await test.Execute(new RecommendationTestInput - { - Test = new RuleTest - { - Type = test.Name, - Condition = new RuleCondition + Assert.True(await test.Execute(new RecommendationTestInput( + new RuleTest( + test.Name, + new RuleCondition { NuGetPackageName = "AWSSDK.SQS" - } - }, - ProjectDefinition = projectDefinition - })); - - Assert.False(await test.Execute(new RecommendationTestInput - { - Test = new RuleTest - { - Type = test.Name, - Condition = new RuleCondition + }), + projectDefinition, + _session))); + + Assert.False(await test.Execute(new RecommendationTestInput( + new RuleTest( + test.Name, + new RuleCondition { NuGetPackageName = "AWSSDK.S3" - } - }, - ProjectDefinition = projectDefinition - })); + }), + projectDefinition, + _session))); } } } diff --git a/test/AWS.Deploy.CLI.UnitTests/SetOptionSettingTests.cs b/test/AWS.Deploy.CLI.UnitTests/SetOptionSettingTests.cs index eef8578f1..1f62b44c8 100644 --- a/test/AWS.Deploy.CLI.UnitTests/SetOptionSettingTests.cs +++ b/test/AWS.Deploy.CLI.UnitTests/SetOptionSettingTests.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using Amazon.Runtime; using AWS.Deploy.CLI.UnitTests.Utilities; using AWS.Deploy.Common; using AWS.Deploy.Common.IO; @@ -10,6 +12,7 @@ using AWS.Deploy.Orchestration; using AWS.Deploy.Orchestration.RecommendationEngine; using AWS.Deploy.Recipes; +using Moq; using Xunit; namespace AWS.Deploy.CLI.UnitTests @@ -24,10 +27,18 @@ public SetOptionSettingTests() var projectPath = SystemIOUtilities.ResolvePath("WebAppNoDockerFile"); var parser = new ProjectDefinitionParser(new FileManager(), new DirectoryManager()); - - var session = new OrchestratorSession + var awsCredentials = new Mock(); + var systemCapabilities = new Mock( + It.IsAny(), + It.IsAny()); + var session = new OrchestratorSession( + parser.Parse(projectPath).Result, + awsCredentials.Object, + "us-west-2", + "123456789012") { - ProjectDefinition = parser.Parse(projectPath).Result + SystemCapabilities = Task.FromResult(systemCapabilities.Object), + AWSProfileName = "default" }; var engine = new RecommendationEngine(new[] { RecipeLocator.FindRecipeDefinitionsPath() }, session); diff --git a/test/AWS.Deploy.Orchestration.UnitTests/Utilities/CloudApplicationNameGeneratorTests.cs b/test/AWS.Deploy.Orchestration.UnitTests/Utilities/CloudApplicationNameGeneratorTests.cs index a21951e18..16ea5fe45 100644 --- a/test/AWS.Deploy.Orchestration.UnitTests/Utilities/CloudApplicationNameGeneratorTests.cs +++ b/test/AWS.Deploy.Orchestration.UnitTests/Utilities/CloudApplicationNameGeneratorTests.cs @@ -69,7 +69,7 @@ public async Task SuggestsValidName(string projectFile) { // ARRANGE var projectPath = _fakeFileManager.AddEmptyProjectFile($"c:\\{projectFile}"); - + var projectDefinition = await _projectDefinitionParser.Parse(projectPath); var existingApplication = new List(); @@ -91,15 +91,12 @@ public async Task SuggestsValidNameAndRespectsExistingApplications() var expectedRecommendation = $"{projectFile}1"; var projectPath = _fakeFileManager.AddEmptyProjectFile($"c:\\{projectFile}.csproj"); - + var projectDefinition = await _projectDefinitionParser.Parse(projectPath); var existingApplication = new List { - new CloudApplication - { - Name = projectFile - } + new CloudApplication(projectFile, string.Empty) }; // ACT diff --git a/testapps/ContosoUniversityWeb/ContosoUniversity.csproj b/testapps/ContosoUniversityWeb/ContosoUniversity.csproj index 03d2ca1dd..8c6b9e20d 100644 --- a/testapps/ContosoUniversityWeb/ContosoUniversity.csproj +++ b/testapps/ContosoUniversityWeb/ContosoUniversity.csproj @@ -5,14 +5,14 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - +