mirror of https://github.com/dotnet/tye.git
5 changed files with 6 additions and 408 deletions
@ -1,137 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.CommandLine; |
|||
using System.CommandLine.Invocation; |
|||
using System.IO; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Tye; |
|||
|
|||
namespace Opulence |
|||
{ |
|||
internal static class DeployCommand |
|||
{ |
|||
public static Command Create() |
|||
{ |
|||
var command = new Command("deploy", "Deploy the application to Kubernetes") |
|||
{ |
|||
StandardOptions.Project, |
|||
StandardOptions.Verbosity, |
|||
StandardOptions.Environment, |
|||
}; |
|||
|
|||
command.Handler = CommandHandler.Create<IConsole, FileInfo, string, Verbosity>((console, project, environment, verbosity) => |
|||
{ |
|||
return ExecuteAsync(new OutputContext(console, verbosity), project, environment); |
|||
}); |
|||
|
|||
return command; |
|||
} |
|||
|
|||
private static async Task ExecuteAsync(OutputContext output, FileInfo projectFile, string environment) |
|||
{ |
|||
output.WriteBanner(); |
|||
|
|||
var application = await ApplicationFactory.CreateApplicationAsync(output, projectFile); |
|||
if (application.Globals.Registry?.Hostname == null) |
|||
{ |
|||
throw new CommandException("A registry is required for deploy operations. run 'dotnet-opulence init'."); |
|||
} |
|||
|
|||
var steps = new List<ServiceExecutor.Step>() |
|||
{ |
|||
new CombineStep() { Environment = environment, }, |
|||
new BuildDockerImageStep() { Environment = environment, }, |
|||
new PushDockerImageStep() { Environment = environment, }, |
|||
}; |
|||
|
|||
if (application.Globals.DeploymentKind == DeploymentKind.None) |
|||
{ |
|||
// No extra steps
|
|||
} |
|||
else if (application.Globals.DeploymentKind == DeploymentKind.Kubernetes) |
|||
{ |
|||
steps.Add(new GenerateKubernetesManifestStep() { Environment = environment, }); |
|||
} |
|||
else if (application.Globals.DeploymentKind == DeploymentKind.Oam) |
|||
{ |
|||
steps.Add(new GenerateOamComponentStep() { Environment = environment, }); |
|||
} |
|||
else |
|||
{ |
|||
throw new InvalidOperationException($"Unknown DeploymentKind: " + application.Globals.DeploymentKind); |
|||
} |
|||
|
|||
// 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(".sln", projectFile.Extension, StringComparison.Ordinal)) |
|||
{ |
|||
steps.Add(new DeployServiceYamlStep() { Environment = environment, }); |
|||
} |
|||
|
|||
var executor = new ServiceExecutor(output, application, steps); |
|||
foreach (var service in application.Services) |
|||
{ |
|||
if (service.IsMatchForProject(application, projectFile)) |
|||
{ |
|||
await executor.ExecuteAsync(service); |
|||
} |
|||
} |
|||
|
|||
if (string.Equals(".sln", projectFile.Extension, StringComparison.Ordinal)) |
|||
{ |
|||
await PackageApplicationAsync(output, application, Path.GetFileNameWithoutExtension(projectFile.Name), environment); |
|||
} |
|||
} |
|||
|
|||
private static async Task PackageApplicationAsync(OutputContext output, 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); |
|||
|
|||
if (application.Globals.DeploymentKind == DeploymentKind.None) |
|||
{ |
|||
// No extra steps
|
|||
} |
|||
else if (application.Globals.DeploymentKind == DeploymentKind.Kubernetes) |
|||
{ |
|||
await ApplicationYamlWriter.WriteAsync(output, writer, application); |
|||
} |
|||
else if (application.Globals.DeploymentKind == DeploymentKind.Oam) |
|||
{ |
|||
await OamApplicationGenerator.WriteOamApplicationAsync(writer, output, application, applicationName, environment); |
|||
} |
|||
else |
|||
{ |
|||
throw new InvalidOperationException($"Unknown DeploymentKind: " + application.Globals.DeploymentKind); |
|||
} |
|||
} |
|||
|
|||
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(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,193 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.CommandLine; |
|||
using System.CommandLine.Builder; |
|||
using System.CommandLine.Invocation; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Build.Construction; |
|||
|
|||
namespace Opulence |
|||
{ |
|||
internal static class InitCommand |
|||
{ |
|||
public static Command Create() |
|||
{ |
|||
var command = new Command("init", "Initialize repo") |
|||
{ |
|||
StandardOptions.Verbosity, |
|||
new Option(new[] { "-d", "--directory", }, "Directory to Initialize") |
|||
{ |
|||
Argument = new Argument<DirectoryInfo>(() => |
|||
{ |
|||
return new DirectoryInfo(Environment.CurrentDirectory); |
|||
}).ExistingOnly(), |
|||
}, |
|||
}; |
|||
|
|||
command.Handler = CommandHandler.Create<IConsole, Verbosity, DirectoryInfo>((console, verbosity, directory) => |
|||
{ |
|||
var output = new OutputContext(console, verbosity); |
|||
return ExecuteAsync(output, directory); |
|||
}); |
|||
|
|||
return command; |
|||
} |
|||
|
|||
private static async Task ExecuteAsync(OutputContext output, DirectoryInfo directory) |
|||
{ |
|||
output.WriteBanner(); |
|||
|
|||
string? solutionFilePath = null; |
|||
string? opulenceFilePath = null; |
|||
|
|||
using (var step = output.BeginStep("Looking For Existing Config...")) |
|||
{ |
|||
opulenceFilePath = DirectorySearch.AscendingSearch(directory.FullName, "Opulence.csx"); |
|||
if (opulenceFilePath != null) |
|||
{ |
|||
output.WriteInfoLine($"Found 'Opulence.csx' at '{opulenceFilePath}'"); |
|||
step.MarkComplete(); |
|||
return; |
|||
} |
|||
else |
|||
{ |
|||
output.WriteInfoLine("Not Found"); |
|||
step.MarkComplete(); |
|||
} |
|||
} |
|||
|
|||
using (var step = output.BeginStep("Looking For .sln File...")) |
|||
{ |
|||
solutionFilePath = DirectorySearch.AscendingWildcardSearch(directory.FullName, "*.sln").FirstOrDefault()?.FullName; |
|||
if (opulenceFilePath == null && |
|||
solutionFilePath != null && |
|||
output.Confirm($"Use '{Path.GetDirectoryName(solutionFilePath)}' as Root?")) |
|||
{ |
|||
opulenceFilePath = Path.Combine(Path.GetDirectoryName(solutionFilePath)!, "Opulence.csx"); |
|||
step.MarkComplete(); |
|||
} |
|||
else |
|||
{ |
|||
output.WriteInfoLine("Not Found."); |
|||
step.MarkComplete(); |
|||
} |
|||
} |
|||
|
|||
if (opulenceFilePath == null && |
|||
Path.GetFullPath(directory.FullName) != Path.GetFullPath(Environment.CurrentDirectory)) |
|||
{ |
|||
// User specified a directory other than the current one
|
|||
using (var step = output.BeginStep("Trying Project Directory...")) |
|||
{ |
|||
if (output.Confirm("Use Project Directory as Root?")) |
|||
{ |
|||
opulenceFilePath = Path.Combine(directory.FullName, "Opulence.csx"); |
|||
} |
|||
|
|||
step.MarkComplete(); |
|||
} |
|||
} |
|||
|
|||
if (opulenceFilePath == null) |
|||
{ |
|||
using (var step = output.BeginStep("Trying Current Directory...")) |
|||
{ |
|||
if (output.Confirm("Use Current Directory as Root?")) |
|||
{ |
|||
opulenceFilePath = Path.Combine(directory.FullName, "Opulence.csx"); |
|||
} |
|||
|
|||
step.MarkComplete(); |
|||
} |
|||
} |
|||
|
|||
if (opulenceFilePath == null) |
|||
{ |
|||
throw new CommandException("Cannot Determine Root Directory."); |
|||
} |
|||
|
|||
using (var step = output.BeginStep("Writing Opulence.csx ...")) |
|||
{ |
|||
var hostname = output.Prompt("Enter the Container Registry (ex: 'example.azurecr.io' for Azure or 'example' for dockerhub)"); |
|||
|
|||
var services = new List<(string, string)>(); |
|||
if (solutionFilePath != null && output.Confirm($"Use solution file '{solutionFilePath}' to initialize services?")) |
|||
{ |
|||
services.AddRange(ReadServicesFromSolution(solutionFilePath)); |
|||
services.Sort((a, b) => a.Item1.CompareTo(b.Item1)); |
|||
} |
|||
|
|||
using var stream = File.OpenWrite(opulenceFilePath); |
|||
using var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true); |
|||
|
|||
await WriteOpulenceConfigAsync(writer, hostname, services); |
|||
|
|||
output.WriteInfoLine($"Initialized Opulence Config at '{opulenceFilePath}'."); |
|||
step.MarkComplete(); |
|||
} |
|||
} |
|||
|
|||
private static IEnumerable<(string, string)> ReadServicesFromSolution(string solutionFilePath) |
|||
{ |
|||
SolutionFile solution; |
|||
try |
|||
{ |
|||
solution = SolutionFile.Parse(solutionFilePath); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new CommandException($"Parsing solution file '{solutionFilePath}' failed.", ex); |
|||
} |
|||
|
|||
for (var i = 0; i < solution.ProjectsInOrder.Count; i++) |
|||
{ |
|||
var project = solution.ProjectsInOrder[i]; |
|||
if (string.Equals(Path.GetExtension(project.RelativePath), ".csproj", StringComparison.Ordinal)) |
|||
{ |
|||
var fileName = Path.GetFileNameWithoutExtension(project.RelativePath.Replace('\\', Path.DirectorySeparatorChar)); |
|||
|
|||
yield return (Names.NormalizeToFriendly(fileName), Names.NormalizeToDns(fileName)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static async Task WriteOpulenceConfigAsync(TextWriter writer, string hostname, List<(string, string)> services) |
|||
{ |
|||
await writer.WriteLineAsync("#r \"Opulence\""); |
|||
await writer.WriteLineAsync(); |
|||
|
|||
await writer.WriteLineAsync($"using Opulence;"); |
|||
await writer.WriteLineAsync(); |
|||
|
|||
await writer.WriteLineAsync($"public class Application"); |
|||
await writer.WriteLineAsync($"{{"); |
|||
await writer.WriteLineAsync($" public ApplicationGlobals Globals {{ get; }} = new ApplicationGlobals()"); |
|||
await writer.WriteLineAsync($" {{"); |
|||
await writer.WriteLineAsync($" Registry = new ContainerRegistry(\"{hostname}\"),"); |
|||
await writer.WriteLineAsync($" }};"); |
|||
await writer.WriteLineAsync(); |
|||
|
|||
await writer.WriteLineAsync($" // Define more services and dependencies here as your application grows."); |
|||
for (var i = 0; i < services.Count; i++) |
|||
{ |
|||
await writer.WriteLineAsync($" public Service {services[i].Item1} {{ get; }} = new Service(\"{services[i].Item2}\");"); |
|||
|
|||
if (i + 1 < services.Count) |
|||
{ |
|||
await writer.WriteLineAsync(); |
|||
} |
|||
} |
|||
|
|||
await writer.WriteLineAsync($"}}"); |
|||
await writer.WriteLineAsync(); |
|||
|
|||
await writer.WriteLineAsync($"Pipeline.Configure<Application>(app =>"); |
|||
await writer.WriteLineAsync($"{{"); |
|||
await writer.WriteLineAsync($" // Configure your service bindings here with code."); |
|||
await writer.WriteLineAsync($"}});"); |
|||
} |
|||
} |
|||
} |
|||
@ -1,82 +1,9 @@ |
|||
using System; |
|||
using System.CommandLine; |
|||
using System.CommandLine.Builder; |
|||
using System.CommandLine.Help; |
|||
using System.CommandLine.Invocation; |
|||
using System.CommandLine.IO; |
|||
using System.CommandLine.Parsing; |
|||
using System.CommandLine.Rendering; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Opulence |
|||
{ |
|||
class Program |
|||
static class Program |
|||
{ |
|||
static async Task<int> Main(string[] args) |
|||
public static void Main() |
|||
{ |
|||
var command = new RootCommand(); |
|||
command.AddCommand(InitCommand.Create()); |
|||
command.AddCommand(GenerateCommand.Create()); |
|||
command.AddCommand(PackageCommand.Create()); |
|||
command.AddCommand(PushCommand.Create()); |
|||
command.AddCommand(DeployCommand.Create()); |
|||
|
|||
command.Description = "White-Glove Service for .NET and Kubernetes"; |
|||
command.Handler = CommandHandler.Create<IHelpBuilder>(help => |
|||
{ |
|||
help.Write(command); |
|||
return 1; |
|||
}); |
|||
|
|||
var builder = new CommandLineBuilder(command); |
|||
|
|||
// Parsing behavior
|
|||
builder.UseHelp(); |
|||
builder.UseVersionOption(); |
|||
builder.UseDebugDirective(); |
|||
builder.UseParseErrorReporting(); |
|||
builder.ParseResponseFileAs(ResponseFileHandling.ParseArgsAsSpaceSeparated); |
|||
builder.UsePrefixes(new[] { "-", "--", }); // disable garbage windows conventions
|
|||
|
|||
builder.CancelOnProcessTermination(); |
|||
builder.UseExceptionHandler(HandleException); |
|||
|
|||
// Allow fancy drawing.
|
|||
builder.UseAnsiTerminalWhenAvailable(); |
|||
|
|||
var parser = builder.Build(); |
|||
return await parser.InvokeAsync(args); |
|||
} |
|||
|
|||
private static void HandleException(Exception exception, InvocationContext context) |
|||
{ |
|||
context.Console.ResetTerminalForegroundColor(); |
|||
context.Console.SetTerminalForegroundColor(ConsoleColor.Red); |
|||
|
|||
if (exception is OperationCanceledException) |
|||
{ |
|||
context.Console.Error.WriteLine("Oh dear! Operation canceled."); |
|||
} |
|||
else if (exception is CommandException command) |
|||
{ |
|||
context.Console.Error.WriteLine($"Drats! '{context.ParseResult.CommandResult.Command.Name}' failed:"); |
|||
context.Console.Error.WriteLine($"\t{command.Message}"); |
|||
|
|||
if (command.InnerException != null) |
|||
{ |
|||
context.Console.Error.WriteLine(); |
|||
context.Console.Error.WriteLine(command.InnerException.ToString()); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
context.Console.Error.WriteLine("An unhandled exception has occurred, how unseemly: "); |
|||
context.Console.Error.WriteLine(exception.ToString()); |
|||
} |
|||
|
|||
context.Console.ResetTerminalForegroundColor(); |
|||
|
|||
context.ResultCode = 1; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue