diff --git a/src/Tye.Core/BuildDockerImageStep.cs b/src/Tye.Core/BuildDockerImageStep.cs index 95949c44..afd74813 100644 --- a/src/Tye.Core/BuildDockerImageStep.cs +++ b/src/Tye.Core/BuildDockerImageStep.cs @@ -29,6 +29,16 @@ namespace Tye return; } + if (!await DockerDetector.Instance.IsDockerInstalled.Value) + { + throw new CommandException($"Cannot generate a docker image for '{service.Service.Name}' because docker is not installed."); + } + + if (!await DockerDetector.Instance.IsDockerConnectedToDaemon.Value) + { + throw new CommandException($"Cannot generate a docker image for '{service.Service.Name}' because docker is not running."); + } + await DockerContainerBuilder.BuildContainerImageAsync(output, application, service, project, container); } } diff --git a/src/Tye.Core/DeployServiceYamlStep.cs b/src/Tye.Core/DeployServiceYamlStep.cs index 65234765..e3e644c6 100644 --- a/src/Tye.Core/DeployServiceYamlStep.cs +++ b/src/Tye.Core/DeployServiceYamlStep.cs @@ -26,6 +26,16 @@ namespace Tye return; } + if (!await KubectlDetector.Instance.IsKubectlInstalled.Value) + { + throw new CommandException($"Cannot apply manifests for '{service.Service.Name}' because kubectl is not installed."); + } + + if (!await KubectlDetector.Instance.IsKubectlConnectedToCluster.Value) + { + throw new CommandException($"Cannot apply manifests for '{service.Service.Name}' because kubectl is not connected to a cluster."); + } + using var tempFile = TempFile.Create(); output.WriteDebugLine($"Writing output to '{tempFile.FilePath}'."); diff --git a/src/Tye.Core/Tye.Core.csproj b/src/Tye.Core/Tye.Core.csproj index 4db4c849..ddb2ac90 100644 --- a/src/Tye.Core/Tye.Core.csproj +++ b/src/Tye.Core/Tye.Core.csproj @@ -10,6 +10,7 @@ + @@ -24,8 +25,13 @@ - - + + + + + + + diff --git a/src/Tye.Hosting/DockerRunner.cs b/src/Tye.Hosting/DockerRunner.cs index 095dd069..69b82f9f 100644 --- a/src/Tye.Hosting/DockerRunner.cs +++ b/src/Tye.Hosting/DockerRunner.cs @@ -15,7 +15,6 @@ namespace Tye.Hosting public class DockerRunner : IApplicationProcessor { private readonly ILogger _logger; - private readonly Lazy> _dockerInstalled = new Lazy>(DetectDockerInstalled); public DockerRunner(ILogger logger) { @@ -51,7 +50,7 @@ namespace Tye.Hosting private async Task StartContainerAsync(Application application, Service service, DockerRunInfo docker) { - if (!await _dockerInstalled.Value) + if (!await DockerDetector.Instance.IsDockerInstalled.Value) { _logger.LogError("Unable to start docker container for service {ServiceName}, Docker is not installed.", service.Description.Name); @@ -59,6 +58,14 @@ namespace Tye.Hosting 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 = ""; diff --git a/src/Tye.Hosting/Tye.Hosting.csproj b/src/Tye.Hosting/Tye.Hosting.csproj index 30066e2a..84851d4c 100644 --- a/src/Tye.Hosting/Tye.Hosting.csproj +++ b/src/Tye.Hosting/Tye.Hosting.csproj @@ -15,7 +15,10 @@ - + + + + diff --git a/src/shared/DockerDetector.cs b/src/shared/DockerDetector.cs new file mode 100644 index 00000000..294d6f44 --- /dev/null +++ b/src/shared/DockerDetector.cs @@ -0,0 +1,52 @@ +// 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.Tasks; + +namespace Tye +{ + internal class DockerDetector + { + public static DockerDetector Instance { get; } = new DockerDetector(); + + private DockerDetector() + { + IsDockerInstalled = new Lazy>(DetectDockerInstalled); + IsDockerConnectedToDaemon = new Lazy>(DetectDockerConnectedToDaemon); + } + + public Lazy> IsDockerInstalled { get; } + + public Lazy> IsDockerConnectedToDaemon { get; } + + private async Task DetectDockerInstalled() + { + try + { + await ProcessUtil.RunAsync("docker", "version", throwOnError: false); + return true; + } + catch (Exception) + { + // Unfortunately, process throws + return false; + } + } + + private async Task DetectDockerConnectedToDaemon() + { + try + { + var result = await ProcessUtil.RunAsync("docker", "version", throwOnError: false); + return result.ExitCode == 0; + } + catch (Exception) + { + // Unfortunately, process throws + return false; + } + } + } +} diff --git a/src/shared/KubectlDetector.cs b/src/shared/KubectlDetector.cs new file mode 100644 index 00000000..f968878b --- /dev/null +++ b/src/shared/KubectlDetector.cs @@ -0,0 +1,54 @@ +// 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.Tasks; + +namespace Tye +{ + internal class KubectlDetector + { + public static KubectlDetector Instance { get; } = new KubectlDetector(); + + private KubectlDetector() + { + IsKubectlInstalled = new Lazy>(DetectKubectlInstalled); + IsKubectlConnectedToCluster = new Lazy>(DetectKubectlConnectedToCluster); + } + + public Lazy> IsKubectlInstalled { get; } + + public Lazy> IsKubectlConnectedToCluster { get; } + + private static async Task DetectKubectlInstalled() + { + try + { + // Ignoring the exit code and relying on Process to throw if kubectl is not found + // kubectl version will return non-zero if you're not connected to a cluster. + await ProcessUtil.RunAsync("kubectl", "version", throwOnError: false); + return true; + } + catch (Exception) + { + // Unfortunately, process throws + return false; + } + } + + private static async Task DetectKubectlConnectedToCluster() + { + try + { + var result = await ProcessUtil.RunAsync("kubectl", "cluster-info", throwOnError: false); + return result.ExitCode == 0; + } + catch (Exception) + { + // Unfortunately, process throws + return false; + } + } + } +} diff --git a/src/tye/NameSanitizer.cs b/src/shared/NameSanitizer.cs similarity index 100% rename from src/tye/NameSanitizer.cs rename to src/shared/NameSanitizer.cs diff --git a/src/Tye.Hosting/Infrastructure/ProcessResult.cs b/src/shared/ProcessResult.cs similarity index 96% rename from src/Tye.Hosting/Infrastructure/ProcessResult.cs rename to src/shared/ProcessResult.cs index b83050e8..111bb87f 100644 --- a/src/Tye.Hosting/Infrastructure/ProcessResult.cs +++ b/src/shared/ProcessResult.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace Tye.Hosting +namespace Tye { internal class ProcessResult { diff --git a/src/Tye.Hosting/Infrastructure/ProcessUtil.cs b/src/shared/ProcessUtil.cs similarity index 99% rename from src/Tye.Hosting/Infrastructure/ProcessUtil.cs rename to src/shared/ProcessUtil.cs index 398314c0..3ac482bd 100644 --- a/src/Tye.Hosting/Infrastructure/ProcessUtil.cs +++ b/src/shared/ProcessUtil.cs @@ -11,7 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Mono.Unix.Native; -namespace Tye.Hosting +namespace Tye { internal static class ProcessUtil { diff --git a/src/tye/TempDirectory.cs b/src/shared/TempDirectory.cs similarity index 100% rename from src/tye/TempDirectory.cs rename to src/shared/TempDirectory.cs diff --git a/src/tye/TempFile.cs b/src/shared/TempFile.cs similarity index 100% rename from src/tye/TempFile.cs rename to src/shared/TempFile.cs diff --git a/src/tye/Program.DeployCommand.cs b/src/tye/Program.DeployCommand.cs index cd16a8ad..c75c13e4 100644 --- a/src/tye/Program.DeployCommand.cs +++ b/src/tye/Program.DeployCommand.cs @@ -161,6 +161,16 @@ namespace Tye await ApplicationYamlWriter.WriteAsync(output, writer, application); } + if (!await KubectlDetector.Instance.IsKubectlInstalled.Value) + { + throw new CommandException($"Cannot apply manifests because kubectl is not installed."); + } + + if (!await KubectlDetector.Instance.IsKubectlConnectedToCluster.Value) + { + throw new CommandException($"Cannot apply manifests because kubectl is not connected to a cluster."); + } + output.WriteDebugLine("Running 'kubectl apply'."); output.WriteCommandLine("kubectl", $"apply -f \"{tempFile.FilePath}\""); var capture = output.Capture(); diff --git a/src/tye/tye.csproj b/src/tye/tye.csproj index cb91d831..6c3225fb 100644 --- a/src/tye/tye.csproj +++ b/src/tye/tye.csproj @@ -10,12 +10,23 @@ true + + + + + + + + + + + diff --git a/test/E2ETest/E2ETest.csproj b/test/E2ETest/E2ETest.csproj index 907ebb91..e3b3106e 100644 --- a/test/E2ETest/E2ETest.csproj +++ b/test/E2ETest/E2ETest.csproj @@ -9,7 +9,7 @@ - +