tool/TeamCity.Docker/ContextFactory.cs (128 lines of code) (raw):

// ReSharper disable ClassNeverInstantiated.Global namespace TeamCity.Docker { using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; using ICSharpCode.SharpZipLib.Tar; using IoC; using Model; internal class ContextFactory : IContextFactory { private readonly IOptions _options; private readonly ILogger _logger; private readonly IPathService _pathService; private readonly IFileSystem _fileSystem; private readonly IStreamService _streamService; private static readonly int Chmod = Convert.ToInt32("100755", 8); public ContextFactory( [NotNull] IOptions options, [NotNull] ILogger logger, [NotNull] IPathService pathService, [NotNull] IFileSystem fileSystem, [NotNull] IStreamService streamService) { _options = options ?? throw new ArgumentNullException(nameof(options)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _pathService = pathService ?? throw new ArgumentNullException(nameof(pathService)); _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); _streamService = streamService ?? throw new ArgumentNullException(nameof(streamService)); } public async Task<Result<Stream>> Create(string dockerFilesRootPath, IEnumerable<Dockerfile> dockerFiles) { if (dockerFilesRootPath == null) { throw new ArgumentNullException(nameof(dockerFilesRootPath)); } if (dockerFiles == null) { throw new ArgumentNullException(nameof(dockerFiles)); } using (_logger.CreateBlock("Context")) { var context = new MemoryStream(); await using var archive = new TarOutputStream(context, Encoding.UTF8) {IsStreamOwner = false}; var number = 0; if (!string.IsNullOrWhiteSpace(_options.ContextPath)) { var path = Path.GetFullPath(_options.ContextPath); if (!_fileSystem.IsDirectoryExist(path)) { throw new InvalidOperationException($"The docker build context directory \"{path}\" does not exist."); } foreach (var file in _fileSystem.EnumerateFileSystemEntries(path)) { if (!_fileSystem.IsFileExist(file)) { continue; } number++; await using var fileStream = _fileSystem.OpenRead(file); var filePathInArchive = _pathService.Normalize(Path.GetRelativePath(path, file)); var result = await AddEntry(archive, filePathInArchive, fileStream); _logger.Details($"{number:0000} \"{filePathInArchive}\" was added."); if (result == Result.Error) { return new Result<Stream>(new MemoryStream(), Result.Error); } } _logger.Log($"{number} files were added to docker build context from the directory \"{_options.ContextPath}\" (\"{path}\")."); } else { _logger.Log("The path for docker build context was not defined.", Result.Warning); } number = 0; using (_logger.CreateBlock("Docker files")) { foreach (var dockerfile in dockerFiles) { number++; var dockerFilePathInArchive = _pathService.Normalize(Path.Combine(dockerFilesRootPath, dockerfile.Path)); await using var dockerFileStream = new MemoryStream(); await using(var writer = new StreamWriter(dockerFileStream, Encoding.UTF8, 0xff, true)) { foreach (var line in dockerfile.Lines) { await writer.WriteLineAsync(line.Text); } } dockerFileStream.Position = 0; var result = await AddEntry(archive, dockerFilePathInArchive, dockerFileStream); if (result == Result.Error) { return new Result<Stream>(new MemoryStream(), Result.Error); } _logger.Log($"{number:0000} \"{dockerFilePathInArchive}\" was added ({dockerFileStream.Length} bytes)."); } } archive.Close(); return new Result<Stream>(context); } } private async Task<Result> AddEntry([NotNull] TarOutputStream archive, [NotNull] string filePathInArchive, [NotNull] Stream contentStream) { if (archive == null) { throw new ArgumentNullException(nameof(archive)); } if (filePathInArchive == null) { throw new ArgumentNullException(nameof(filePathInArchive)); } if (contentStream == null) { throw new ArgumentNullException(nameof(contentStream)); } var entry = TarEntry.CreateTarEntry(filePathInArchive); entry.Size = contentStream.Length; entry.TarHeader.Mode = Chmod; //chmod 755 archive.PutNextEntry(entry); var result = await _streamService.Copy(contentStream, archive, $"Adding {filePathInArchive}"); archive.CloseEntry(); return result; } } }