diff --git a/src/opulence/dotnet-opulence/Application.cs b/src/opulence/dotnet-opulence/Application.cs index b5dac790..d430f4b9 100644 --- a/src/opulence/dotnet-opulence/Application.cs +++ b/src/opulence/dotnet-opulence/Application.cs @@ -6,7 +6,7 @@ using System.Reflection; namespace Opulence { - internal abstract class Application + public abstract class Application { public abstract ApplicationGlobals Globals { get; } @@ -25,7 +25,7 @@ namespace Opulence } } - internal class ServiceEntry + public class ServiceEntry { public ServiceEntry(Service service, string friendlyName, IEnumerable? environments = null) { @@ -63,7 +63,7 @@ namespace Opulence return Environments.Count == 0 || Environments.Contains(environment, StringComparer.OrdinalIgnoreCase); } - internal bool IsMatchForProject(Application application, FileInfo projectFile) + public bool IsMatchForProject(Application application, FileInfo projectFile) { if (application is null) { diff --git a/src/opulence/dotnet-opulence/ApplicationFactory.cs b/src/opulence/dotnet-opulence/ApplicationFactory.cs index 6f437d3b..0fd47574 100644 --- a/src/opulence/dotnet-opulence/ApplicationFactory.cs +++ b/src/opulence/dotnet-opulence/ApplicationFactory.cs @@ -6,7 +6,7 @@ using Microsoft.Build.Construction; namespace Opulence { - internal static class ApplicationFactory + public static class ApplicationFactory { public static async Task CreateApplicationAsync(OutputContext output, FileInfo projectFile) { diff --git a/src/opulence/dotnet-opulence/ApplicationYamlWriter.cs b/src/opulence/dotnet-opulence/ApplicationYamlWriter.cs index 61626a7a..5fb46e47 100644 --- a/src/opulence/dotnet-opulence/ApplicationYamlWriter.cs +++ b/src/opulence/dotnet-opulence/ApplicationYamlWriter.cs @@ -6,7 +6,7 @@ using YamlDotNet.RepresentationModel; namespace Opulence { - internal sealed class ApplicationYamlWriter + public sealed class ApplicationYamlWriter { public static Task WriteAsync(OutputContext output, StreamWriter writer, Application application) { diff --git a/src/opulence/dotnet-opulence/BuildDockerImageStep.cs b/src/opulence/dotnet-opulence/BuildDockerImageStep.cs index 3d1ef9d8..35049133 100644 --- a/src/opulence/dotnet-opulence/BuildDockerImageStep.cs +++ b/src/opulence/dotnet-opulence/BuildDockerImageStep.cs @@ -2,7 +2,7 @@ namespace Opulence { - internal sealed class BuildDockerImageStep : ServiceExecutor.Step + public sealed class BuildDockerImageStep : ServiceExecutor.Step { public override string DisplayText => "Building Docker Image..."; diff --git a/src/opulence/dotnet-opulence/CombineStep.cs b/src/opulence/dotnet-opulence/CombineStep.cs index 9317131d..d5097d3d 100644 --- a/src/opulence/dotnet-opulence/CombineStep.cs +++ b/src/opulence/dotnet-opulence/CombineStep.cs @@ -1,13 +1,9 @@ -using System.CommandLine.Invocation; -using System.IO; -using System.Linq; -using System.Text; +using System.Linq; using System.Threading.Tasks; -using YamlDotNet.RepresentationModel; namespace Opulence { - internal sealed class CombineStep : ServiceExecutor.Step + public sealed class CombineStep : ServiceExecutor.Step { public override string DisplayText => "Compiling Services..."; diff --git a/src/opulence/dotnet-opulence/DeployCommand.cs b/src/opulence/dotnet-opulence/DeployCommand.cs index 197770f3..2ddd398d 100644 --- a/src/opulence/dotnet-opulence/DeployCommand.cs +++ b/src/opulence/dotnet-opulence/DeployCommand.cs @@ -5,10 +5,11 @@ using System.CommandLine.Invocation; using System.IO; using System.Text; using System.Threading.Tasks; +using Tye; namespace Opulence { - public class DeployCommand + internal static class DeployCommand { public static Command Create() { diff --git a/src/opulence/dotnet-opulence/DeployServiceYamlStep.cs b/src/opulence/dotnet-opulence/DeployServiceYamlStep.cs index cd5b5b9f..2e910f71 100644 --- a/src/opulence/dotnet-opulence/DeployServiceYamlStep.cs +++ b/src/opulence/dotnet-opulence/DeployServiceYamlStep.cs @@ -3,11 +3,12 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using Tye; using YamlDotNet.RepresentationModel; namespace Opulence { - internal sealed class DeployServiceYamlStep : ServiceExecutor.Step + public sealed class DeployServiceYamlStep : ServiceExecutor.Step { public override string DisplayText => "Deploying Manifests..."; diff --git a/src/opulence/dotnet-opulence/DockerContainerBuilder.cs b/src/opulence/dotnet-opulence/DockerContainerBuilder.cs index fe9b87bc..70813cc1 100644 --- a/src/opulence/dotnet-opulence/DockerContainerBuilder.cs +++ b/src/opulence/dotnet-opulence/DockerContainerBuilder.cs @@ -2,6 +2,7 @@ using System.CommandLine.Invocation; using System.IO; using System.Threading.Tasks; +using Tye; namespace Opulence { diff --git a/src/opulence/dotnet-opulence/DockerfileGenerator.cs b/src/opulence/dotnet-opulence/DockerfileGenerator.cs index 9d60c87c..0c142566 100644 --- a/src/opulence/dotnet-opulence/DockerfileGenerator.cs +++ b/src/opulence/dotnet-opulence/DockerfileGenerator.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Opulence { - internal static class DockerfileGenerator + public static class DockerfileGenerator { public static async Task WriteDockerfileAsync(OutputContext output, Application application, ServiceEntry service, Project project, ContainerInfo container, string filePath) { diff --git a/src/opulence/dotnet-opulence/GenerateKubernetesManifestStep.cs b/src/opulence/dotnet-opulence/GenerateKubernetesManifestStep.cs index b71cc62f..a51021ca 100644 --- a/src/opulence/dotnet-opulence/GenerateKubernetesManifestStep.cs +++ b/src/opulence/dotnet-opulence/GenerateKubernetesManifestStep.cs @@ -2,7 +2,7 @@ namespace Opulence { - internal sealed class GenerateKubernetesManifestStep : ServiceExecutor.Step + public sealed class GenerateKubernetesManifestStep : ServiceExecutor.Step { public override string DisplayText => "Generating Manifests..."; diff --git a/src/opulence/dotnet-opulence/HelmChartBuilder.cs b/src/opulence/dotnet-opulence/HelmChartBuilder.cs index cccd44df..ba59bc11 100644 --- a/src/opulence/dotnet-opulence/HelmChartBuilder.cs +++ b/src/opulence/dotnet-opulence/HelmChartBuilder.cs @@ -2,6 +2,7 @@ using System.CommandLine.Invocation; using System.IO; using System.Threading.Tasks; +using Tye; namespace Opulence { diff --git a/src/opulence/dotnet-opulence/OutputContext.cs b/src/opulence/dotnet-opulence/OutputContext.cs index 0fd20c60..3bcd1081 100644 --- a/src/opulence/dotnet-opulence/OutputContext.cs +++ b/src/opulence/dotnet-opulence/OutputContext.cs @@ -5,7 +5,7 @@ using System.CommandLine.IO; namespace Opulence { - internal sealed class OutputContext + public sealed class OutputContext { private const int IndentAmount = 4; diff --git a/src/opulence/dotnet-opulence/ProjectReader.cs b/src/opulence/dotnet-opulence/ProjectReader.cs index 637f86cb..958acada 100644 --- a/src/opulence/dotnet-opulence/ProjectReader.cs +++ b/src/opulence/dotnet-opulence/ProjectReader.cs @@ -7,7 +7,7 @@ using Semver; namespace Opulence { - internal static class ProjectReader + public static class ProjectReader { public static async Task ReadProjectDetailsAsync(OutputContext output, FileInfo projectFile, Project project) { diff --git a/src/opulence/dotnet-opulence/PushDockerImageStep.cs b/src/opulence/dotnet-opulence/PushDockerImageStep.cs index 8bc120dc..b36982b6 100644 --- a/src/opulence/dotnet-opulence/PushDockerImageStep.cs +++ b/src/opulence/dotnet-opulence/PushDockerImageStep.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; namespace Opulence { - internal sealed class PushDockerImageStep : ServiceExecutor.Step + public sealed class PushDockerImageStep : ServiceExecutor.Step { public override string DisplayText => "Pushing Docker Image..."; diff --git a/src/opulence/dotnet-opulence/ServiceExecutor.cs b/src/opulence/dotnet-opulence/ServiceExecutor.cs index 9cc9c1f5..2748b38b 100644 --- a/src/opulence/dotnet-opulence/ServiceExecutor.cs +++ b/src/opulence/dotnet-opulence/ServiceExecutor.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Opulence { - internal sealed class ServiceExecutor + public sealed class ServiceExecutor { private readonly OutputContext output; private readonly Application application; diff --git a/src/opulence/dotnet-opulence/StandardOptions.cs b/src/opulence/dotnet-opulence/StandardOptions.cs index a1ef5f93..ef4c92e0 100644 --- a/src/opulence/dotnet-opulence/StandardOptions.cs +++ b/src/opulence/dotnet-opulence/StandardOptions.cs @@ -7,7 +7,7 @@ using System.Linq; namespace Opulence { - internal static class StandardOptions + public static class StandardOptions { private static readonly string[] AllOutputs = new string[] { "container", "chart", }; @@ -37,6 +37,17 @@ namespace Opulence } } + public static Option Interactive + { + get + { + return new Option(new[] { "-i", "--interactive", }, "Interactive mode") + { + Argument = new Argument(), + }; + } + } + public static Option Outputs { get diff --git a/src/opulence/dotnet-opulence/Verbosity.cs b/src/opulence/dotnet-opulence/Verbosity.cs index 967cdcbb..2a04f32e 100644 --- a/src/opulence/dotnet-opulence/Verbosity.cs +++ b/src/opulence/dotnet-opulence/Verbosity.cs @@ -1,6 +1,6 @@ namespace Opulence { - internal enum Verbosity + public enum Verbosity { Quiet, Info, diff --git a/src/opulence/dotnet-opulence/dotnet-opulence.csproj b/src/opulence/dotnet-opulence/dotnet-opulence.csproj index c59f367c..c9b7441a 100644 --- a/src/opulence/dotnet-opulence/dotnet-opulence.csproj +++ b/src/opulence/dotnet-opulence/dotnet-opulence.csproj @@ -32,6 +32,11 @@ + + + + + diff --git a/src/tye/OpulenceApplicationAdapter.cs b/src/tye/OpulenceApplicationAdapter.cs new file mode 100644 index 00000000..cc81112d --- /dev/null +++ b/src/tye/OpulenceApplicationAdapter.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Opulence; + +namespace Tye +{ + internal class OpulenceApplicationAdapter : Opulence.Application + { + private readonly Micronetes.Hosting.Model.Application application; + + public OpulenceApplicationAdapter( + Micronetes.Hosting.Model.Application application, + ApplicationGlobals globals, + IReadOnlyList services) + { + this.application = application; + Globals = globals; + Services = services; + } + + public override ApplicationGlobals Globals { get; } + + public override string RootDirectory => application.ContextDirectory; + + public override IReadOnlyList Services { get; } + } +} diff --git a/src/tye/Program.DeployCommand.cs b/src/tye/Program.DeployCommand.cs new file mode 100644 index 00000000..1e323d01 --- /dev/null +++ b/src/tye/Program.DeployCommand.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Opulence; + +namespace Tye +{ + static partial class Program + { + public static Command CreateDeployCommand() + { + var command = new Command("deploy", "Deploy the application"); + + var argument = new Argument("path") + { + Description = "A solution or project file to generate a yaml manifest from", + Arity = ArgumentArity.ZeroOrOne + }; + + command.Add(argument); + command.Add(StandardOptions.Interactive); + command.Add(StandardOptions.Verbosity); + + command.Handler = CommandHandler.Create((console, path, verbosity, interactive) => + { + var application = ResolveApplication(path); + if (application is null) + { + throw new CommandException($"None of the supported files were found (tye.yaml, .csproj, .fsproj, .sln)"); + } + + return ExecuteAsync(new OutputContext(console, verbosity), application, environment: "production", interactive); + }); + + return command; + } + + private static async Task ExecuteAsync(OutputContext output, Micronetes.Hosting.Model.Application application, string environment, bool interactive) + { + var globals = new ApplicationGlobals(); + var services = new List(); + + foreach (var kvp in application.Services) + { + if (kvp.Value.Description.Project is string projectFile) + { + var project = new Project(projectFile); + var service = new Service(kvp.Key) + { + Source = project, + }; + var serviceEntry = new ServiceEntry(service, kvp.Key); + + await ProjectReader.ReadProjectDetailsAsync(output, new FileInfo(projectFile), project); + + var container = new ContainerInfo(); + service.GeneratedAssets.Container = container; + services.Add(serviceEntry); + } + } + + var opulenceApplication = new OpulenceApplicationAdapter(application, globals, services); + if (opulenceApplication.Globals.Registry?.Hostname == null && interactive) + { + var registry = output.Prompt("Enter the Container Registry (ex: 'example.azurecr.io' for Azure or 'example' for dockerhub)"); + opulenceApplication.Globals.Registry = new ContainerRegistry(registry); + } + else if (opulenceApplication.Globals.Registry?.Hostname == null) + { + throw new CommandException("A registry is required for deploy operations. Add the registry to 'tye.yaml' or use '-i' for interactive mode."); + } + + foreach (var service in opulenceApplication.Services) + { + if (service.Service.Source is Project project && service.Service.GeneratedAssets.Container is ContainerInfo container) + { + DockerfileGenerator.ApplyContainerDefaults(opulenceApplication, service, project, container); + } + } + + var steps = new List() + { + new CombineStep() { Environment = environment, }, + new BuildDockerImageStep() { Environment = environment, }, + new PushDockerImageStep() { Environment = environment, }, + }; + + steps.Add(new GenerateKubernetesManifestStep() { Environment = environment, }); + + // If this is command is for a project, then deploy the component manifest + // for just the project. We won't run the "application deploy" part. + if (!string.Equals(".csproj", Path.GetExtension(application.Source), StringComparison.Ordinal) && + !string.Equals(".fsproj", Path.GetExtension(application.Source), StringComparison.Ordinal)) + { + steps.Add(new DeployServiceYamlStep() { Environment = environment, }); + } + + var executor = new ServiceExecutor(output, opulenceApplication, steps); + foreach (var service in opulenceApplication.Services) + { + if (service.IsMatchForProject(opulenceApplication, new FileInfo(application.Source))) + { + await executor.ExecuteAsync(service); + } + } + + await PackageApplicationAsync(output, opulenceApplication, Path.GetDirectoryName(application.Source), environment); + } + + private static async Task PackageApplicationAsync(OutputContext output, Opulence.Application application, string applicationName, string environment) + { + using var step = output.BeginStep("Deploying Application Manifests..."); + + using var tempFile = TempFile.Create(); + output.WriteInfoLine($"Writing output to '{tempFile.FilePath}'."); + + { + using var stream = File.OpenWrite(tempFile.FilePath); + using var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true); + + await ApplicationYamlWriter.WriteAsync(output, writer, application); + } + + output.WriteDebugLine("Running 'kubectl apply'."); + output.WriteCommandLine("kubectl", $"apply -f \"{tempFile.FilePath}\""); + var capture = output.Capture(); + var exitCode = await Process.ExecuteAsync( + $"kubectl", + $"apply -f \"{tempFile.FilePath}\"", + System.Environment.CurrentDirectory, + stdOut: capture.StdOut, + stdErr: capture.StdErr); + + output.WriteDebugLine($"Done running 'kubectl apply' exit code: {exitCode}"); + if (exitCode != 0) + { + throw new CommandException("'kubectl apply' failed."); + } + + output.WriteInfoLine($"Deployed application '{applicationName}'."); + + step.MarkComplete(); + } + } +} diff --git a/src/tye/Program.cs b/src/tye/Program.cs index 35bfb53a..bf903c56 100644 --- a/src/tye/Program.cs +++ b/src/tye/Program.cs @@ -20,6 +20,7 @@ namespace Tye command.AddCommand(CreateInitCommand()); command.AddCommand(CreateRunCommand(args)); + command.AddCommand(CreateDeployCommand()); // Show commandline help unless a subcommand was used. command.Handler = CommandHandler.Create(help => diff --git a/src/opulence/dotnet-opulence/TempDirectory.cs b/src/tye/TempDirectory.cs similarity index 97% rename from src/opulence/dotnet-opulence/TempDirectory.cs rename to src/tye/TempDirectory.cs index 75f1bdeb..99cd8e7b 100644 --- a/src/opulence/dotnet-opulence/TempDirectory.cs +++ b/src/tye/TempDirectory.cs @@ -1,7 +1,7 @@ using System; using System.IO; -namespace Opulence +namespace Tye { internal class TempDirectory : IDisposable { diff --git a/src/opulence/dotnet-opulence/TempFile.cs b/src/tye/TempFile.cs similarity index 95% rename from src/opulence/dotnet-opulence/TempFile.cs rename to src/tye/TempFile.cs index c5b0dd86..cd3b7b6e 100644 --- a/src/opulence/dotnet-opulence/TempFile.cs +++ b/src/tye/TempFile.cs @@ -1,7 +1,7 @@ using System; using System.IO; -namespace Opulence +namespace Tye { internal class TempFile : IDisposable { diff --git a/src/tye/tye.csproj b/src/tye/tye.csproj index e81b620b..14609c44 100644 --- a/src/tye/tye.csproj +++ b/src/tye/tye.csproj @@ -11,6 +11,7 @@ +