Compare commits

...

5 Commits

Author SHA1 Message Date
David Fowler eab7abe09b Undo output context changes 7 years ago
David Fowler 4243d659c1 Tweaks 7 years ago
David Fowler 848af10ee4 Remove unused parameter 7 years ago
David Fowler 532386a39d Do msbuild things once 7 years ago
David Fowler 3a67a5c3a4 WIP 7 years ago
  1. 49
      src/Microsoft.Tye.Core/ApplicationBuilderExtensions.cs
  2. 5
      src/Microsoft.Tye.Core/ApplicationFactory.cs
  3. 1
      src/Microsoft.Tye.Core/ConfigModel/ConfigService.cs
  4. 2
      src/Microsoft.Tye.Core/ContainerServiceBuilder.cs
  5. 33
      src/Microsoft.Tye.Core/ProjectReader.cs
  6. 2
      src/Microsoft.Tye.Core/ProjectServiceBuilder.cs
  7. 7
      src/Microsoft.Tye.Core/Serialization/ConfigServiceParser.cs
  8. 2
      src/Microsoft.Tye.Hosting/Model/ProjectRunInfo.cs
  9. 1
      src/Microsoft.Tye.Hosting/Model/V1/V1RunInfo.cs
  10. 25
      src/Microsoft.Tye.Hosting/ProcessRunner.cs
  11. 93
      src/Microsoft.Tye.Hosting/TransformProjectsIntoContainers.cs
  12. 1
      src/Microsoft.Tye.Hosting/TyeDashboardApi.cs
  13. 6
      src/Microsoft.Tye.Hosting/TyeHost.cs
  14. 5
      src/tye/ApplicationBuilderExtensions.cs
  15. 27
      src/tye/Program.RunCommand.cs
  16. 5
      test/E2ETest/TyeRunTests.cs
  17. 1
      test/UnitTests/TyeDeserializationTests.cs

49
src/Microsoft.Tye.Core/ApplicationBuilderExtensions.cs

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.Tye
{
public static class ApplicationBuilderExtensions
{
public static void TransformProjectsIntoContainers(this ApplicationBuilder application)
{
for (var i = 0; i < application.Services.Count; i++)
{
var service = application.Services[i];
if (!(service is ProjectServiceBuilder project))
{
continue;
}
static string DetermineContainerImage(ProjectServiceBuilder project)
{
return $"mcr.microsoft.com/dotnet/core/sdk:{project.TargetFrameworkVersion}";
}
// We transform the project information into the following docker command:
// docker run -w /app -v {publishDir}:/app -it {image} dotnet {outputfile}.dll
var containerImage = DetermineContainerImage(project);
var outputFileName = project.AssemblyName + ".dll";
var containerService = new ContainerServiceBuilder(service.Name, containerImage)
{
Replicas = project.Replicas,
Args = $"dotnet {outputFileName} {project.Args}",
WorkingDirectory = "/app"
};
containerService.Volumes.Add(new VolumeBuilder(source: project.PublishDir, name: null, target: "/app"));
// Make volume mapping works when running as a container
containerService.Volumes.AddRange(project.Volumes);
containerService.Bindings.AddRange(project.Bindings);
containerService.EnvironmentVariables.AddRange(project.EnvironmentVariables);
application.Services[i] = containerService;
}
}
}
}

5
src/Microsoft.Tye.Core/ApplicationFactory.cs

@ -14,7 +14,7 @@ namespace Microsoft.Tye
{
public static class ApplicationFactory
{
public static async Task<ApplicationBuilder> CreateAsync(OutputContext output, FileInfo source)
public static async Task<ApplicationBuilder> CreateAsync(OutputContext output, FileInfo source, string[]? targets = null)
{
if (source is null)
{
@ -57,11 +57,10 @@ namespace Microsoft.Tye
var project = new ProjectServiceBuilder(configService.Name!, projectFile);
service = project;
project.Build = configService.Build ?? true;
project.Args = configService.Args;
project.Replicas = configService.Replicas ?? 1;
await ProjectReader.ReadProjectDetailsAsync(output, project);
await ProjectReader.ReadProjectDetailsAsync(output, project, targets);
// We don't apply more container defaults here because we might need
// to prompt for the registry name.

1
src/Microsoft.Tye.Core/ConfigModel/ConfigService.cs

@ -15,7 +15,6 @@ namespace Microsoft.Tye.ConfigModel
public bool External { get; set; }
public string? Image { get; set; }
public string? Project { get; set; }
public bool? Build { get; set; }
public string? Executable { get; set; }
public string? WorkingDirectory { get; set; }
public string? Args { get; set; }

2
src/Microsoft.Tye.Core/ContainerServiceBuilder.cs

@ -23,5 +23,7 @@ namespace Microsoft.Tye
public List<EnvironmentVariableBuilder> EnvironmentVariables { get; } = new List<EnvironmentVariableBuilder>();
public List<VolumeBuilder> Volumes { get; } = new List<VolumeBuilder>();
public string? WorkingDirectory { get; set; }
}
}

33
src/Microsoft.Tye.Core/ProjectReader.cs

@ -60,7 +60,7 @@ namespace Microsoft.Tye
}
}
public static Task ReadProjectDetailsAsync(OutputContext output, ProjectServiceBuilder project)
public static Task<bool> ReadProjectDetailsAsync(OutputContext output, ProjectServiceBuilder project, string[]? targets = null)
{
if (output is null)
{
@ -74,7 +74,7 @@ namespace Microsoft.Tye
EnsureMSBuildRegistered(output, project.ProjectFile);
EvaluateProject(output, project);
var result = EvaluateProject(output, project, targets);
if (!SemVersion.TryParse(project.Version, out var version))
{
@ -83,7 +83,7 @@ namespace Microsoft.Tye
project.Version = version.ToString();
}
return Task.CompletedTask;
return Task.FromResult(result);
}
private static void EnsureMSBuildRegistered(OutputContext? output, FileInfo projectFile)
@ -141,8 +141,10 @@ namespace Microsoft.Tye
// Do not load MSBuild types before using EnsureMSBuildRegistered.
[MethodImpl(MethodImplOptions.NoInlining)]
private static void EvaluateProject(OutputContext output, ProjectServiceBuilder project)
private static bool EvaluateProject(OutputContext output, ProjectServiceBuilder project, string[]? targets = null)
{
var result = true;
var sw = Stopwatch.StartNew();
// We need to isolate projects from each other for testing. MSBuild does not support
@ -168,17 +170,19 @@ namespace Microsoft.Tye
// Currently we only log at debug level.
var logger = new ConsoleLogger(
verbosity: LoggerVerbosity.Normal,
verbosity: LoggerVerbosity.Quiet,
write: message => output.WriteDebug(message),
colorSet: null,
colorReset: null);
try
{
targets ??= new[] { "Restore", "ResolveReferences", "ResolvePackageDependenciesDesignTime", "PrepareResources", "GetAssemblyAttributes" };
output.WriteDebugLine($"Executing targtes: {string.Join(", ", targets)}");
AssemblyLoadContext.Default.Resolving += ResolveAssembly;
var result = projectInstance.Build(
targets: new[] { "Restore", "ResolveReferences", "ResolvePackageDependenciesDesignTime", "PrepareResources", "GetAssemblyAttributes", },
loggers: new[] { logger, });
result = projectInstance.Build(targets, loggers: new[] { logger });
// If the build fails, we're not really blocked from doing our work.
// For now we just log the output to debug. There are errors that occur during
@ -234,6 +238,7 @@ namespace Microsoft.Tye
output.WriteDebugLine($"Evaluation Took: {sw.Elapsed.TotalMilliseconds}ms");
return result;
// The Microsoft.Build.Locator doesn't handle the loading of other assemblies
// that are shipped with MSBuild (ex NuGet).
//
@ -246,14 +251,12 @@ namespace Microsoft.Tye
// See: https://github.com/microsoft/MSBuildLocator/issues/86
Assembly? ResolveAssembly(AssemblyLoadContext context, AssemblyName assemblyName)
{
if (assemblyName.Name is object && assemblyName.Name.StartsWith("NuGet."))
var msbuildDirectory = Environment.GetEnvironmentVariable("MSBuildExtensionsPath")!;
var assemblyFilePath = Path.Combine(msbuildDirectory, assemblyName.Name + ".dll");
if (File.Exists(assemblyFilePath))
{
var msbuildDirectory = Environment.GetEnvironmentVariable("MSBuildExtensionsPath")!;
var assemblyFilePath = Path.Combine(msbuildDirectory, assemblyName.Name + ".dll");
if (File.Exists(assemblyFilePath))
{
return context.LoadFromAssemblyPath(assemblyFilePath);
}
return context.LoadFromAssemblyPath(assemblyFilePath);
}
return default;

2
src/Microsoft.Tye.Core/ProjectServiceBuilder.cs

@ -19,8 +19,6 @@ namespace Microsoft.Tye
public int Replicas { get; set; } = 1;
public bool Build { get; set; }
public string? Args { get; set; }
public FrameworkCollection Frameworks { get; } = new FrameworkCollection();

7
src/Microsoft.Tye.Core/Serialization/ConfigServiceParser.cs

@ -45,13 +45,6 @@ namespace Tye.Serialization
case "project":
service.Project = YamlParser.GetScalarValue(key, child.Value);
break;
case "build":
if (!bool.TryParse(YamlParser.GetScalarValue(key, child.Value), out var build))
{
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatMustBeABoolean(key));
}
service.Build = build;
break;
case "executable":
service.Executable = YamlParser.GetScalarValue(key, child.Value);
break;

2
src/Microsoft.Tye.Hosting/Model/ProjectRunInfo.cs

@ -13,7 +13,6 @@ namespace Microsoft.Tye.Hosting.Model
{
ProjectFile = project.ProjectFile;
Args = project.Args;
Build = project.Build;
TargetFramework = project.TargetFramework;
TargetFrameworkName = project.TargetFrameworkName;
TargetFrameworkVersion = project.TargetFrameworkVersion;
@ -26,7 +25,6 @@ namespace Microsoft.Tye.Hosting.Model
}
public string? Args { get; }
public bool Build { get; }
public FileInfo ProjectFile { get; }
public string TargetFrameworkName { get; set; } = default!;
public string TargetFrameworkVersion { get; set; } = default!;

1
src/Microsoft.Tye.Hosting/Model/V1/V1RunInfo.cs

@ -10,7 +10,6 @@ namespace Microsoft.Tye.Hosting.Model.V1
{
public V1RunInfoType Type { get; set; }
public string? Args { get; set; }
public bool Build { get; set; }
public string? Project { get; set; }
public string? WorkingDirectory { get; set; }
public List<V1DockerVolume>? VolumeMappings { get; set; }

25
src/Microsoft.Tye.Hosting/ProcessRunner.cs

@ -56,7 +56,7 @@ namespace Microsoft.Tye.Hosting
return KillRunningProcesses(application.Services);
}
private async Task LaunchService(Application application, Service service)
private Task LaunchService(Application application, Service service)
{
var serviceDescription = service.Description;
var serviceName = serviceDescription.Name;
@ -96,27 +96,6 @@ namespace Microsoft.Tye.Hosting
var processInfo = new ProcessInfo(new Task[service.Description.Replicas]);
if (service.Status.ProjectFilePath != null &&
service.Description.RunInfo is ProjectRunInfo project2 &&
project2.Build &&
_options.BuildProjects)
{
// Sometimes building can fail because of file locking (like files being open in VS)
_logger.LogInformation("Building project {ProjectFile}", service.Status.ProjectFilePath);
service.Logs.OnNext($"dotnet build \"{service.Status.ProjectFilePath}\" /nologo");
var buildResult = await ProcessUtil.RunAsync("dotnet", $"build \"{service.Status.ProjectFilePath}\" /nologo", throwOnError: false, workingDirectory: workingDirectory);
service.Logs.OnNext(buildResult.StandardOutput);
if (buildResult.ExitCode != 0)
{
_logger.LogInformation("Building {ProjectFile} failed with exit code {ExitCode}: \r\n" + buildResult.StandardOutput, service.Status.ProjectFilePath, buildResult.ExitCode);
return;
}
}
async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string? Protocol)> ports)
{
// Make sure we yield before trying to start the process, this is important so we don't block startup
@ -298,6 +277,8 @@ namespace Microsoft.Tye.Hosting
}
service.Items[typeof(ProcessInfo)] = processInfo;
return Task.CompletedTask;
}
private Task KillRunningProcesses(IDictionary<string, Service> services)

93
src/Microsoft.Tye.Hosting/TransformProjectsIntoContainers.cs

@ -1,93 +0,0 @@
// 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.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Tye.Hosting.Model;
namespace Microsoft.Tye.Hosting
{
public class TransformProjectsIntoContainers : IApplicationProcessor
{
private readonly ILogger _logger;
public TransformProjectsIntoContainers(ILogger logger)
{
_logger = logger;
}
public Task StartAsync(Application application)
{
// This transforms a ProjectRunInfo into a container
var tasks = new List<Task>();
foreach (var s in application.Services.Values)
{
if (s.Description.RunInfo is ProjectRunInfo project)
{
tasks.Add(TransformProjectToContainer(s, project));
}
}
return Task.WhenAll(tasks);
}
private async Task TransformProjectToContainer(Service service, ProjectRunInfo project)
{
var serviceDescription = service.Description;
var serviceName = serviceDescription.Name;
service.Status.ProjectFilePath = project.ProjectFile.FullName;
var targetFramework = project.TargetFramework;
// Sometimes building can fail because of file locking (like files being open in VS)
_logger.LogInformation("Publishing project {ProjectFile}", service.Status.ProjectFilePath);
var publishCommand = $"publish \"{service.Status.ProjectFilePath}\" --framework {targetFramework} /nologo";
service.Logs.OnNext($"dotnet {publishCommand}");
var buildResult = await ProcessUtil.RunAsync("dotnet", publishCommand, throwOnError: false);
service.Logs.OnNext(buildResult.StandardOutput);
if (buildResult.ExitCode != 0)
{
_logger.LogInformation("Publishing {ProjectFile} failed with exit code {ExitCode}: \r\n" + buildResult.StandardOutput, service.Status.ProjectFilePath, buildResult.ExitCode);
// Null out the RunInfo so that
serviceDescription.RunInfo = null;
return;
}
// We transform the project information into the following docker command:
// docker run -w /app -v {publishDir}:/app -it {image} dotnet {outputfile}.dll
var containerImage = DetermineContainerImage(project);
var outputFileName = project.AssemblyName + ".dll";
var dockerRunInfo = new DockerRunInfo(containerImage, $"dotnet {outputFileName} {project.Args}")
{
WorkingDirectory = "/app"
};
dockerRunInfo.VolumeMappings.Add(new DockerVolume(source: project.PublishOutputPath, name: null, target: "/app"));
// Make volume mapping works when running as a container
dockerRunInfo.VolumeMappings.AddRange(project.VolumeMappings);
// Change the project into a container info
serviceDescription.RunInfo = dockerRunInfo;
}
private static string DetermineContainerImage(ProjectRunInfo project)
{
return $"mcr.microsoft.com/dotnet/core/sdk:{project.TargetFrameworkVersion}";
}
public Task StopAsync(Application application)
{
return Task.CompletedTask;
}
}
}

1
src/Microsoft.Tye.Hosting/TyeDashboardApi.cs

@ -151,7 +151,6 @@ namespace Microsoft.Tye.Hosting
{
v1RunInfo.Type = V1RunInfoType.Project;
v1RunInfo.Args = projectRunInfo.Args;
v1RunInfo.Build = projectRunInfo.Build;
v1RunInfo.Project = projectRunInfo.ProjectFile.FullName;
}

6
src/Microsoft.Tye.Hosting/TyeHost.cs

@ -264,12 +264,6 @@ namespace Microsoft.Tye.Hosting
new ProcessRunner(logger, replicaRegistry, ProcessRunnerOptions.FromArgs(args, servicesToDebug))
};
// If the docker command is specified then transform the ProjectRunInfo into DockerRunInfo
if (args.Contains("--docker"))
{
processors.Insert(0, new TransformProjectsIntoContainers(logger));
}
return new AggregateApplicationProcessor(processors);
}

