diff --git a/src/Microsoft.Tye.Hosting/DockerImagePuller.cs b/src/Microsoft.Tye.Hosting/DockerImagePuller.cs new file mode 100644 index 00000000..e88dfdce --- /dev/null +++ b/src/Microsoft.Tye.Hosting/DockerImagePuller.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Tye.Hosting.Model; + +namespace Microsoft.Tye.Hosting +{ + public class DockerImagePuller : IApplicationProcessor + { + private readonly ILogger _logger; + + public DockerImagePuller(ILogger logger) + { + _logger = logger; + } + + public async Task StartAsync(Application application) + { + var images = new HashSet(); + + foreach (var s in application.Services) + { + if (s.Value.Description.RunInfo is DockerRunInfo docker) + { + images.Add(docker.Image); + } + } + + // No images, no docker skip it. + if (images.Count == 0) + { + return; + } + + if (!await DockerDetector.Instance.IsDockerInstalled.Value) + { + _logger.LogError("Unable to detect docker installation. Docker is not installed."); + + throw new CommandException("Docker is not installed."); + } + + if (!await DockerDetector.Instance.IsDockerConnectedToDaemon.Value) + { + _logger.LogError("Unable to connect to docker daemon. Docker is not running."); + + throw new CommandException("Docker is not running."); + } + + var tasks = new Task[images.Count]; + var index = 0; + foreach (var image in images) + { + tasks[index++] = PullContainerAsync(image); + } + + await Task.WhenAll(tasks); + } + + private async Task PullContainerAsync(string image) + { + await Task.Yield(); + + var command = $"pull {image}"; + + _logger.LogInformation("Running docker command {command}", command); + + var result = await ProcessUtil.RunAsync( + "docker", + command, + outputDataReceived: data => _logger.LogInformation("{Image}: " + data, image), + errorDataReceived: data => _logger.LogInformation("{Image}: " + data, image), + throwOnError: false); + + if (result.ExitCode != 0) + { + throw new CommandException("Docker pull command failed"); + } + } + + public Task StopAsync(Application application) + { + return Task.CompletedTask; + } + } +} diff --git a/src/Microsoft.Tye.Hosting/DockerRunner.cs b/src/Microsoft.Tye.Hosting/DockerRunner.cs index 8d06be87..a07d4041 100644 --- a/src/Microsoft.Tye.Hosting/DockerRunner.cs +++ b/src/Microsoft.Tye.Hosting/DockerRunner.cs @@ -61,22 +61,6 @@ namespace Microsoft.Tye.Hosting private async Task StartContainerAsync(Application application, Service service, DockerRunInfo docker) { - if (!await DockerDetector.Instance.IsDockerInstalled.Value) - { - _logger.LogError("Unable to start docker container for service {ServiceName}, Docker is not installed.", service.Description.Name); - - service.Logs.OnNext($"Unable to start docker container for service {service.Description.Name}, Docker is not installed."); - return; - } - - if (!await DockerDetector.Instance.IsDockerConnectedToDaemon.Value) - { - _logger.LogError("Unable to start docker container for service {ServiceName}, Docker is not running.", service.Description.Name); - - service.Logs.OnNext($"Unable to start docker container for service {service.Description.Name}, Docker is not running."); - return; - } - var serviceDescription = service.Description; var environmentArguments = ""; var volumes = ""; diff --git a/src/Microsoft.Tye.Hosting/TyeHost.cs b/src/Microsoft.Tye.Hosting/TyeHost.cs index dcf4886b..1bc84d06 100644 --- a/src/Microsoft.Tye.Hosting/TyeHost.cs +++ b/src/Microsoft.Tye.Hosting/TyeHost.cs @@ -60,13 +60,18 @@ namespace Microsoft.Tye.Hosting public async Task RunAsync() { - await StartAsync(); - - var waitForStop = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _lifetime?.ApplicationStopping.Register(obj => waitForStop.TrySetResult(null), null); - await waitForStop.Task; + try + { + await StartAsync(); - await StopAsync(); + var waitForStop = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _lifetime?.ApplicationStopping.Register(obj => waitForStop.TrySetResult(null), null); + await waitForStop.Task; + } + finally + { + await StopAsync(); + } } public async Task StartAsync() @@ -91,14 +96,7 @@ namespace Microsoft.Tye.Hosting _logger.LogInformation("Dashboard running on {Address}", app.Addresses.First()); - try - { - await _processor.StartAsync(_application); - } - catch (Exception ex) - { - _logger.LogError(0, ex, "Failed to launch application"); - } + await _processor.StartAsync(_application); return app; } @@ -280,6 +278,7 @@ namespace Microsoft.Tye.Hosting new PortAssigner(logger), new ProxyService(logger), new HttpProxyService(logger), + new DockerImagePuller(logger), new DockerRunner(logger, replicaRegistry), new ProcessRunner(logger, replicaRegistry, ProcessRunnerOptions.FromArgs(args, servicesToDebug)) };