// 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 AWS.Deploy.Common; using AWS.Deploy.Common.IO; using Newtonsoft.Json; namespace AWS.Deploy.Orchestration.LocalUserSettings { public interface ILocalUserSettingsEngine { Task UpdateLastDeployedStack(string stackName, string projectName, string? awsAccountId, string? awsRegion); Task DeleteLastDeployedStack(string stackName, string projectName, string? awsAccountId, string? awsRegion); Task CleanOrphanStacks(List deployedStacks, string projectName, string? awsAccountId, string? awsRegion); Task GetLocalUserSettings(); } public class LocalUserSettingsEngine : ILocalUserSettingsEngine { private readonly IFileManager _fileManager; private readonly IDirectoryManager _directoryManager; private readonly IDeployToolWorkspaceMetadata _workspaceMetadata; private const string LOCAL_USER_SETTINGS_FILE_NAME = "local-user-settings.json"; public LocalUserSettingsEngine(IFileManager fileManager, IDirectoryManager directoryManager, IDeployToolWorkspaceMetadata workspaceMetadata) { _fileManager = fileManager; _directoryManager = directoryManager; _workspaceMetadata = workspaceMetadata; } /// /// This method updates the local user settings json file by adding the name of the stack that was most recently used. /// If the file does not exists then a new file is generated. /// public async Task UpdateLastDeployedStack(string stackName, string projectName, string? awsAccountId, string? awsRegion) { try { if (string.IsNullOrEmpty(projectName)) throw new FailedToUpdateLocalUserSettingsFileException(DeployToolErrorCode.FailedToUpdateLocalUserSettingsFile, "The Project Name is not defined."); if (string.IsNullOrEmpty(awsAccountId) || string.IsNullOrEmpty(awsRegion)) throw new FailedToUpdateLocalUserSettingsFileException(DeployToolErrorCode.FailedToUpdateLocalUserSettingsFile, "The AWS Account Id or Region is not defined."); var localUserSettings = await GetLocalUserSettings(); var lastDeployedStack = localUserSettings?.LastDeployedStacks? .FirstOrDefault(x => x.Exists(awsAccountId, awsRegion, projectName)); if (localUserSettings != null) { if (lastDeployedStack != null) { if (lastDeployedStack.Stacks == null) { lastDeployedStack.Stacks = new List { stackName }; } else { if (!lastDeployedStack.Stacks.Contains(stackName)) lastDeployedStack.Stacks.Add(stackName); lastDeployedStack.Stacks.Sort(); } } else { var currentStack = new LastDeployedStack( awsAccountId, awsRegion, projectName, new List() { stackName }); if (localUserSettings.LastDeployedStacks == null) { localUserSettings.LastDeployedStacks = new List() { currentStack }; } else { localUserSettings.LastDeployedStacks.Add(currentStack); } } } else { var lastDeployedStacks = new List { new LastDeployedStack( awsAccountId, awsRegion, projectName, new List() { stackName }) }; localUserSettings = new LocalUserSettings { LastDeployedStacks = lastDeployedStacks }; } await WriteLocalUserSettingsFile(localUserSettings); } catch (Exception ex) { throw new FailedToUpdateLocalUserSettingsFileException(DeployToolErrorCode.FailedToUpdateLocalUserSettingsFile, $"Failed to update the local user settings file " + $"to include the last deployed to stack '{stackName}'.", ex); } } /// /// This method updates the local user settings json file by deleting the stack that was most recently used. /// public async Task DeleteLastDeployedStack(string stackName, string projectName, string? awsAccountId, string? awsRegion) { try { if (string.IsNullOrEmpty(projectName)) throw new FailedToUpdateLocalUserSettingsFileException(DeployToolErrorCode.FailedToUpdateLocalUserSettingsFile, "The Project Name is not defined."); if (string.IsNullOrEmpty(awsAccountId) || string.IsNullOrEmpty(awsRegion)) throw new FailedToUpdateLocalUserSettingsFileException(DeployToolErrorCode.FailedToUpdateLocalUserSettingsFile, "The AWS Account Id or Region is not defined."); var localUserSettings = await GetLocalUserSettings(); var lastDeployedStack = localUserSettings?.LastDeployedStacks? .FirstOrDefault(x => x.Exists(awsAccountId, awsRegion, projectName)); if (localUserSettings == null || lastDeployedStack == null) return; lastDeployedStack.Stacks.Remove(stackName); await WriteLocalUserSettingsFile(localUserSettings); } catch (Exception ex) { throw new FailedToUpdateLocalUserSettingsFileException(DeployToolErrorCode.FailedToUpdateLocalUserSettingsFile, $"Failed to update the local user settings file " + $"to delete the stack '{stackName}'.", ex); } } /// /// This method updates the local user settings json file by deleting orphan stacks. /// public async Task CleanOrphanStacks(List deployedStacks, string projectName, string? awsAccountId, string? awsRegion) { try { if (string.IsNullOrEmpty(projectName)) throw new FailedToUpdateLocalUserSettingsFileException(DeployToolErrorCode.FailedToUpdateLocalUserSettingsFile, "The Project Name is not defined."); if (string.IsNullOrEmpty(awsAccountId) || string.IsNullOrEmpty(awsRegion)) throw new FailedToUpdateLocalUserSettingsFileException(DeployToolErrorCode.FailedToUpdateLocalUserSettingsFile, "The AWS Account Id or Region is not defined."); var localUserSettings = await GetLocalUserSettings(); var localStacks = localUserSettings?.LastDeployedStacks? .FirstOrDefault(x => x.Exists(awsAccountId, awsRegion, projectName)); if (localUserSettings == null || localStacks == null || localStacks.Stacks == null) return; var validStacks = deployedStacks.Intersect(localStacks.Stacks); localStacks.Stacks = validStacks.ToList(); await WriteLocalUserSettingsFile(localUserSettings); } catch (Exception ex) { throw new FailedToUpdateLocalUserSettingsFileException(DeployToolErrorCode.FailedToUpdateLocalUserSettingsFile, $"Failed to update the local user settings file " + $"to delete orphan stacks.", ex); } } /// /// This method parses the into a string and writes it to disk. /// private async Task WriteLocalUserSettingsFile(LocalUserSettings deploymentManifestModel) { var localUserSettingsFilePath = GetLocalUserSettingsFilePath(); var settingsFilejsonString = JsonConvert.SerializeObject(deploymentManifestModel, new JsonSerializerSettings { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore }); await _fileManager.WriteAllTextAsync(localUserSettingsFilePath, settingsFilejsonString); return localUserSettingsFilePath; } /// /// This method parses the local user settings file into a /// public async Task GetLocalUserSettings() { try { var localUserSettingsFilePath = GetLocalUserSettingsFilePath(); if (!_fileManager.Exists(localUserSettingsFilePath)) return null; var settingsFilejsonString = await _fileManager.ReadAllTextAsync(localUserSettingsFilePath); return JsonConvert.DeserializeObject(settingsFilejsonString); } catch (Exception ex) { throw new InvalidLocalUserSettingsFileException(DeployToolErrorCode.InvalidLocalUserSettingsFile, "The Local User Settings file is invalid.", ex); } } /// /// This method returns the path at which the local user settings file will be stored. /// private string GetLocalUserSettingsFilePath() { var deployToolWorkspace = _directoryManager.GetDirectoryInfo(_workspaceMetadata.DeployToolWorkspaceDirectoryRoot).FullName; var localUserSettingsFileFullPath = Path.Combine(deployToolWorkspace, LOCAL_USER_SETTINGS_FILE_NAME); return localUserSettingsFileFullPath; } } }