Skip to content
This repository has been archived by the owner on Nov 20, 2023. It is now read-only.

Support podman #1014

Merged
merged 4 commits into from
Apr 29, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions src/Microsoft.Tye.Core/BuildDockerImageStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,9 @@ public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder
return;
}

if (!await DockerDetector.Instance.IsDockerInstalled.Value)
if (!DockerDetector.Instance.IsUsable(out string? unusableReason))
{
throw new CommandException($"Cannot generate a docker image for '{service.Name}' because docker is not installed.");
}

if (!await DockerDetector.Instance.IsDockerConnectedToDaemon.Value)
{
throw new CommandException($"Cannot generate a docker image for '{service.Name}' because docker is not running.");
throw new CommandException($"Cannot generate a docker image for '{service.Name}' because {unusableReason}.");
}

if (project is DotnetProjectServiceBuilder dotnetProject)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder
output.WriteDebugLine($"Running 'kubectl apply' in ${ns}");
output.WriteCommandLine("kubectl", $"apply -f \"{tempFile.FilePath}\"");
var capture = output.Capture();
var exitCode = await Process.ExecuteAsync(
var exitCode = await ProcessUtil.ExecuteAsync(
$"kubectl",
$"apply -f \"{tempFile.FilePath}\"",
System.Environment.CurrentDirectory,
Expand Down Expand Up @@ -81,7 +81,7 @@ public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder
var retries = 0;
while (!done && retries < 60)
{
var ingressExitCode = await Process.ExecuteAsync(
var ingressExitCode = await ProcessUtil.ExecuteAsync(
"kubectl",
$"get ingress {ingress.Name} -o jsonpath='{{..ip}}'",
Environment.CurrentDirectory,
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.Tye.Core/DockerContainerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public static async Task BuildContainerImageFromDockerFileAsync(OutputContext ou
output.WriteDebugLine("Running 'docker build'.");
output.WriteCommandLine("docker", $"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"");
var capture = output.Capture();
var exitCode = await Process.ExecuteAsync(
var exitCode = await ProcessUtil.ExecuteAsync(
$"docker",
$"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"",
new FileInfo(containerService.DockerFile).DirectoryName,
Expand Down Expand Up @@ -148,7 +148,7 @@ public static async Task BuildContainerImageAsync(OutputContext output, Applicat
output.WriteDebugLine("Running 'docker build'.");
output.WriteCommandLine("docker", $"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"");
var capture = output.Capture();
var exitCode = await Process.ExecuteAsync(
var exitCode = await ProcessUtil.ExecuteAsync(
$"docker",
$"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"",
project.ProjectFile.DirectoryName,
Expand Down
118 changes: 94 additions & 24 deletions src/Microsoft.Tye.Core/DockerDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
// See the LICENSE file in the project root for more information.

using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -14,41 +18,107 @@ public class DockerDetector

public static DockerDetector Instance { get; } = new DockerDetector();

private DockerDetector()
{
IsDockerInstalled = new Lazy<Task<bool>>(DetectDockerInstalled);
IsDockerConnectedToDaemon = new Lazy<Task<bool>>(DetectDockerConnectedToDaemon);
}
private bool _isUsable { get; }
private string? _unusableReason { get; }

public Lazy<Task<bool>> IsDockerInstalled { get; }
public bool IsPodman { get; }
public string AspNetUrlsHost { get; }
public string? ContainerHost { get; }

public Lazy<Task<bool>> IsDockerConnectedToDaemon { get; }

private async Task<bool> DetectDockerInstalled()
public bool IsUsable(out string? unusableReason)
{
try
{
await ProcessUtil.RunAsync("docker", "version", throwOnError: false, cancellationToken: new CancellationTokenSource(Timeout).Token);
return true;
}
catch (Exception)
{
// Unfortunately, process throws
return false;
}
unusableReason = _unusableReason;
return _isUsable;
}

private async Task<bool> DetectDockerConnectedToDaemon()
private DockerDetector()
{
AspNetUrlsHost = "localhost";
try
{
var result = await ProcessUtil.RunAsync("docker", "version", throwOnError: false, cancellationToken: new CancellationTokenSource(Timeout).Token);
return result.ExitCode == 0;
ProcessResult result;
try
{
// try to use podman.
result = ProcessUtil.RunAsync("podman", "version -f \"{{ .Client.Version }}\"", throwOnError: false, cancellationToken: new CancellationTokenSource(Timeout).Token).Result;
davidfowl marked this conversation as resolved.
Show resolved Hide resolved
IsPodman = true;

if (result.ExitCode != 0)
{
_unusableReason = $"podman version exited with {result.ExitCode}. Standard error: \"{result.StandardError}\".";
return;
}

if (!Version.TryParse(result.StandardOutput, out Version? version))
{
_unusableReason = $"cannot parse podman version '{result.StandardOutput}'.";
return;
}
Version minVersion = new Version(3, 1);
if (version < minVersion)
{
_unusableReason = $"podman version '{result.StandardOutput}' is less than the required '{minVersion}'.";
return;
}

// 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";
}
}
catch (Exception)
{
// try to use docker.

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";
}

result = ProcessUtil.RunAsync("docker", "version", throwOnError: false, cancellationToken: new CancellationTokenSource(Timeout).Token).Result;

if (result.ExitCode != 0)
{
_unusableReason = "docker is not connected.";
return;
}
}

_isUsable = true;
}
catch (Exception)
{
// Unfortunately, process throws
return false;
_unusableReason = "docker is not installed.";
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.Tye.Core/DockerPush.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static async Task ExecuteAsync(OutputContext output, string imageName, st
output.WriteDebugLine("Running 'docker push'.");
output.WriteCommandLine("docker", $"push {imageName}:{imageTag}");
var capture = output.Capture();
var exitCode = await Process.ExecuteAsync(
var exitCode = await ProcessUtil.ExecuteAsync(
$"docker",
$"push {imageName}:{imageTag}",
stdOut: capture.StdOut,
Expand Down
24 changes: 23 additions & 1 deletion src/Microsoft.Tye.Core/ProcessUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ public static class ProcessUtil

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)
{
command = CheckForPodman(command);
return System.CommandLine.Invocation.Process.ExecuteAsync(command, args, workingDir, stdOut, stdErr, environmentVariables);
}

public static async Task<ProcessResult> RunAsync(
string filename,
string arguments,
Expand All @@ -35,6 +47,7 @@ public static async Task<ProcessResult> RunAsync(
Action<int>? onStop = null,
CancellationToken cancellationToken = default)
{
filename = CheckForPodman(filename);
using var process = new Process()
{
StartInfo =
Expand Down Expand Up @@ -109,7 +122,7 @@ public static async Task<ProcessResult> RunAsync(

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
{
Expand Down Expand Up @@ -175,5 +188,14 @@ public static void KillProcess(int pid)
catch (ArgumentException) { }
catch (InvalidOperationException) { }
}

private static string CheckForPodman(string command)
{
if (command == "docker" && DockerDetector.Instance.IsPodman)
{
return "podman";
}
return command;
}
}
}
4 changes: 2 additions & 2 deletions src/Microsoft.Tye.Core/ValidateIngressStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder
output.WriteDebugLine($"Running 'minikube addons enable ingress'");
output.WriteCommandLine("minikube", "addon enable ingress");
var capture = output.Capture();
var exitCode = await Process.ExecuteAsync(
var exitCode = await ProcessUtil.ExecuteAsync(
$"minikube",
$"addons enable ingress",
System.Environment.CurrentDirectory,
Expand All @@ -149,7 +149,7 @@ public override async Task ExecuteAsync(OutputContext output, ApplicationBuilder
output.WriteDebugLine($"Running 'kubectl apply'");
output.WriteCommandLine("kubectl", $"apply -f \"https://aka.ms/tye/ingress/deploy\"");
var capture = output.Capture();
var exitCode = await Process.ExecuteAsync(
var exitCode = await ProcessUtil.ExecuteAsync(
$"kubectl",
$"apply -f \"https://aka.ms/tye/ingress/deploy\"",
System.Environment.CurrentDirectory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Net;
using System.Net.Sockets;

namespace Microsoft.Extensions.Configuration
{
Expand All @@ -21,6 +23,11 @@ public static class TyeConfigurationExtensions
return null;
}

if (IPAddress.TryParse(host, out IPAddress address) && address.AddressFamily == AddressFamily.InterNetworkV6)
{
host = "[" + host + "]";
}

return new Uri(protocol + "://" + host + ":" + port + "/");
}

Expand Down
13 changes: 3 additions & 10 deletions src/Microsoft.Tye.Hosting/DockerImagePuller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,11 @@ public async Task StartAsync(Application application)
return;
}

if (!await DockerDetector.Instance.IsDockerInstalled.Value)
if (!DockerDetector.Instance.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.");
}

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.");
throw new CommandException($"Unable to pull image: {unusableReason}.");
}

var tasks = new Task[images.Count];
Expand Down
Loading