5
src/tye/ApplicationBuilderExtensions.cs

@ -43,7 +43,10 @@ namespace Microsoft.Tye
}
else if (service is ContainerServiceBuilder container)
{
var dockerRunInfo = new DockerRunInfo(container.Image, container.Args);
var dockerRunInfo = new DockerRunInfo(container.Image, container.Args)
{
WorkingDirectory = container.WorkingDirectory
};
foreach (var mapping in container.Volumes)
{

27
src/tye/Program.RunCommand.cs

@ -7,6 +7,7 @@ using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.Tye.ConfigModel;
using Microsoft.Tye.Extensions;
@ -64,7 +65,7 @@ namespace Microsoft.Tye
Required = false
});
command.Handler = CommandHandler.Create<IConsole, FileInfo, string[]>(async (console, path, debug) =>
command.Handler = CommandHandler.Create<IConsole, FileInfo, string[], bool, bool>(async (console, path, debug, docker, nobuild) =>
{
// Workaround for https://github.com/dotnet/command-line-api/issues/723#issuecomment-593062654
if (path is null)
@ -72,8 +73,28 @@ namespace Microsoft.Tye
throw new CommandException("No project or solution file was found.");
}
var output = new OutputContext(console, Verbosity.Quiet);
var application = await ApplicationFactory.CreateAsync(output, path);
string[]? targets = null;
if (docker)
{
targets = new[] { "Restore", "Publish" };
}
else if (nobuild)
{
targets = null;
}
else
{
targets = new[] { "Restore", "Build" };
}
var output = new OutputContext(console, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(output, path, targets);
if (docker)
{
application.TransformProjectsIntoContainers();
}
await application.ProcessExtensionsAsync(ExtensionContext.OperationKind.LocalRun);

5
test/E2ETest/TyeRunTests.cs

@ -536,6 +536,11 @@ namespace E2ETest
private async Task RunHostingApplication(ApplicationBuilder application, string[] args, Func<Application, Uri, Task> execute)
{
if (args.Contains("--docker"))
{
application.TransformProjectsIntoContainers();
}
await using var host = new TyeHost(application.ToHostingApplication(), args)
{
Sink = _sink,

1
test/UnitTests/TyeDeserializationTests.cs

@ -180,7 +180,6 @@ ingress:
.Single();
Assert.NotNull(otherService);
Assert.Equal(otherService.Args, service.Args);
Assert.Equal(otherService.Build, service.Build);
Assert.Equal(otherService.Executable, service.Executable);
Assert.Equal(otherService.External, service.External);
Assert.Equal(otherService.Image, service.Image);

Loading…
Cancel
Save