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

Commit

Permalink
Support podman (#1014)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmds authored Apr 29, 2021
1 parent c09165c commit c3f3c6c
Show file tree
Hide file tree
Showing 27 changed files with 328 additions and 171 deletions.
5 changes: 4 additions & 1 deletion src/Microsoft.Tye.Core/ApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ namespace Microsoft.Tye
{
public sealed class ApplicationBuilder
{
public ApplicationBuilder(FileInfo source, string name)
public ApplicationBuilder(FileInfo source, string name, ContainerEngine containerEngine)
{
Source = source;
Name = name;
ContainerEngine = containerEngine;
}

public FileInfo Source { get; set; }
Expand All @@ -23,6 +24,8 @@ public ApplicationBuilder(FileInfo source, string name)

public ContainerRegistry? Registry { get; set; }

public ContainerEngine ContainerEngine { get; set; }

public List<ExtensionConfiguration> Extensions { get; } = new List<ExtensionConfiguration>();

public List<ServiceBuilder> Services { get; } = new List<ServiceBuilder>();
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.Tye.Core/ApplicationFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static async Task<ApplicationBuilder> CreateAsync(OutputContext output, F
var rootConfig = ConfigFactory.FromFile(source);
rootConfig.Validate();

var root = new ApplicationBuilder(source, rootConfig.Name!)
var root = new ApplicationBuilder(source, rootConfig.Name!, new ContainerEngine(rootConfig.ContainerEngineType))
{
Namespace = rootConfig.Namespace
};
Expand Down
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 (!application.ContainerEngine.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
2 changes: 2 additions & 0 deletions src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public class ConfigApplication

public string? Registry { get; set; }

public ContainerEngineType? ContainerEngineType { get; set; }

public string? Network { get; set; }

public List<Dictionary<string, object>> Extensions { get; set; } = new List<Dictionary<string, object>>();
Expand Down
12 changes: 12 additions & 0 deletions src/Microsoft.Tye.Core/ConfigModel/ContainerEngineType.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 181 additions & 0 deletions src/Microsoft.Tye.Core/ContainerEngine.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
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
6 changes: 2 additions & 4 deletions src/Microsoft.Tye.Core/DockerContainerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +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(
$"docker",
var exitCode = await application.ContainerEngine.ExecuteAsync(
$"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"",
new FileInfo(containerService.DockerFile).DirectoryName,
stdOut: capture.StdOut,
Expand Down Expand Up @@ -148,8 +147,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(
$"docker",
var exitCode = await application.ContainerEngine.ExecuteAsync(
$"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"",
project.ProjectFile.DirectoryName,
stdOut: capture.StdOut,
Expand Down
55 changes: 0 additions & 55 deletions src/Microsoft.Tye.Core/DockerDetector.cs

This file was deleted.

5 changes: 2 additions & 3 deletions src/Microsoft.Tye.Core/DockerPush.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Microsoft.Tye
{
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)
{
Expand All @@ -30,8 +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(
$"docker",
var exitCode = await containerEngine.ExecuteAsync(
$"push {imageName}:{imageTag}",
stdOut: capture.StdOut,
stdErr: capture.StdErr);
Expand Down
13 changes: 12 additions & 1 deletion src/Microsoft.Tye.Core/ProcessUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ 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)
{
return System.CommandLine.Invocation.Process.ExecuteAsync(command, args, workingDir, stdOut, stdErr, environmentVariables);
}

public static async Task<ProcessResult> RunAsync(
string filename,
string arguments,
Expand Down Expand Up @@ -109,7 +120,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
Loading

0 comments on commit c3f3c6c

Please sign in to comment.