// 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.Text; using System.Threading.Tasks; using AWS.Deploy.Common; using AWS.Deploy.Common.IO; using AWS.Deploy.Common.Recipes; using AWS.Deploy.Common.Recipes.Validation; using AWS.Deploy.Orchestration.Utilities; using Newtonsoft.Json; namespace AWS.Deploy.Orchestration { public interface IDeploymentSettingsHandler { /// /// Read the JSON content at the specified file and deserializes it into /// Task ReadSettings(string filePath); /// /// Iterates over the option setting values found at and applies them to the selected recommendation /// Task ApplySettings(DeploymentSettings deploymentSettings, Recommendation recommendation, IDeployToolValidationContext deployToolValidationContext); /// /// Save the deployment settings at the specified file path. /// /// Thrown if this operation fails. Task SaveSettings(SaveSettingsConfiguration saveSettingsConfig, Recommendation recommendation, CloudApplication cloudApplication, OrchestratorSession orchestratorSession); } public class DeploymentSettingsHandler : IDeploymentSettingsHandler { private readonly IFileManager _fileManager; private readonly IDirectoryManager _directoryManager; private readonly IOptionSettingHandler _optionSettingHandler; private readonly IRecipeHandler _recipeHandler; public DeploymentSettingsHandler(IFileManager fileManager, IDirectoryManager directoryManager, IOptionSettingHandler optionSettingHandler, IRecipeHandler recipeHandler) { _fileManager = fileManager; _directoryManager = directoryManager; _optionSettingHandler = optionSettingHandler; _recipeHandler = recipeHandler; } public async Task ReadSettings(string filePath) { if (_fileManager.Exists(filePath)) { try { var contents = await _fileManager.ReadAllTextAsync(filePath); var userDeploymentSettings = JsonConvert.DeserializeObject(contents); return userDeploymentSettings; } catch (Exception ex) { throw new InvalidDeploymentSettingsException(DeployToolErrorCode.FailedToDeserializeUserDeploymentFile, $"An error occurred while trying to deserialize the deployment settings file located at {filePath}.\n {ex.Message}", ex); } } else { throw new InvalidDeploymentSettingsException(DeployToolErrorCode.UserDeploymentFileNotFound, $"The deployment settings file located at {filePath} doesn't exist."); } } public async Task ApplySettings(DeploymentSettings deploymentSettings, Recommendation recommendation, IDeployToolValidationContext deployToolValidationContext) { var optionSettings = deploymentSettings.Settings ?? new Dictionary(); foreach (var entry in optionSettings) { try { var optionSettingId = entry.Key; var optionSettingValue = entry.Value; var optionSetting = _optionSettingHandler.GetOptionSetting(recommendation, optionSettingId); await _optionSettingHandler.SetOptionSettingValue(recommendation, optionSetting, optionSettingValue, true); } catch (OptionSettingItemDoesNotExistException ex) { throw new InvalidDeploymentSettingsException(DeployToolErrorCode.DeploymentConfigurationNeedsAdjusting, ex.Message, ex); } } var optionSettingValidationFailedResult = _optionSettingHandler.RunOptionSettingValidators(recommendation); var recipeValidationFailedResult = _recipeHandler.RunRecipeValidators(recommendation, deployToolValidationContext); if (!optionSettingValidationFailedResult.Any() && !recipeValidationFailedResult.Any()) { // All validations are successful return; } var errorMessage = "The deployment configuration needs to be adjusted before it can be deployed:" + Environment.NewLine; var failedValidations = optionSettingValidationFailedResult.Concat(recipeValidationFailedResult); foreach (var validation in failedValidations) { errorMessage += validation.ValidationFailedMessage + Environment.NewLine; } throw new InvalidDeploymentSettingsException(DeployToolErrorCode.DeploymentConfigurationNeedsAdjusting, errorMessage.Trim()); } public async Task SaveSettings(SaveSettingsConfiguration saveSettingsConfig, Recommendation recommendation, CloudApplication cloudApplication, OrchestratorSession orchestratorSession) { if (saveSettingsConfig.SettingsType == SaveSettingsType.None) { // We are not throwing an expected exception here as this issue is not caused by the user. throw new InvalidOperationException($"Cannot persist settings with {SaveSettingsType.None}"); } if (!_fileManager.IsFileValidPath(saveSettingsConfig.FilePath)) { var message = $"Failed to save deployment settings because {saveSettingsConfig.FilePath} is invalid or its parent directory does not exist on disk."; throw new FailedToSaveDeploymentSettingsException(DeployToolErrorCode.FailedToSaveDeploymentSettings, message); } var projectDirectory = Path.GetDirectoryName(orchestratorSession.ProjectDefinition.ProjectPath); if (string.IsNullOrEmpty(projectDirectory)) { var message = "Failed to save deployment settings because the current deployment session does not have a valid project path"; throw new FailedToSaveDeploymentSettingsException(DeployToolErrorCode.FailedToSaveDeploymentSettings, message); } var deploymentSettings = new DeploymentSettings { AWSProfile = orchestratorSession.AWSProfileName, AWSRegion = orchestratorSession.AWSRegion, ApplicationName = recommendation.Recipe.DeploymentType == DeploymentTypes.ElasticContainerRegistryImage ? null : cloudApplication.Name, RecipeId = cloudApplication.RecipeId, Settings = _optionSettingHandler.GetOptionSettingsMap(recommendation, orchestratorSession.ProjectDefinition, _directoryManager) }; if (saveSettingsConfig.SettingsType == SaveSettingsType.Modified) { foreach (var optionSetting in recommendation.GetConfigurableOptionSettingItems()) { if (!_optionSettingHandler.IsOptionSettingModified(recommendation, optionSetting)) { deploymentSettings.Settings.Remove(optionSetting.FullyQualifiedId); } } } try { var content = JsonConvert.SerializeObject(deploymentSettings, new JsonSerializerSettings { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore, ContractResolver = new SerializeModelContractResolver() }); await _fileManager.WriteAllTextAsync(saveSettingsConfig.FilePath, content); } catch (Exception ex) { var message = $"Failed to save the deployment settings at {saveSettingsConfig.FilePath} due to the following error: {Environment.NewLine}{ex.Message}"; throw new FailedToSaveDeploymentSettingsException(DeployToolErrorCode.FailedToSaveDeploymentSettings, message, ex); } } } }