diff --git a/src/Microsoft.Tye.Hosting/DockerRunner.cs b/src/Microsoft.Tye.Hosting/DockerRunner.cs index 6724f352..c02d914b 100644 --- a/src/Microsoft.Tye.Hosting/DockerRunner.cs +++ b/src/Microsoft.Tye.Hosting/DockerRunner.cs @@ -204,21 +204,6 @@ namespace Microsoft.Tye.Hosting var hostname = "host.docker.internal"; var dockerImage = docker.Image ?? service.Description.Name; - if (docker.DockerFile != null) - { - var dockerBuildResult = await ProcessUtil.RunAsync( - $"docker", - $"build \"{docker.DockerFileContext?.FullName}\" -t {dockerImage} -f \"{docker.DockerFile}\"", - docker.WorkingDirectory, - throwOnError: false); - - if (dockerBuildResult.ExitCode != 0) - { - _logger.LogInformation("Running docker command with exception info {ExceptionStdOut} {ExceptionStdErr}", dockerBuildResult.StandardOutput, dockerBuildResult.StandardError); - throw new CommandException("'docker build' failed."); - } - } - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { // See: https://github.com/docker/for-linux/issues/264 @@ -229,9 +214,7 @@ namespace Microsoft.Tye.Hosting hostname = addresses[0].ToString(); } - var dockerInfo = new DockerInformation(new Task[service.Description.Replicas]); - - async Task RunDockerContainer(IEnumerable<(int ExternalPort, int Port, int? ContainerPort, string? Protocol)> ports) + async Task RunDockerContainer(IEnumerable<(int ExternalPort, int Port, int? ContainerPort, string? Protocol)> ports, CancellationToken cancellationToken) { var hasPorts = ports.Any(); @@ -282,10 +265,10 @@ namespace Microsoft.Tye.Hosting // // The way we do proxying here doesn't really work for multi-container scenarios on linux // without some more setup. - application.PopulateEnvironment(service, (key, value) => environment[key] = value, hostname); + application.PopulateEnvironment(service, (key, value) => environment[key] = value, hostname!); environment["APP_INSTANCE"] = replica; - environment["CONTAINER_HOST"] = hostname; + environment["CONTAINER_HOST"] = hostname!; status.Environment = environment; @@ -321,7 +304,7 @@ namespace Microsoft.Tye.Hosting "docker", command, throwOnError: false, - cancellationToken: dockerInfo.StoppingTokenSource.Token, + cancellationToken: cancellationToken, outputDataReceived: data => service.Logs.OnNext($"[{replica}]: {data}"), errorDataReceived: data => service.Logs.OnNext($"[{replica}]: {data}")); @@ -355,7 +338,7 @@ namespace Microsoft.Tye.Hosting if (!string.IsNullOrEmpty(dockerNetwork)) { - status.DockerNetworkAlias = docker.NetworkAlias ?? serviceDescription.Name; + status.DockerNetworkAlias = docker.NetworkAlias ?? serviceDescription!.Name; var networkCommand = $"network connect {dockerNetwork} {replica} --alias {status.DockerNetworkAlias}"; @@ -370,7 +353,7 @@ namespace Microsoft.Tye.Hosting var sentStartedEvent = false; - while (!dockerInfo.StoppingTokenSource.Token.IsCancellationRequested) + while (!cancellationToken.IsCancellationRequested) { if (sentStartedEvent) { @@ -396,7 +379,7 @@ namespace Microsoft.Tye.Hosting service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Started, status)); sentStartedEvent = true; - await using var _ = dockerInfo.StoppingTokenSource.Token.Register(() => status.StoppingTokenSource.Cancel()); + await using var _ = cancellationToken.Register(() => status.StoppingTokenSource.Cancel()); _logger.LogInformation("Collecting docker logs for {ContainerName}.", replica); @@ -473,34 +456,79 @@ namespace Microsoft.Tye.Hosting service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Removed, status)); }; - if (serviceDescription.Bindings.Count > 0) + async Task DockerBuildAsync(CancellationToken cancellationToken) { - // Each replica is assigned a list of internal ports, one mapped to each external - // port - for (var i = 0; i < serviceDescription.Replicas; i++) + if (docker.DockerFile != null) { - var ports = new List<(int, int, int?, string?)>(); - foreach (var binding in serviceDescription.Bindings) - { - if (binding.Port == null) - { - continue; - } + _logger.LogInformation("Building docker image {Image} from docker file", dockerImage); - ports.Add((binding.Port.Value, binding.ReplicaPorts[i], binding.ContainerPort, binding.Protocol)); + void Log(string data) + { + _logger.LogInformation("[" + serviceDescription!.Name + "]:" + data); + service.Logs.OnNext(data); } - dockerInfo.Tasks[i] = RunDockerContainer(ports); + var dockerBuildResult = await ProcessUtil.RunAsync( + $"docker", + $"build \"{docker.DockerFileContext?.FullName}\" -t {dockerImage} -f \"{docker.DockerFile}\"", + outputDataReceived: Log, + errorDataReceived: Log, + workingDirectory: docker.WorkingDirectory, + cancellationToken: cancellationToken, + throwOnError: false); + + if (dockerBuildResult.ExitCode != 0) + { + throw new CommandException("'docker build' failed."); + } } } - else + + Task DockerRunAsync(CancellationToken cancellationToken) { - for (var i = 0; i < service.Description.Replicas; i++) + var tasks = new Task[serviceDescription!.Replicas]; + + if (serviceDescription.Bindings.Count > 0) + { + // Each replica is assigned a list of internal ports, one mapped to each external + // port + for (var i = 0; i < serviceDescription.Replicas; i++) + { + var ports = new List<(int, int, int?, string?)>(); + foreach (var binding in serviceDescription.Bindings) + { + if (binding.Port == null) + { + continue; + } + + ports.Add((binding.Port.Value, binding.ReplicaPorts[i], binding.ContainerPort, binding.Protocol)); + } + + tasks[i] = RunDockerContainer(ports, cancellationToken); + } + } + else { - dockerInfo.Tasks[i] = RunDockerContainer(Enumerable.Empty<(int, int, int?, string?)>()); + for (var i = 0; i < service.Description.Replicas; i++) + { + tasks[i] = RunDockerContainer(Enumerable.Empty<(int, int, int?, string?)>(), cancellationToken); + } } + + return Task.WhenAll(tasks); + } + + async Task BuildAndRunAsync(CancellationToken cancellationToken) + { + await DockerBuildAsync(cancellationToken); + + await DockerRunAsync(cancellationToken); } + var dockerInfo = new DockerInformation(); + dockerInfo.Task = BuildAndRunAsync(dockerInfo.StoppingTokenSource.Token); + service.Items[typeof(DockerInformation)] = dockerInfo; } @@ -511,7 +539,7 @@ namespace Microsoft.Tye.Hosting { var container = replica["container"]; await ProcessUtil.RunAsync("docker", $"rm -f {container}", throwOnError: false); - _logger.LogInformation("removed contaienr {container} from previous run", container); + _logger.LogInformation("removed container {container} from previous run", container); } _replicaRegistry.DeleteStore(DockerReplicaStore); @@ -547,7 +575,7 @@ namespace Microsoft.Tye.Hosting { di.StoppingTokenSource.Cancel(); - return Task.WhenAll(di.Tasks); + return di.Task; } return Task.CompletedTask; @@ -555,12 +583,7 @@ namespace Microsoft.Tye.Hosting private class DockerInformation { - public DockerInformation(Task[] tasks) - { - Tasks = tasks; - } - - public Task[] Tasks { get; } + public Task Task { get; set; } = default!; public CancellationTokenSource StoppingTokenSource { get; } = new CancellationTokenSource(); } diff --git a/test/Test.Infrastructure/TestHelpers.cs b/test/Test.Infrastructure/TestHelpers.cs index 6286df92..7f86f422 100644 --- a/test/Test.Infrastructure/TestHelpers.cs +++ b/test/Test.Infrastructure/TestHelpers.cs @@ -19,7 +19,7 @@ namespace Test.Infrastructure { public static class TestHelpers { - private static readonly TimeSpan WaitForServicesTimeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(20); + private static readonly TimeSpan WaitForServicesTimeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromMinutes(1); // https://github.com/dotnet/aspnetcore/blob/5a0526dfd991419d5bce0d8ea525b50df2e37b04/src/Testing/src/TestPathUtilities.cs // This can get into a bad pattern for having crazy paths in places. Eventually, especially if we use helix,