Browse Source

Support podman (#1014)

jkotalik/testBuild
Tom Deseyn 5 years ago
committed by GitHub
parent
commit
c3f3c6cb91
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      src/Microsoft.Tye.Core/ApplicationBuilder.cs
  2. 2
      src/Microsoft.Tye.Core/ApplicationFactory.cs
  3. 9
      src/Microsoft.Tye.Core/BuildDockerImageStep.cs
  4. 2
      src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs
  5. 12
      src/Microsoft.Tye.Core/ConfigModel/ContainerEngineType.cs
  6. 181
      src/Microsoft.Tye.Core/ContainerEngine.cs
  7. 4
      src/Microsoft.Tye.Core/DeployApplicationKubernetesManifestStep.cs
  8. 6
      src/Microsoft.Tye.Core/DockerContainerBuilder.cs
  9. 55
      src/Microsoft.Tye.Core/DockerDetector.cs
  10. 5
      src/Microsoft.Tye.Core/DockerPush.cs
  11. 13
      src/Microsoft.Tye.Core/ProcessUtil.cs
  12. 2
      src/Microsoft.Tye.Core/PushDockerImageStep.cs
  13. 16
      src/Microsoft.Tye.Core/Serialization/ConfigApplicationParser.cs
  14. 4
      src/Microsoft.Tye.Core/ValidateIngressStep.cs
  15. 7
      src/Microsoft.Tye.Extensions.Configuration/TyeConfigurationExtensions.cs
  16. 23
      src/Microsoft.Tye.Hosting/DockerImagePuller.cs
  17. 82
      src/Microsoft.Tye.Hosting/DockerRunner.cs
  18. 5
      src/Microsoft.Tye.Hosting/Model/Application.cs
  19. 4
      src/Microsoft.Tye.Hosting/Model/DockerVolume.cs
  20. 7
      src/Microsoft.Tye.Hosting/ProcessRunner.cs
  21. 6
      src/Microsoft.Tye.Hosting/TransformProjectsIntoContainers.cs
  22. 5
      src/schema/tye-schema.json
  23. 2
      src/tye/ApplicationBuilderExtensions.cs
  24. 15
      test/E2ETest/TyeRunTests.cs
  25. 18
      test/Test.Infrastructure/DockerAssert.cs
  26. 5
      test/Test.Infrastructure/SkipIfDockerNotRunningAttribute.cs
  27. 4
      test/Test.Infrastructure/TestHelpers.cs

5
src/Microsoft.Tye.Core/ApplicationBuilder.cs

@ -9,10 +9,11 @@ namespace Microsoft.Tye
{ {
public sealed class ApplicationBuilder public sealed class ApplicationBuilder
{ {
public ApplicationBuilder(FileInfo source, string name) public ApplicationBuilder(FileInfo source, string name, ContainerEngine containerEngine)
{ {
Source = source; Source = source;
Name = name; Name = name;
ContainerEngine = containerEngine;
} }
public FileInfo Source { get; set; } public FileInfo Source { get; set; }
@ -23,6 +24,8 @@ namespace Microsoft.Tye
public ContainerRegistry? Registry { get; set; } public ContainerRegistry? Registry { get; set; }
public ContainerEngine ContainerEngine { get; set; }
public List<ExtensionConfiguration> Extensions { get; } = new List<ExtensionConfiguration>(); public List<ExtensionConfiguration> Extensions { get; } = new List<ExtensionConfiguration>();
public List<ServiceBuilder> Services { get; } = new List<ServiceBuilder>(); public List<ServiceBuilder> Services { get; } = new List<ServiceBuilder>();

2
src/Microsoft.Tye.Core/ApplicationFactory.cs

@ -30,7 +30,7 @@ namespace Microsoft.Tye
var rootConfig = ConfigFactory.FromFile(source); var rootConfig = ConfigFactory.FromFile(source);
rootConfig.Validate(); rootConfig.Validate();
var root = new ApplicationBuilder(source, rootConfig.Name!) var root = new ApplicationBuilder(source, rootConfig.Name!, new ContainerEngine(rootConfig.ContainerEngineType))
{ {
Namespace = rootConfig.Namespace Namespace = rootConfig.Namespace
}; };

9
src/Microsoft.Tye.Core/BuildDockerImageStep.cs

@ -25,14 +25,9 @@ namespace Microsoft.Tye
return; return;
} }
if (!await DockerDetector.Instance.IsDockerInstalled.Value) if (!application.ContainerEngine.IsUsable(out string? unusableReason))
{ {
throw new CommandException($"Cannot generate a docker image for '{service.Name}' because docker is not installed."); throw new CommandException($"Cannot generate a docker image for '{service.Name}' because {unusableReason}.");
}
if (!await DockerDetector.Instance.IsDockerConnectedToDaemon.Value)
{
throw new CommandException($"Cannot generate a docker image for '{service.Name}' because docker is not running.");
} }
if (project is DotnetProjectServiceBuilder dotnetProject) if (project is DotnetProjectServiceBuilder dotnetProject)

2
src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs

@ -28,6 +28,8 @@ namespace Microsoft.Tye.ConfigModel
public string? Registry { get; set; } public string? Registry { get; set; }
public ContainerEngineType? ContainerEngineType { get; set; }
public string? Network { get; set; } public string? Network { get; set; }
public List<Dictionary<string, object>> Extensions { get; set; } = new List<Dictionary<string, object>>(); public List<Dictionary<string, object>> Extensions { get; set; } = new List<Dictionary<string, object>>();

12
src/Microsoft.Tye.Core/ConfigModel/ContainerEngineType.cs

@ -0,0 +1,12 @@
// 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.
namespace Microsoft.Tye.ConfigModel
{
public enum ContainerEngineType
{
Docker,
Podman
}
}

181
src/Microsoft.Tye.Core/ContainerEngine.cs

@ -0,0 +1,181 @@
// 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.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Tye.ConfigModel;
namespace Microsoft.Tye
{
public class ContainerEngine
{
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(10);
// Used by tests:
public static ContainerEngine? s_default;
public static ContainerEngine Default
=> (s_default ??= new ContainerEngine(default));
private bool _isUsable { get; }
private string? _unusableReason;
private bool _isPodman;
private string? _containerHost;
private string _aspnetUrlsHost;
public string AspNetUrlsHost => _aspnetUrlsHost;
public string? ContainerHost => _containerHost;
public Task<int> ExecuteAsync(
string args,
string? workingDir = null,
Action<string>? stdOut = null,
Action<string>? stdErr = null,
params (string key, string value)[] environmentVariables)
=> ProcessUtil.ExecuteAsync(CommandName, args, workingDir, stdOut, stdErr, environmentVariables);
public Task<ProcessResult> RunAsync(
string arguments,
string? workingDirectory = null,
bool throwOnError = true,
IDictionary<string, string>? environmentVariables = null,
Action<string>? outputDataReceived = null,
Action<string>? errorDataReceived = null,
Action<int>? onStart = null,
Action<int>? onStop = null,
CancellationToken cancellationToken = default)
=> ProcessUtil.RunAsync(CommandName, arguments, workingDirectory, throwOnError, environmentVariables,
outputDataReceived, errorDataReceived, onStart, onStop, cancellationToken);
private string CommandName
{
get
{
if (!_isUsable)
{
throw new InvalidOperationException($"Container engine is not usable: {_unusableReason}");
}
return _isPodman ? "podman" : "docker";
}
}
public bool IsUsable(out string? unusableReason)
{
unusableReason = _unusableReason;
return _isUsable;
}
public ContainerEngine(ContainerEngineType? containerEngine)
{
_isUsable = true;
_aspnetUrlsHost = "localhost";
if ((!containerEngine.HasValue || containerEngine == ContainerEngineType.Podman) &&
TryUsePodman(ref _unusableReason, ref _containerHost))
{
_isPodman = true;
return;
}
if ((!containerEngine.HasValue || containerEngine == ContainerEngineType.Docker) &&
TryUseDocker(ref _unusableReason, ref _containerHost, ref _aspnetUrlsHost))
{
return;
}
_isUsable = false;
_unusableReason = "container engine is not installed.";
}
private static bool TryUsePodman(ref string? unusableReason, ref string? containerHost)
{
ProcessResult result;
try
{
result = ProcessUtil.RunAsync("podman", "version -f \"{{ .Client.Version }}\"", throwOnError: false, cancellationToken: new CancellationTokenSource(Timeout).Token).Result;
}
catch
{
return false;
}
if (result.ExitCode != 0)
{
unusableReason = $"podman version exited with {result.ExitCode}. Standard error: \"{result.StandardError}\".";
return true;
}
Version minVersion = new Version(3, 1);
if (Version.TryParse(result.StandardOutput, out Version? version) &&
version < minVersion)
{
unusableReason = $"podman version '{result.StandardOutput}' is less than the required '{minVersion}'.";
return true;
}
// Check if podman is configured to allow containers to access host services.
bool hostLoopbackEnabled = false;
string containersConfPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.DoNotVerify),
"containers/containers.conf");
string[] containersConf = File.Exists(containersConfPath) ? File.ReadAllLines(containersConfPath) : Array.Empty<string>();
// Poor man's TOML parsing.
foreach (var line in containersConf)
{
string trimmed = line.Replace(" ", "");
if (trimmed.StartsWith("network_cmd_options=", StringComparison.InvariantCultureIgnoreCase) &&
trimmed.Contains("\"allow_host_loopback=true\""))
{
hostLoopbackEnabled = true;
break;
}
}
if (hostLoopbackEnabled)
{
containerHost = "10.0.2.2";
}
return true;
}
private static bool TryUseDocker(ref string? unusableReason, ref string? containerHost, ref string aspnetUrlsHost)
{
ProcessResult result;
try
{
result = ProcessUtil.RunAsync("docker", "version", throwOnError: false, cancellationToken: new CancellationTokenSource(Timeout).Token).Result;
}
catch
{
return false;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// See: https://github.com/docker/for-linux/issues/264
//
// host.docker.internal is making it's way into linux docker but doesn't work yet
// instead we use the machine IP
var addresses = Dns.GetHostAddresses(Dns.GetHostName());
containerHost = addresses[0].ToString();
// We need to bind to all interfaces on linux since the container -> host communication won't work
// if we use the IP address to reach out of the host. This works fine on osx and windows
// but doesn't work on linux.
aspnetUrlsHost = "*";
}
else
{
containerHost = "host.docker.internal";
}
if (result.ExitCode != 0)
{
unusableReason = "docker is not connected.";
}
return true;
}
}
}

4
src/Microsoft.Tye.Core/DeployApplicationKubernetesManifestStep.cs

@ -46,7 +46,7 @@ namespace Microsoft.Tye
output.WriteDebugLine($"Running 'kubectl apply' in ${ns}"); output.WriteDebugLine($"Running 'kubectl apply' in ${ns}");
output.WriteCommandLine("kubectl", $"apply -f \"{tempFile.FilePath}\""); output.WriteCommandLine("kubectl", $"apply -f \"{tempFile.FilePath}\"");
var capture = output.Capture(); var capture = output.Capture();
var exitCode = await Process.ExecuteAsync( var exitCode = await ProcessUtil.ExecuteAsync(
$"kubectl", $"kubectl",
$"apply -f \"{tempFile.FilePath}\"", $"apply -f \"{tempFile.FilePath}\"",
System.Environment.CurrentDirectory, System.Environment.CurrentDirectory,
@ -81,7 +81,7 @@ namespace Microsoft.Tye
var retries = 0; var retries = 0;
while (!done && retries < 60) while (!done && retries < 60)
{ {
var ingressExitCode = await Process.ExecuteAsync( var ingressExitCode = await ProcessUtil.ExecuteAsync(
"kubectl", "kubectl",
$"get ingress {ingress.Name} -o jsonpath='{{..ip}}'", $"get ingress {ingress.Name} -o jsonpath='{{..ip}}'",
Environment.CurrentDirectory, Environment.CurrentDirectory,

6
src/Microsoft.Tye.Core/DockerContainerBuilder.cs

@ -43,8 +43,7 @@ namespace Microsoft.Tye
output.WriteDebugLine("Running 'docker build'."); output.WriteDebugLine("Running 'docker build'.");
output.WriteCommandLine("docker", $"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\""); output.WriteCommandLine("docker", $"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"");
var capture = output.Capture(); var capture = output.Capture();
var exitCode = await Process.ExecuteAsync( var exitCode = await application.ContainerEngine.ExecuteAsync(
$"docker",
$"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"", $"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"",
new FileInfo(containerService.DockerFile).DirectoryName, new FileInfo(containerService.DockerFile).DirectoryName,
stdOut: capture.StdOut, stdOut: capture.StdOut,
@ -148,8 +147,7 @@ namespace Microsoft.Tye
output.WriteDebugLine("Running 'docker build'."); output.WriteDebugLine("Running 'docker build'.");
output.WriteCommandLine("docker", $"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\""); output.WriteCommandLine("docker", $"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"");
var capture = output.Capture(); var capture = output.Capture();
var exitCode = await Process.ExecuteAsync( var exitCode = await application.ContainerEngine.ExecuteAsync(
$"docker",
$"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"", $"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"",
project.ProjectFile.DirectoryName, project.ProjectFile.DirectoryName,
stdOut: capture.StdOut, stdOut: capture.StdOut,

55
src/Microsoft.Tye.Core/DockerDetector.cs

@ -1,55 +0,0 @@
// 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.Threading;
using System.Threading.Tasks;
namespace Microsoft.Tye
{
public class DockerDetector
{
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(10);
public static DockerDetector Instance { get; } = new DockerDetector();
private DockerDetector()
{
IsDockerInstalled = new Lazy<Task<bool>>(DetectDockerInstalled);
IsDockerConnectedToDaemon = new Lazy<Task<bool>>(DetectDockerConnectedToDaemon);
}
public Lazy<Task<bool>> IsDockerInstalled { get; }
public Lazy<Task<bool>> IsDockerConnectedToDaemon { get; }
private async Task<bool> DetectDockerInstalled()
{
try
{
await ProcessUtil.RunAsync("docker", "version", throwOnError: false, cancellationToken: new CancellationTokenSource(Timeout).Token);
return true;
}
catch (Exception)
{
// Unfortunately, process throws
return false;
}
}
private async Task<bool> DetectDockerConnectedToDaemon()
{
try
{
var result = await ProcessUtil.RunAsync("docker", "version", throwOnError: false, cancellationToken: new CancellationTokenSource(Timeout).Token);
return result.ExitCode == 0;
}
catch (Exception)
{
// Unfortunately, process throws
return false;
}
}
}
}

5
src/Microsoft.Tye.Core/DockerPush.cs

@ -10,7 +10,7 @@ namespace Microsoft.Tye
{ {
internal static class DockerPush internal static class DockerPush
{ {
public static async Task ExecuteAsync(OutputContext output, string imageName, string imageTag) public static async Task ExecuteAsync(OutputContext output, ContainerEngine containerEngine, string imageName, string imageTag)
{ {
if (output is null) if (output is null)
{ {
@ -30,8 +30,7 @@ namespace Microsoft.Tye
output.WriteDebugLine("Running 'docker push'."); output.WriteDebugLine("Running 'docker push'.");
output.WriteCommandLine("docker", $"push {imageName}:{imageTag}"); output.WriteCommandLine("docker", $"push {imageName}:{imageTag}");
var capture = output.Capture(); var capture = output.Capture();
var exitCode = await Process.ExecuteAsync( var exitCode = await containerEngine.ExecuteAsync(
$"docker",
$"push {imageName}:{imageTag}", $"push {imageName}:{imageTag}",
stdOut: capture.StdOut, stdOut: capture.StdOut,
stdErr: capture.StdErr); stdErr: capture.StdErr);

13
src/Microsoft.Tye.Core/ProcessUtil.cs

@ -23,6 +23,17 @@ namespace Microsoft.Tye
private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public static Task<int> ExecuteAsync(
string command,
string args,
string? workingDir = null,
Action<string>? stdOut = null,
Action<string>? stdErr = null,
params (string key, string value)[] environmentVariables)
{
return System.CommandLine.Invocation.Process.ExecuteAsync(command, args, workingDir, stdOut, stdErr, environmentVariables);
}
public static async Task<ProcessResult> RunAsync( public static async Task<ProcessResult> RunAsync(
string filename, string filename,
string arguments, string arguments,
@ -109,7 +120,7 @@ namespace Microsoft.Tye
if (throwOnError && process.ExitCode != 0) if (throwOnError && process.ExitCode != 0)
{ {
processLifetimeTask.TrySetException(new InvalidOperationException($"Command {filename} {arguments} returned exit code {process.ExitCode}")); processLifetimeTask.TrySetException(new InvalidOperationException($"Command {filename} {arguments} returned exit code {process.ExitCode}. Standard error: \"{errorBuilder.ToString()}\""));
} }
else else
{ {

2
src/Microsoft.Tye.Core/PushDockerImageStep.cs

@ -27,7 +27,7 @@ namespace Microsoft.Tye
foreach (var image in service.Outputs.OfType<DockerImageOutput>()) foreach (var image in service.Outputs.OfType<DockerImageOutput>())
{ {
await DockerPush.ExecuteAsync(output, image.ImageName, image.ImageTag); await DockerPush.ExecuteAsync(output, application.ContainerEngine, image.ImageName, image.ImageTag);
output.WriteInfoLine($"Pushed docker image: '{image.ImageName}:{image.ImageTag}'"); output.WriteInfoLine($"Pushed docker image: '{image.ImageName}:{image.ImageTag}'");
} }
} }

16
src/Microsoft.Tye.Core/Serialization/ConfigApplicationParser.cs

@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using Microsoft.Tye.ConfigModel; using Microsoft.Tye.ConfigModel;
using YamlDotNet.RepresentationModel; using YamlDotNet.RepresentationModel;
@ -29,6 +30,21 @@ namespace Tye.Serialization
case "registry": case "registry":
app.Registry = YamlParser.GetScalarValue(key, child.Value); app.Registry = YamlParser.GetScalarValue(key, child.Value);
break; break;
case "containerEngine":
string engine = YamlParser.GetScalarValue(key, child.Value);
if (engine.Equals("docker", StringComparison.InvariantCultureIgnoreCase))
{
app.ContainerEngineType = ContainerEngineType.Docker;
}
else if (engine.Equals("podman", StringComparison.InvariantCultureIgnoreCase))
{
app.ContainerEngineType = ContainerEngineType.Podman;
}
else
{
throw new TyeYamlException($"Unknown container engine: \"{engine}\"");
}
break;
case "ingress": case "ingress":
YamlParser.ThrowIfNotYamlSequence(key, child.Value); YamlParser.ThrowIfNotYamlSequence(key, child.Value);
ConfigIngressParser.HandleIngress((child.Value as YamlSequenceNode)!, app.Ingress); ConfigIngressParser.HandleIngress((child.Value as YamlSequenceNode)!, app.Ingress);

4
src/Microsoft.Tye.Core/ValidateIngressStep.cs

@ -122,7 +122,7 @@ namespace Microsoft.Tye
output.WriteDebugLine($"Running 'minikube addons enable ingress'"); output.WriteDebugLine($"Running 'minikube addons enable ingress'");
output.WriteCommandLine("minikube", "addon enable ingress"); output.WriteCommandLine("minikube", "addon enable ingress");
var capture = output.Capture(); var capture = output.Capture();
var exitCode = await Process.ExecuteAsync( var exitCode = await ProcessUtil.ExecuteAsync(
$"minikube", $"minikube",
$"addons enable ingress", $"addons enable ingress",
System.Environment.CurrentDirectory, System.Environment.CurrentDirectory,
@ -149,7 +149,7 @@ namespace Microsoft.Tye
output.WriteDebugLine($"Running 'kubectl apply'"); output.WriteDebugLine($"Running 'kubectl apply'");
output.WriteCommandLine("kubectl", $"apply -f \"https://aka.ms/tye/ingress/deploy\""); output.WriteCommandLine("kubectl", $"apply -f \"https://aka.ms/tye/ingress/deploy\"");
var capture = output.Capture(); var capture = output.Capture();
var exitCode = await Process.ExecuteAsync( var exitCode = await ProcessUtil.ExecuteAsync(
$"kubectl", $"kubectl",
$"apply -f \"https://aka.ms/tye/ingress/deploy\"", $"apply -f \"https://aka.ms/tye/ingress/deploy\"",
System.Environment.CurrentDirectory); System.Environment.CurrentDirectory);

7
src/Microsoft.Tye.Extensions.Configuration/TyeConfigurationExtensions.cs

@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Net;
using System.Net.Sockets;
namespace Microsoft.Extensions.Configuration namespace Microsoft.Extensions.Configuration
{ {
@ -21,6 +23,11 @@ namespace Microsoft.Extensions.Configuration
return null; return null;
} }
if (IPAddress.TryParse(host, out IPAddress address) && address.AddressFamily == AddressFamily.InterNetworkV6)
{
host = "[" + host + "]";
}
return new Uri(protocol + "://" + host + ":" + port + "/"); return new Uri(protocol + "://" + host + ":" + port + "/");
} }

23
src/Microsoft.Tye.Hosting/DockerImagePuller.cs

@ -36,36 +36,28 @@ namespace Microsoft.Tye.Hosting
return; return;
} }
if (!await DockerDetector.Instance.IsDockerInstalled.Value) if (!application.ContainerEngine.IsUsable(out string? unusableReason))
{ {
_logger.LogError("Unable to detect docker installation. Docker is not installed."); _logger.LogError($"Unable to pull image: {unusableReason}.");
throw new CommandException("Docker is not installed."); throw new CommandException($"Unable to pull image: {unusableReason}.");
}
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 tasks = new Task[images.Count];
var index = 0; var index = 0;
foreach (var image in images) foreach (var image in images)
{ {
tasks[index++] = PullContainerAsync(image); tasks[index++] = PullContainerAsync(application, image);
} }
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
} }
private async Task PullContainerAsync(string image) private async Task PullContainerAsync(Application application, string image)
{ {
await Task.Yield(); await Task.Yield();
var result = await ProcessUtil.RunAsync( var result = await application.ContainerEngine.RunAsync(
"docker",
$"images --filter \"reference={image}\" --format \"{{{{.ID}}}}\"", $"images --filter \"reference={image}\" --format \"{{{{.ID}}}}\"",
throwOnError: false); throwOnError: false);
@ -85,8 +77,7 @@ namespace Microsoft.Tye.Hosting
_logger.LogInformation("Running docker command {command}", command); _logger.LogInformation("Running docker command {command}", command);
result = await ProcessUtil.RunAsync( result = await application.ContainerEngine.RunAsync(
"docker",
command, command,
outputDataReceived: data => _logger.LogInformation("{Image}: " + data, image), outputDataReceived: data => _logger.LogInformation("{Image}: " + data, image),
errorDataReceived: data => _logger.LogInformation("{Image}: " + data, image), errorDataReceived: data => _logger.LogInformation("{Image}: " + data, image),

82
src/Microsoft.Tye.Hosting/DockerRunner.cs

@ -33,7 +33,7 @@ namespace Microsoft.Tye.Hosting
public async Task StartAsync(Application application) public async Task StartAsync(Application application)
{ {
await PurgeFromPreviousRun(); await PurgeFromPreviousRun(application);
var containers = new List<Service>(); var containers = new List<Service>();
@ -100,7 +100,7 @@ namespace Microsoft.Tye.Hosting
if (!string.IsNullOrEmpty(application.Network)) if (!string.IsNullOrEmpty(application.Network))
{ {
var dockerNetworkResult = await ProcessUtil.RunAsync("docker", $"network ls --filter \"name={application.Network}\" --format \"{{{{.ID}}}}\"", throwOnError: false); var dockerNetworkResult = await application.ContainerEngine.RunAsync($"network ls --filter \"name={application.Network}\" --format \"{{{{.ID}}}}\"", throwOnError: false);
if (dockerNetworkResult.ExitCode != 0) if (dockerNetworkResult.ExitCode != 0)
{ {
_logger.LogError("{Network}: Run docker network ls command failed", application.Network); _logger.LogError("{Network}: Run docker network ls command failed", application.Network);
@ -135,7 +135,7 @@ namespace Microsoft.Tye.Hosting
_logger.LogInformation("Running docker command {Command}", command); _logger.LogInformation("Running docker command {Command}", command);
var dockerNetworkResult = await ProcessUtil.RunAsync("docker", command, throwOnError: false); var dockerNetworkResult = await application.ContainerEngine.RunAsync(command, throwOnError: false);
if (dockerNetworkResult.ExitCode != 0) if (dockerNetworkResult.ExitCode != 0)
{ {
@ -148,17 +148,12 @@ namespace Microsoft.Tye.Hosting
// Stash information outside of the application services // Stash information outside of the application services
application.Items[typeof(DockerApplicationInformation)] = new DockerApplicationInformation(dockerNetwork, proxies); application.Items[typeof(DockerApplicationInformation)] = new DockerApplicationInformation(dockerNetwork, proxies);
var tasks = new Task[containers.Count];
var index = 0;
foreach (var s in containers) foreach (var s in containers)
{ {
var docker = (DockerRunInfo)s.Description.RunInfo!; var docker = (DockerRunInfo)s.Description.RunInfo!;
tasks[index++] = StartContainerAsync(application, s, docker, dockerNetwork); StartContainerAsync(application, s, docker, dockerNetwork);
} }
await Task.WhenAll(tasks);
} }
public async Task StopAsync(Application application) public async Task StopAsync(Application application)
@ -195,29 +190,25 @@ namespace Microsoft.Tye.Hosting
_logger.LogInformation("Running docker command {Command}", command); _logger.LogInformation("Running docker command {Command}", command);
// Clean up the network we created // Clean up the network we created
await ProcessUtil.RunAsync("docker", command, throwOnError: false); await application.ContainerEngine.RunAsync(command, throwOnError: false);
} }
} }
private async Task StartContainerAsync(Application application, Service service, DockerRunInfo docker, string? dockerNetwork) private void StartContainerAsync(Application application, Service service, DockerRunInfo docker, string? dockerNetwork)
{ {
var serviceDescription = service.Description; var serviceDescription = service.Description;
var environmentArguments = "";
var volumes = "";
var workingDirectory = docker.WorkingDirectory != null ? $"-w \"{docker.WorkingDirectory}\"" : ""; var workingDirectory = docker.WorkingDirectory != null ? $"-w \"{docker.WorkingDirectory}\"" : "";
var hostname = "host.docker.internal";
var dockerImage = docker.Image ?? service.Description.Name;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) var hostname = application.ContainerEngine.ContainerHost;
if (hostname == null)
{ {
// See: https://github.com/docker/for-linux/issues/264 _logger.LogError("Configuration doesn't allow containers to access services on the host.");
//
// host.docker.internal is making it's way into linux docker but doesn't work yet throw new CommandException("Configuration doesn't allow containers to access services on the host.");
// instead we use the machine IP
var addresses = await Dns.GetHostAddressesAsync(Dns.GetHostName());
hostname = addresses[0].ToString();
} }
var dockerImage = docker.Image ?? service.Description.Name;
async Task RunDockerContainer(IEnumerable<(int ExternalPort, int Port, int? ContainerPort, string? Protocol)> ports, CancellationToken cancellationToken) async Task RunDockerContainer(IEnumerable<(int ExternalPort, int Port, int? ContainerPort, string? Protocol)> ports, CancellationToken cancellationToken)
{ {
var hasPorts = ports.Any(); var hasPorts = ports.Any();
@ -276,17 +267,19 @@ namespace Microsoft.Tye.Hosting
status.Environment = environment; status.Environment = environment;
var environmentArguments = "";
foreach (var pair in environment) foreach (var pair in environment)
{ {
environmentArguments += $"-e \"{pair.Key}={pair.Value}\" "; environmentArguments += $"-e \"{pair.Key}={pair.Value}\" ";
} }
var volumes = "";
foreach (var volumeMapping in docker.VolumeMappings) foreach (var volumeMapping in docker.VolumeMappings)
{ {
if (volumeMapping.Source != null) if (volumeMapping.Source != null)
{ {
var sourcePath = Path.GetFullPath(Path.Combine(application.ContextDirectory, volumeMapping.Source)); var sourcePath = Path.GetFullPath(Path.Combine(application.ContextDirectory, volumeMapping.Source));
volumes += $"-v \"{sourcePath}:{volumeMapping.Target}\" "; volumes += $"-v \"{sourcePath}:{volumeMapping.Target}:{(volumeMapping.ReadOnly ? "ro," : "")}z\" ";
} }
else if (volumeMapping.Name != null) else if (volumeMapping.Name != null)
{ {
@ -294,7 +287,13 @@ namespace Microsoft.Tye.Hosting
} }
} }
var command = $"run -d {workingDirectory} {volumes} {environmentArguments} {portString} --name {replica} --restart=unless-stopped {dockerImage} {docker.Args ?? ""}"; var command = $"run -d {workingDirectory} {volumes} {environmentArguments} {portString} --name {replica} --restart=unless-stopped";
if (!string.IsNullOrEmpty(dockerNetwork))
{
status.DockerNetworkAlias = docker.NetworkAlias ?? serviceDescription!.Name;
command += $" --network {dockerNetwork} --network-alias {status.DockerNetworkAlias}";
}
command += $" {dockerImage} {docker.Args ?? ""}";
if (!docker.IsProxy) if (!docker.IsProxy)
{ {
@ -311,8 +310,7 @@ namespace Microsoft.Tye.Hosting
status.DockerNetwork = dockerNetwork; status.DockerNetwork = dockerNetwork;
WriteReplicaToStore(replica); WriteReplicaToStore(replica);
var result = await ProcessUtil.RunAsync( var result = await application.ContainerEngine.RunAsync(
"docker",
command, command,
throwOnError: false, throwOnError: false,
cancellationToken: cancellationToken, cancellationToken: cancellationToken,
@ -336,7 +334,7 @@ namespace Microsoft.Tye.Hosting
while (string.IsNullOrEmpty(containerId)) while (string.IsNullOrEmpty(containerId))
{ {
// Try to get the ID of the container // Try to get the ID of the container
result = await ProcessUtil.RunAsync("docker", $"ps --no-trunc -f name={replica} --format " + "{{.ID}}"); result = await application.ContainerEngine.RunAsync($"ps --no-trunc -f name={replica} --format " + "{{.ID}}");
containerId = result.ExitCode == 0 ? result.StandardOutput.Trim() : null; containerId = result.ExitCode == 0 ? result.StandardOutput.Trim() : null;
} }
@ -347,21 +345,6 @@ namespace Microsoft.Tye.Hosting
_logger.LogInformation("Running container {ContainerName} with ID {ContainerId}", replica, shortContainerId); _logger.LogInformation("Running container {ContainerName} with ID {ContainerId}", replica, shortContainerId);
if (!string.IsNullOrEmpty(dockerNetwork))
{
status.DockerNetworkAlias = docker.NetworkAlias ?? serviceDescription!.Name;
var networkCommand = $"network connect {dockerNetwork} {replica} --alias {status.DockerNetworkAlias}";
service.Logs.OnNext($"[{replica}]: docker {networkCommand}");
_logger.LogInformation("Running docker command {Command}", networkCommand);
result = await ProcessUtil.RunAsync("docker", networkCommand);
PrintStdOutAndErr(service, replica, result);
}
var sentStartedEvent = false; var sentStartedEvent = false;
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
@ -369,7 +352,7 @@ namespace Microsoft.Tye.Hosting
if (sentStartedEvent) if (sentStartedEvent)
{ {
using var restartCts = new CancellationTokenSource(DockerStopTimeout); using var restartCts = new CancellationTokenSource(DockerStopTimeout);
result = await ProcessUtil.RunAsync("docker", $"restart {containerId}", throwOnError: false, cancellationToken: restartCts.Token); result = await application.ContainerEngine.RunAsync($"restart {containerId}", throwOnError: false, cancellationToken: restartCts.Token);
if (restartCts.IsCancellationRequested) if (restartCts.IsCancellationRequested)
{ {
@ -398,7 +381,7 @@ namespace Microsoft.Tye.Hosting
while (!status.StoppingTokenSource.Token.IsCancellationRequested) while (!status.StoppingTokenSource.Token.IsCancellationRequested)
{ {
var logsRes = await ProcessUtil.RunAsync("docker", $"logs -f {containerId}", var logsRes = await application.ContainerEngine.RunAsync($"logs -f {containerId}",
outputDataReceived: data => service.Logs.OnNext($"[{replica}]: {data}"), outputDataReceived: data => service.Logs.OnNext($"[{replica}]: {data}"),
errorDataReceived: data => service.Logs.OnNext($"[{replica}]: {data}"), errorDataReceived: data => service.Logs.OnNext($"[{replica}]: {data}"),
throwOnError: false, throwOnError: false,
@ -435,7 +418,7 @@ namespace Microsoft.Tye.Hosting
_logger.LogInformation("Stopping container {ContainerName} with ID {ContainerId}", replica, shortContainerId); _logger.LogInformation("Stopping container {ContainerName} with ID {ContainerId}", replica, shortContainerId);
result = await ProcessUtil.RunAsync("docker", $"stop {containerId}", throwOnError: false, cancellationToken: timeoutCts.Token); result = await application.ContainerEngine.RunAsync($"stop {containerId}", throwOnError: false, cancellationToken: timeoutCts.Token);
if (timeoutCts.IsCancellationRequested) if (timeoutCts.IsCancellationRequested)
{ {
@ -451,7 +434,7 @@ namespace Microsoft.Tye.Hosting
_logger.LogInformation("Stopped container {ContainerName} with ID {ContainerId} exited with {ExitCode}", replica, shortContainerId, result.ExitCode); _logger.LogInformation("Stopped container {ContainerName} with ID {ContainerId} exited with {ExitCode}", replica, shortContainerId, result.ExitCode);
result = await ProcessUtil.RunAsync("docker", $"rm {containerId}", throwOnError: false, cancellationToken: timeoutCts.Token); result = await application.ContainerEngine.RunAsync($"rm {containerId}", throwOnError: false, cancellationToken: timeoutCts.Token);
if (timeoutCts.IsCancellationRequested) if (timeoutCts.IsCancellationRequested)
{ {
@ -486,8 +469,7 @@ namespace Microsoft.Tye.Hosting
arguments.Append($" --build-arg {buildArg.Key}={buildArg.Value}"); arguments.Append($" --build-arg {buildArg.Key}={buildArg.Value}");
} }
var dockerBuildResult = await ProcessUtil.RunAsync( var dockerBuildResult = await application.ContainerEngine.RunAsync(
$"docker",
arguments.ToString(), arguments.ToString(),
outputDataReceived: Log, outputDataReceived: Log,
errorDataReceived: Log, errorDataReceived: Log,
@ -550,13 +532,13 @@ namespace Microsoft.Tye.Hosting
service.Items[typeof(DockerInformation)] = dockerInfo; service.Items[typeof(DockerInformation)] = dockerInfo;
} }
private async Task PurgeFromPreviousRun() private async Task PurgeFromPreviousRun(Application application)
{ {
var dockerReplicas = await _replicaRegistry.GetEvents(DockerReplicaStore); var dockerReplicas = await _replicaRegistry.GetEvents(DockerReplicaStore);
foreach (var replica in dockerReplicas) foreach (var replica in dockerReplicas)
{ {
var container = replica["container"]; var container = replica["container"];
await ProcessUtil.RunAsync("docker", $"rm -f {container}", throwOnError: false); await application.ContainerEngine.RunAsync($"rm -f {container}", throwOnError: false);
_logger.LogInformation("removed container {container} from previous run", container); _logger.LogInformation("removed container {container} from previous run", container);
} }

5
src/Microsoft.Tye.Hosting/Model/Application.cs

@ -12,17 +12,20 @@ namespace Microsoft.Tye.Hosting.Model
{ {
public class Application public class Application
{ {
public Application(FileInfo source, Dictionary<string, Service> services) public Application(FileInfo source, Dictionary<string, Service> services, ContainerEngine containerEngine)
{ {
Source = source.FullName; Source = source.FullName;
ContextDirectory = source.DirectoryName!; ContextDirectory = source.DirectoryName!;
Services = services; Services = services;
ContainerEngine = containerEngine;
} }
public string Source { get; } public string Source { get; }
public string ContextDirectory { get; } public string ContextDirectory { get; }
public ContainerEngine ContainerEngine { get; set; }
public Dictionary<string, Service> Services { get; } public Dictionary<string, Service> Services { get; }
public Dictionary<object, object> Items { get; } = new Dictionary<object, object>(); public Dictionary<object, object> Items { get; } = new Dictionary<object, object>();

4
src/Microsoft.Tye.Hosting/Model/DockerVolume.cs

@ -7,15 +7,17 @@ namespace Microsoft.Tye.Hosting.Model
{ {
public class DockerVolume public class DockerVolume
{ {
public DockerVolume(string? source, string? name, string target) public DockerVolume(string? source, string? name, string target, bool readOnly = false)
{ {
Source = source; Source = source;
Name = name; Name = name;
Target = target; Target = target;
ReadOnly = readOnly;
} }
public string? Name { get; } public string? Name { get; }
public string? Source { get; } public string? Source { get; }
public string Target { get; } public string Target { get; }
public bool ReadOnly { get; }
} }
} }

7
src/Microsoft.Tye.Hosting/ProcessRunner.cs

@ -217,15 +217,10 @@ namespace Microsoft.Tye.Hosting
if (hasPorts) if (hasPorts)
{ {
// We need to bind to all interfaces on linux since the container -> host communication won't work
// if we use the IP address to reach out of the host. This works fine on osx and windows
// but doesn't work on linux.
var host = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "*" : "localhost";
// These are the ports that the application should use for binding // These are the ports that the application should use for binding
// 1. Configure ASP.NET Core to bind to those same ports // 1. Configure ASP.NET Core to bind to those same ports
environment["ASPNETCORE_URLS"] = string.Join(";", ports.Select(p => $"{p.Protocol ?? "http"}://{host}:{p.Port}")); environment["ASPNETCORE_URLS"] = string.Join(";", ports.Select(p => $"{p.Protocol ?? "http"}://{application.ContainerEngine.AspNetUrlsHost}:{p.Port}"));
// Set the HTTPS port for the redirect middleware // Set the HTTPS port for the redirect middleware
foreach (var p in ports) foreach (var p in ports)

6
src/Microsoft.Tye.Hosting/TransformProjectsIntoContainers.cs

@ -87,10 +87,12 @@ namespace Microsoft.Tye.Hosting
// This is .NET specific // This is .NET specific
var userSecretStore = GetUserSecretsPathFromSecrets(); var userSecretStore = GetUserSecretsPathFromSecrets();
Directory.CreateDirectory(userSecretStore);
if (!string.IsNullOrEmpty(userSecretStore)) if (!string.IsNullOrEmpty(userSecretStore))
{ {
// Map the user secrets on this drive to user secrets // Map the user secrets on this drive to user secrets
dockerRunInfo.VolumeMappings.Add(new DockerVolume(source: userSecretStore, name: null, target: "/root/.microsoft/usersecrets:ro")); dockerRunInfo.VolumeMappings.Add(new DockerVolume(source: userSecretStore, name: null, target: "/root/.microsoft/usersecrets", readOnly: true));
} }
// Default to development environment // Default to development environment
@ -116,7 +118,7 @@ namespace Microsoft.Tye.Hosting
serviceDescription.Configuration.Add(new EnvironmentVariable("Kestrel__Certificates__Development__Password", certPassword)); serviceDescription.Configuration.Add(new EnvironmentVariable("Kestrel__Certificates__Development__Password", certPassword));
// Certificate Path: https://github.com/dotnet/aspnetcore/blob/a9d702624a02ad4ebf593d9bf9c1c69f5702a6f5/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs#L419 // Certificate Path: https://github.com/dotnet/aspnetcore/blob/a9d702624a02ad4ebf593d9bf9c1c69f5702a6f5/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs#L419
dockerRunInfo.VolumeMappings.Add(new DockerVolume(source: certificateDirectory.DirectoryPath, name: null, target: "/root/.aspnet/https:ro")); dockerRunInfo.VolumeMappings.Add(new DockerVolume(source: certificateDirectory.DirectoryPath, name: null, target: "/root/.aspnet/https", readOnly: true));
} }
// Change the project into a container info // Change the project into a container info

5
src/schema/tye-schema.json

@ -13,6 +13,11 @@
"description": "Dockerhub username or hostname of remote registry. Used for tagging images.", "description": "Dockerhub username or hostname of remote registry. Used for tagging images.",
"type": "string" "type": "string"
}, },
"containerEngine": {
"description": "Container engine.",
"type": "string",
"enum": ["docker", "podman"]
},
"namespace": { "namespace": {
"description": "The Kubernetes namespace to use.", "description": "The Kubernetes namespace to use.",
"type": "string" "type": "string"

2
src/tye/ApplicationBuilderExtensions.cs

@ -213,7 +213,7 @@ namespace Microsoft.Tye
services.Add(ingress.Name, new Service(description)); services.Add(ingress.Name, new Service(description));
} }
return new Application(application.Source, services) { Network = application.Network }; return new Application(application.Source, services, application.ContainerEngine) { Network = application.Network };
} }
public static Tye.Hosting.Model.EnvironmentVariable ToHostingEnvironmentVariable(this EnvironmentVariableBuilder builder) public static Tye.Hosting.Model.EnvironmentVariable ToHostingEnvironmentVariable(this EnvironmentVariableBuilder builder)

15
test/E2ETest/TyeRunTests.cs

@ -9,6 +9,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -504,7 +505,7 @@ services:
}); });
// Delete the volume // Delete the volume
await ProcessUtil.RunAsync("docker", $"volume rm {volumeName}"); await ContainerEngine.Default.RunAsync($"volume rm {volumeName}");
} }
[ConditionalFact] [ConditionalFact]
@ -521,7 +522,7 @@ services:
application.Network = dockerNetwork; application.Network = dockerNetwork;
// Create the existing network // Create the existing network
await ProcessUtil.RunAsync("docker", $"network create {dockerNetwork}"); await ContainerEngine.Default.RunAsync($"network create {dockerNetwork}");
var handler = new HttpClientHandler var handler = new HttpClientHandler
{ {
@ -560,7 +561,7 @@ services:
finally finally
{ {
// Delete the network // Delete the network
await ProcessUtil.RunAsync("docker", $"network rm {dockerNetwork}"); await ContainerEngine.Default.RunAsync($"network rm {dockerNetwork}");
} }
} }
@ -771,6 +772,14 @@ services:
[SkipIfDockerNotRunning] [SkipIfDockerNotRunning]
public async Task NginxIngressTest() public async Task NginxIngressTest()
{ {
// https://github.com/dotnet/tye/issues/428
// nginx container fails to start succesfully on non-Windows because it
// can't resolve the upstream hosts.
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}
using var projectDirectory = CopyTestProjectDirectory("nginx-ingress"); using var projectDirectory = CopyTestProjectDirectory("nginx-ingress");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml")); var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));

18
test/Test.Infrastructure/DockerAssert.cs

@ -10,6 +10,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit.Abstractions; using Xunit.Abstractions;
using Xunit.Sdk; using Xunit.Sdk;
using Microsoft.Tye;
namespace Test.Infrastructure namespace Test.Infrastructure
{ {
@ -22,8 +23,7 @@ namespace Test.Infrastructure
var builder = new StringBuilder(); var builder = new StringBuilder();
output.WriteLine($"> docker images \"{repository}\" --format \"{{{{.Repository}}}}\""); output.WriteLine($"> docker images \"{repository}\" --format \"{{{{.Repository}}}}\"");
var exitCode = await Process.ExecuteAsync( var exitCode = await ContainerEngine.Default.ExecuteAsync(
"docker",
$"images \"{repository}\" --format \"{{{{.Repository}}}}\"", $"images \"{repository}\" --format \"{{{{.Repository}}}}\"",
stdOut: OnOutput, stdOut: OnOutput,
stdErr: OnOutput); stdErr: OnOutput);
@ -33,12 +33,13 @@ namespace Test.Infrastructure
} }
var lines = builder.ToString().Split(new[] { '\r', '\n', }, StringSplitOptions.RemoveEmptyEntries); var lines = builder.ToString().Split(new[] { '\r', '\n', }, StringSplitOptions.RemoveEmptyEntries);
if (lines.Any(line => line == repository)) if (lines.Any(line => line == repository ||
line == $"localhost/{repository}")) // podman format.
{ {
return; return;
} }
throw new XunitException($"Image '{repository}' was not found."); throw new XunitException($"Image '{repository}' was not found in {builder.ToString()}.");
void OnOutput(string text) void OnOutput(string text)
{ {
@ -57,8 +58,7 @@ namespace Test.Infrastructure
{ {
output.WriteLine($"> docker rmi \"{id}\" --force"); output.WriteLine($"> docker rmi \"{id}\" --force");
var exitCode = await Process.ExecuteAsync( var exitCode = await ContainerEngine.Default.ExecuteAsync(
"docker",
$"rmi \"{id}\" --force", $"rmi \"{id}\" --force",
stdOut: OnOutput, stdOut: OnOutput,
stdErr: OnOutput); stdErr: OnOutput);
@ -82,8 +82,7 @@ namespace Test.Infrastructure
var builder = new StringBuilder(); var builder = new StringBuilder();
output.WriteLine($"> docker ps --format \"{{{{.ID}}}}\""); output.WriteLine($"> docker ps --format \"{{{{.ID}}}}\"");
var exitCode = await Process.ExecuteAsync( var exitCode = await ContainerEngine.Default.ExecuteAsync(
"docker",
$"ps --format \"{{{{.ID}}}}\"", $"ps --format \"{{{{.ID}}}}\"",
stdOut: OnOutput, stdOut: OnOutput,
stdErr: OnOutput); stdErr: OnOutput);
@ -110,8 +109,7 @@ namespace Test.Infrastructure
var builder = new StringBuilder(); var builder = new StringBuilder();
output.WriteLine($"> docker images -q \"{repository}\""); output.WriteLine($"> docker images -q \"{repository}\"");
var exitCode = await Process.ExecuteAsync( var exitCode = await ContainerEngine.Default.ExecuteAsync(
"docker",
$"images -q \"{repository}\"", $"images -q \"{repository}\"",
stdOut: OnOutput, stdOut: OnOutput,
stdErr: OnOutput); stdErr: OnOutput);

5
test/Test.Infrastructure/SkipIfDockerNotRunningAttribute.cs

@ -5,6 +5,7 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Microsoft.Tye; using Microsoft.Tye;
using Microsoft.Tye.ConfigModel;
namespace Test.Infrastructure namespace Test.Infrastructure
{ {
@ -14,8 +15,8 @@ namespace Test.Infrastructure
public SkipIfDockerNotRunningAttribute() public SkipIfDockerNotRunningAttribute()
{ {
// TODO Check performance of this. // TODO Check performance of this.
IsMet = DockerDetector.Instance.IsDockerConnectedToDaemon.Value.GetAwaiter().GetResult() && !(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS"))); IsMet = ContainerEngine.Default.IsUsable(out string unusableReason) && !(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS")));
SkipReason = "Docker is not installed or running."; SkipReason = $"Container engine not usable: {unusableReason}";
} }
public bool IsMet { get; } public bool IsMet { get; }

4
test/Test.Infrastructure/TestHelpers.cs

@ -251,8 +251,8 @@ namespace Test.Infrastructure
var processRunner = new ProcessRunner(logger, replicaRegistry, new ProcessRunnerOptions()); var processRunner = new ProcessRunner(logger, replicaRegistry, new ProcessRunnerOptions());
var dockerRunner = new DockerRunner(logger, replicaRegistry); var dockerRunner = new DockerRunner(logger, replicaRegistry);
await processRunner.StartAsync(new Application(new FileInfo(host.Application.Source), new Dictionary<string, Service>())); await processRunner.StartAsync(new Application(new FileInfo(host.Application.Source), new Dictionary<string, Service>(), ContainerEngine.Default));
await dockerRunner.StartAsync(new Application(new FileInfo(host.Application.Source), new Dictionary<string, Service>())); await dockerRunner.StartAsync(new Application(new FileInfo(host.Application.Source), new Dictionary<string, Service>(), ContainerEngine.Default));
} }
await DoOperationAndWaitForReplicasToChangeState(host, ReplicaState.Stopped, replicas.Length, replicas.ToHashSet(), new HashSet<string>(), TimeSpan.Zero, Purge); await DoOperationAndWaitForReplicasToChangeState(host, ReplicaState.Stopped, replicas.Length, replicas.ToHashSet(), new HashSet<string>(), TimeSpan.Zero, Purge);

Loading…
Cancel
Save