Browse Source

Implement single-phase docker workflow

This implements support for building docker images using a single-phase
workflow (publish then build docker). This is going to be faster in
almost every case since it avoids the slowest part of deployment
(running restore inside a container in the multi-phase process).
This additionally supports .NET constructs like p2ps and shared files
without any customization - you can just customize dotnet publish
and be done!

So, switching to single-phase by default because there are few drawbacks
for our target use cases.
pull/80/head
Ryan Nowak 6 years ago
parent
commit
e3db88c5f7
  1. 24
      src/Tye.Core/DockerContainerBuilder.cs
  2. 18
      src/Tye.Core/ProjectPublishOutput.cs
  3. 56
      src/Tye.Core/PublishProjectStep.cs
  4. 3
      src/tye/Program.DeployCommand.cs
  5. 1
      src/tye/Program.GenerateCommand.cs

24
src/Tye.Core/DockerContainerBuilder.cs

@ -5,6 +5,7 @@
using System;
using System.CommandLine.Invocation;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Tye
@ -51,12 +52,31 @@ namespace Tye
dockerFilePath = tempFile.FilePath;
}
// We need to know if this is a single-phase or multi-phase dockerfile because the context directory will be
// different depending on that choice.
string contextDirectory;
if (container.UseMultiphaseDockerfile ?? true)
{
contextDirectory = ".";
}
else
{
var publishOutput = service.Outputs.OfType<ProjectPublishOutput>().FirstOrDefault();
if (publishOutput is null)
{
throw new InvalidOperationException("We should have published the project for a single-phase dockerfile.");
}
contextDirectory = publishOutput.Directory.FullName;
}
output.WriteDebugLine("Running 'docker build'.");
output.WriteCommandLine("docker", $"build . -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"");
output.WriteCommandLine("docker", $"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"");
var capture = output.Capture();
var exitCode = await Process.ExecuteAsync(
$"docker",
$"build . -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"",
$"build \"{contextDirectory}\" -t {container.ImageName}:{container.ImageTag} -f \"{dockerFilePath}\"",
application.GetProjectDirectory(project),
stdOut: capture.StdOut,
stdErr: capture.StdErr);

18
src/Tye.Core/ProjectPublishOutput.cs

@ -0,0 +1,18 @@
// 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.IO;
namespace Tye
{
public class ProjectPublishOutput : ServiceOutput
{
public ProjectPublishOutput(DirectoryInfo directory)
{
Directory = directory;
}
public DirectoryInfo Directory { get; }
}
}

56
src/Tye.Core/PublishProjectStep.cs

@ -0,0 +1,56 @@
// 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.CommandLine.Invocation;
using System.IO;
using System.Threading.Tasks;
namespace Tye
{
// Used to publish a project when using a single-file dockerfile
public class PublishProjectStep : ServiceExecutor.Step
{
public override string DisplayText => "Publishing Project...";
public override async Task ExecuteAsync(OutputContext output, Application application, ServiceEntry service)
{
if (SkipWithoutProject(output, service, out var project))
{
return;
}
if (SkipWithoutContainerInfo(output, service, out var container))
{
return;
}
if (container.UseMultiphaseDockerfile != false)
{
return;
}
var projectFilePath = Path.Combine(application.RootDirectory, project.RelativeFilePath);
var outputDirectory = Path.Combine(Path.GetDirectoryName(projectFilePath), "bin", "Release", project.TargetFramework, "publish");
output.WriteDebugLine("Running 'dotnet publish'.");
output.WriteCommandLine("dotnet", $"publish \"{projectFilePath}\" -c Release -o \"{outputDirectory}\"");
var capture = output.Capture();
var exitCode = await Process.ExecuteAsync(
$"dotnet",
$"publish \"{projectFilePath}\" -c Release -o \"{outputDirectory}\"",
application.GetProjectDirectory(project),
stdOut: capture.StdOut,
stdErr: capture.StdErr);
output.WriteDebugLine($"Done running 'dotnet publish' exit code: {exitCode}");
if (exitCode != 0)
{
throw new CommandException("'dotnet publish' failed.");
}
output.WriteInfoLine($"Created Publish Output: '{outputDirectory}'");
service.Outputs.Add(new ProjectPublishOutput(new DirectoryInfo(outputDirectory)));
}
}
}

3
src/tye/Program.DeployCommand.cs

@ -44,6 +44,7 @@ namespace Tye
var steps = new List<ServiceExecutor.Step>()
{
new CombineStep() { Environment = environment, },
new PublishProjectStep(),
new BuildDockerImageStep() { Environment = environment, },
new PushDockerImageStep() { Environment = environment, },
};
@ -100,7 +101,7 @@ namespace Tye
var container = new ContainerInfo()
{
// Single-phase workflow doesn't currently work.
UseMultiphaseDockerfile = true,
UseMultiphaseDockerfile = false,
};
service.GeneratedAssets.Container = container;
services.Add(serviceEntry);

1
src/tye/Program.GenerateCommand.cs

@ -48,6 +48,7 @@ namespace Tye
var steps = new List<ServiceExecutor.Step>()
{
new CombineStep() { Environment = environment, },
new PublishProjectStep(),
new BuildDockerImageStep() { Environment = environment, }, // Make an image but don't push it
};

Loading…
Cancel
Save