// 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.IO.Compression; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using AWS.Deploy.Common; using AWS.Deploy.Common.IO; namespace AWS.Deploy.Orchestration.Utilities { public interface IZipFileManager { Task CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName); } public class ZipFileManager : IZipFileManager { private readonly ICommandLineWrapper _commandLineWrapper; private readonly IFileManager _fileManager; public ZipFileManager(ICommandLineWrapper commandLineWrapper, IFileManager fileManager) { _commandLineWrapper = commandLineWrapper; _fileManager = fileManager; } public async Task CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { ZipFile.CreateFromDirectory(sourceDirectoryName, destinationArchiveFileName); } else { await BuildZipForLinux(sourceDirectoryName, destinationArchiveFileName); } } /// /// Use the native zip utility if it exist which will maintain linux/osx file permissions. /// private async Task BuildZipForLinux(string sourceDirectoryName, string destinationArchiveFileName) { var zipCLI = FindExecutableInPath("zip"); if (string.IsNullOrEmpty(zipCLI)) throw new FailedToCreateZipFileException(DeployToolErrorCode.FailedToFindZipUtility, "Failed to find the \"zip\" utility program in path. This program is required to maintain Linux file permissions in the zip archive.", null); var args = new StringBuilder($"\"{destinationArchiveFileName}\""); var allFiles = GetFilesToIncludeInArchive(sourceDirectoryName); foreach (var kvp in allFiles) { args.AppendFormat(" \"{0}\"", kvp.Key); } var command = $"{zipCLI} {args}"; var result = await _commandLineWrapper.TryRunWithResult(command, sourceDirectoryName); if (result.ExitCode != 0) { var errorMessage = "We were unable to create a zip archive of the packaged application."; if (!string.IsNullOrEmpty(result.StandardError)) errorMessage = $"We were unable to create a zip archive of the packaged application due to the following reason:{Environment.NewLine}{result.StandardError}"; errorMessage += $"{Environment.NewLine}Normally this indicates a problem running the \"zip\" utility. Make sure that application is installed and available in your PATH."; throw new FailedToCreateZipFileException(DeployToolErrorCode.ZipUtilityFailedToZip, errorMessage, result.ExitCode); } } /// /// Get the list of files from the publish folder that should be added to the zip archive. /// This will skip all files in the runtimes folder because they have already been flatten to the root. /// private IDictionary GetFilesToIncludeInArchive(string publishLocation) { var includedFiles = new Dictionary(); var allFiles = Directory.GetFiles(publishLocation, "*.*", SearchOption.AllDirectories); foreach (var file in allFiles) { var relativePath = Path.GetRelativePath(publishLocation, file); includedFiles[relativePath] = file; } return includedFiles; } /// /// A collection of known paths for common utilities that are usually not found in the path /// private readonly IDictionary KNOWN_LOCATIONS = new Dictionary(StringComparer.OrdinalIgnoreCase) { {"zip", @"/usr/bin/zip" } }; /// /// Search the path environment variable for the command given. /// /// 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) { if (_fileManager.Exists(command)) return Path.GetFullPath(command); Func quoteRemover = x => { if (x.StartsWith("\"")) x = x.Substring(1); if (x.EndsWith("\"")) x = x.Substring(0, x.Length - 1); return x; }; var envPath = Environment.GetEnvironmentVariable("PATH") ?? ""; foreach (var path in envPath.Split(Path.PathSeparator)) { try { var fullPath = Path.Combine(quoteRemover(path), command); if (_fileManager.Exists(fullPath)) return fullPath; } catch (Exception) { // Catch exceptions and continue if there are invalid characters in the user's path. } } if (KNOWN_LOCATIONS.ContainsKey(command) && _fileManager.Exists(KNOWN_LOCATIONS[command])) return KNOWN_LOCATIONS[command]; return null; } } }