tool/TeamCity.Docker/BuildCommand.cs (159 lines of code) (raw):
// ReSharper disable ClassNeverInstantiated.Global
namespace TeamCity.Docker
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Generic;
using global::Docker.DotNet;
using global::Docker.DotNet.Models;
using IoC;
using Model;
internal class BuildCommand: ICommand<IBuildOptions>
{
[NotNull] private readonly ILogger _logger;
[NotNull] private readonly IMessageLogger _messageLogger;
[NotNull] private readonly CancellationTokenSource _cancellationTokenSource;
[NotNull] private readonly IFileSystem _fileSystem;
[NotNull] private readonly IPathService _pathService;
[NotNull] private readonly IBuildOptions _options;
[NotNull] private readonly IConfigurationExplorer _configurationExplorer;
[NotNull] private readonly IFactory<IGraph<IArtifact, Dependency>, IEnumerable<Template>> _buildGraphFactory;
[NotNull] private readonly IFactory<IEnumerable<IGraph<IArtifact, Dependency>>, IGraph<IArtifact, Dependency>> _buildGraphsFactory;
[NotNull] private readonly IFactory<NodesDescription, IEnumerable<INode<IArtifact>>> _nodesDescriptionFactory;
[NotNull] private readonly IBuildPathProvider _buildPathProvider;
[NotNull] private readonly IContextFactory _contextFactory;
[NotNull] private readonly IStreamService _streamService;
[NotNull] private readonly IDockerClient _dockerClient;
public BuildCommand(
[NotNull] ILogger logger,
[NotNull] IMessageLogger messageLogger,
[NotNull] CancellationTokenSource cancellationTokenSource,
[NotNull] IFileSystem fileSystem,
[NotNull] IPathService pathService,
[NotNull] IBuildOptions options,
[NotNull] IConfigurationExplorer configurationExplorer,
[NotNull] IFactory<IGraph<IArtifact, Dependency>, IEnumerable<Template>> buildGraphFactory,
[NotNull] IFactory<IEnumerable<IGraph<IArtifact, Dependency>>, IGraph<IArtifact, Dependency>> buildGraphsFactory,
[NotNull] IFactory<NodesDescription, IEnumerable<INode<IArtifact>>> nodesDescriptionFactory,
[NotNull] IBuildPathProvider buildPathProvider,
[NotNull] IContextFactory contextFactory,
[NotNull] IStreamService streamService,
[NotNull] IDockerClient dockerClient)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_messageLogger = messageLogger ?? throw new ArgumentNullException(nameof(messageLogger));
_cancellationTokenSource = cancellationTokenSource ?? throw new ArgumentNullException(nameof(cancellationTokenSource));
_fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
_pathService = pathService ?? throw new ArgumentNullException(nameof(pathService));
_options = options ?? throw new ArgumentNullException(nameof(options));
_configurationExplorer = configurationExplorer ?? throw new ArgumentNullException(nameof(configurationExplorer));
_buildGraphFactory = buildGraphFactory ?? throw new ArgumentNullException(nameof(buildGraphFactory));
_buildGraphsFactory = buildGraphsFactory ?? throw new ArgumentNullException(nameof(buildGraphsFactory));
_nodesDescriptionFactory = nodesDescriptionFactory ?? throw new ArgumentNullException(nameof(nodesDescriptionFactory));
_buildPathProvider = buildPathProvider ?? throw new ArgumentNullException(nameof(buildPathProvider));
_contextFactory = contextFactory ?? throw new ArgumentNullException(nameof(contextFactory));
_streamService = streamService ?? throw new ArgumentNullException(nameof(streamService));
_dockerClient = dockerClient ?? throw new ArgumentNullException(nameof(dockerClient));
}
public async Task<Result> Run()
{
var templates = _configurationExplorer.Explore(_options.SourcePath, _options.ConfigurationFiles);
if (templates.State == Result.Error)
{
return Result.Error;
}
var buildGraphResult = _buildGraphFactory.Create(templates.Value);
if (buildGraphResult.State == Result.Error)
{
return Result.Error;
}
var buildGraphsResult = _buildGraphsFactory.Create(buildGraphResult.Value);
if (buildGraphsResult.State == Result.Error)
{
return Result.Error;
}
var buildGraphs = buildGraphsResult.Value.ToList();
if (!buildGraphs.Any())
{
_logger.Log("Nothing to build.", Result.Error);
return Result.Error;
}
var dockerFilesRootPath = _fileSystem.UniqueName;
var contextStreamResult = await _contextFactory.Create(dockerFilesRootPath, buildGraphResult.Value.Nodes.Select(i => i.Value).OfType<Image>().Select(i => i.File));
if (contextStreamResult.State == Result.Error)
{
return Result.Error;
}
var contextStream = contextStreamResult.Value;
using (contextStream)
using (_logger.CreateBlock("Build"))
{
foreach (var buildGraph in buildGraphs)
{
var nameResult = _nodesDescriptionFactory.Create(buildGraph.Nodes);
var name = nameResult.State != Result.Error ? nameResult.Value.Name : "Unnamed graph";
if (!string.IsNullOrWhiteSpace(_options.FilterRegex) && !new Regex(_options.FilterRegex).IsMatch(name))
{
_logger.Log($"\"{name}\" was skipped according to filter \"{_options.FilterRegex}\".", Result.Warning);
continue;
}
using (_logger.CreateBlock(name))
{
var buildPath = _buildPathProvider.GetPath(buildGraph).ToList();
foreach (var buildNode in buildPath)
{
switch (buildNode.Value)
{
case Image image:
var dockerFile = image.File;
using (_logger.CreateBlock(dockerFile.ToString()))
{
var id = Guid.NewGuid().ToString();
var labels = new Dictionary<string, string> { { "InternalImageId", id } };
var tags = (
from tag in dockerFile.Tags
select $"{dockerFile.ImageId}:{tag}")
.Distinct()
.ToList();
contextStream.Position = 0;
var dockerFilePathInContext = _pathService.Normalize(Path.Combine(dockerFilesRootPath, dockerFile.Path));
var buildParameters = new ImageBuildParameters
{
Dockerfile = dockerFilePathInContext,
Tags = tags,
PullParent = true,
Labels = labels
};
using (var buildEventStream = await _dockerClient.Images.BuildImageFromDockerfileAsync(
contextStream,
buildParameters,
_cancellationTokenSource.Token))
{
_streamService.ProcessLines(buildEventStream, line => { _messageLogger.Log(line); });
}
var filter = new Dictionary<string, IDictionary<string, bool>>
{
{"label", labels.ToDictionary(i => $"{i.Key}={i.Value}", _ => true)}
};
var images = await _dockerClient.Images.ListImagesAsync(new ImagesListParameters { Filters = filter });
if (images.Count == 0)
{
_logger.Log($"Error while building the image {dockerFile}", Result.Error);
return Result.Error;
}
}
break;
}
}
}
}
}
return Result.Success;
}
}
}