mirror of https://github.com/dotnet/tye.git
12 changed files with 352 additions and 10 deletions
@ -0,0 +1,39 @@ |
|||||
|
// 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.CommandLine; |
||||
|
using System.IO; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Tye.ConfigModel; |
||||
|
|
||||
|
namespace Tye |
||||
|
{ |
||||
|
public static class BuildHost |
||||
|
{ |
||||
|
public static Task BuildAsync(IConsole console, FileInfo path, Verbosity verbosity, bool interactive) |
||||
|
{ |
||||
|
var application = ConfigFactory.FromFile(path); |
||||
|
return ExecuteBuildAsync(new OutputContext(console, verbosity), application, environment: "production", interactive); |
||||
|
} |
||||
|
|
||||
|
public static async Task ExecuteBuildAsync(OutputContext output, ConfigApplication application, string environment, bool interactive) |
||||
|
{ |
||||
|
var temporaryApplication = await Program.CreateApplicationAdapterAsync(output, application, interactive, requireRegistry: false); |
||||
|
var steps = new List<ServiceExecutor.Step>() |
||||
|
{ |
||||
|
new CombineStep() { Environment = environment, }, |
||||
|
new PublishProjectStep(), |
||||
|
new BuildDockerImageStep() { Environment = environment, }, |
||||
|
}; |
||||
|
|
||||
|
var executor = new ServiceExecutor(output, temporaryApplication, steps); |
||||
|
foreach (var service in temporaryApplication.Services) |
||||
|
{ |
||||
|
await executor.ExecuteAsync(service); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
// 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.CommandLine; |
||||
|
using System.CommandLine.Invocation; |
||||
|
using System.IO; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Tye.ConfigModel; |
||||
|
|
||||
|
namespace Tye |
||||
|
{ |
||||
|
static partial class Program |
||||
|
{ |
||||
|
public static Command CreateBuildCommand() |
||||
|
{ |
||||
|
var command = new Command("build", "build container for the application") |
||||
|
{ |
||||
|
CommonArguments.Path_Required, |
||||
|
StandardOptions.Interactive, |
||||
|
StandardOptions.Verbosity, |
||||
|
}; |
||||
|
|
||||
|
command.Handler = CommandHandler.Create<IConsole, FileInfo, Verbosity, bool>((console, path, verbosity, interactive) => |
||||
|
{ |
||||
|
// Workaround for https://github.com/dotnet/command-line-api/issues/723#issuecomment-593062654
|
||||
|
if (path is null) |
||||
|
{ |
||||
|
throw new CommandException("No project or solution file was found."); |
||||
|
} |
||||
|
|
||||
|
return BuildHost.BuildAsync(console, path, verbosity, interactive); |
||||
|
}); |
||||
|
|
||||
|
return command; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
// 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.CommandLine.Invocation; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Xunit.Abstractions; |
||||
|
using Xunit.Sdk; |
||||
|
|
||||
|
namespace E2ETest |
||||
|
{ |
||||
|
public static class DockerAssert |
||||
|
{ |
||||
|
// Repository is the "registry/image" format. Yeah Docker uses that term for it, and it's
|
||||
|
// wierd and confusing.
|
||||
|
public static async Task AssertImageExistsAsync(ITestOutputHelper output, string repository) |
||||
|
{ |
||||
|
if (repository is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(repository)); |
||||
|
} |
||||
|
|
||||
|
var builder = new StringBuilder(); |
||||
|
|
||||
|
var exitCode = await Process.ExecuteAsync( |
||||
|
"docker", |
||||
|
$"images \"{repository}\" --format \"{{{{.Repository}}}}\"", |
||||
|
stdOut: OnOutput, |
||||
|
stdErr: OnOutput); |
||||
|
if (exitCode != 0) |
||||
|
{ |
||||
|
throw new XunitException($"Running `docker images \"{repository}\"` failed." + Environment.NewLine + builder.ToString()); |
||||
|
} |
||||
|
|
||||
|
var lines = builder.ToString().Split(new[] { '\r', '\n', }); |
||||
|
if (lines.Any(line => line == repository)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
throw new XunitException($"Image '{repository}' was not found."); |
||||
|
|
||||
|
void OnOutput(string text) |
||||
|
{ |
||||
|
builder.AppendLine(text); |
||||
|
output.WriteLine(text); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
// 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 Xunit; |
||||
|
|
||||
|
// We have numerous tests that manipulate machine-wide state (docker, ports, etc)
|
||||
|
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)] |
||||
@ -0,0 +1,115 @@ |
|||||
|
// 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; |
||||
|
using System.Threading.Tasks; |
||||
|
using Tye; |
||||
|
using Tye.ConfigModel; |
||||
|
using Xunit; |
||||
|
using Xunit.Abstractions; |
||||
|
|
||||
|
namespace E2ETest |
||||
|
{ |
||||
|
public class TyeBuildTests |
||||
|
{ |
||||
|
private readonly ITestOutputHelper output; |
||||
|
private readonly TestOutputLogEventSink sink; |
||||
|
|
||||
|
public TyeBuildTests(ITestOutputHelper output) |
||||
|
{ |
||||
|
this.output = output; |
||||
|
sink = new TestOutputLogEventSink(output); |
||||
|
} |
||||
|
|
||||
|
[ConditionalFact] |
||||
|
[SkipIfDockerNotRunning] |
||||
|
public async Task SingleProjectBuildTest() |
||||
|
{ |
||||
|
var projectName = "single-project"; |
||||
|
var environment = "production"; |
||||
|
|
||||
|
var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", projectName)); |
||||
|
using var tempDirectory = TempDirectory.Create(); |
||||
|
DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath); |
||||
|
|
||||
|
var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml")); |
||||
|
|
||||
|
var application = ConfigFactory.FromFile(projectFile); |
||||
|
|
||||
|
application.Registry = "test"; |
||||
|
|
||||
|
await BuildHost.ExecuteBuildAsync(new OutputContext(sink, Verbosity.Debug), application, environment, interactive: false); |
||||
|
|
||||
|
await DockerAssert.AssertImageExistsAsync(output, "test/test-project"); |
||||
|
} |
||||
|
|
||||
|
[ConditionalFact] |
||||
|
[SkipIfDockerNotRunning] |
||||
|
public async Task FrontendBackendBuildTest() |
||||
|
{ |
||||
|
var projectName = "frontend-backend"; |
||||
|
var environment = "production"; |
||||
|
|
||||
|
var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", projectName)); |
||||
|
using var tempDirectory = TempDirectory.Create(); |
||||
|
DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath); |
||||
|
|
||||
|
var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml")); |
||||
|
|
||||
|
var application = ConfigFactory.FromFile(projectFile); |
||||
|
|
||||
|
application.Registry = "test"; |
||||
|
|
||||
|
await BuildHost.ExecuteBuildAsync(new OutputContext(sink, Verbosity.Debug), application, environment, interactive: false); |
||||
|
|
||||
|
await DockerAssert.AssertImageExistsAsync(output, "test/backend"); |
||||
|
await DockerAssert.AssertImageExistsAsync(output, "test/frontend"); |
||||
|
} |
||||
|
|
||||
|
[ConditionalFact] |
||||
|
[SkipIfDockerNotRunning] |
||||
|
public async Task MultipleProjectBuildTest() |
||||
|
{ |
||||
|
|
||||
|
var projectName = "multi-project"; |
||||
|
var environment = "production"; |
||||
|
|
||||
|
var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", projectName)); |
||||
|
using var tempDirectory = TempDirectory.Create(); |
||||
|
DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath); |
||||
|
|
||||
|
var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml")); |
||||
|
|
||||
|
var application = ConfigFactory.FromFile(projectFile); |
||||
|
|
||||
|
application.Registry = "test"; |
||||
|
|
||||
|
await BuildHost.ExecuteBuildAsync(new OutputContext(sink, Verbosity.Debug), application, environment, interactive: false); |
||||
|
|
||||
|
await DockerAssert.AssertImageExistsAsync(output, "test/backend"); |
||||
|
await DockerAssert.AssertImageExistsAsync(output, "test/frontend"); |
||||
|
await DockerAssert.AssertImageExistsAsync(output, "test/worker"); |
||||
|
} |
||||
|
|
||||
|
[ConditionalFact] |
||||
|
[SkipIfDockerNotRunning] |
||||
|
public async Task BuildDoesNotRequireRegistry() |
||||
|
{ |
||||
|
var projectName = "single-project"; |
||||
|
var environment = "production"; |
||||
|
|
||||
|
var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", projectName)); |
||||
|
using var tempDirectory = TempDirectory.Create(); |
||||
|
DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath); |
||||
|
|
||||
|
var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml")); |
||||
|
|
||||
|
var application = ConfigFactory.FromFile(projectFile); |
||||
|
|
||||
|
await BuildHost.ExecuteBuildAsync(new OutputContext(sink, Verbosity.Debug), application, environment, interactive: false); |
||||
|
|
||||
|
await DockerAssert.AssertImageExistsAsync(output, "test-project"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
kind: Deployment |
||||
|
apiVersion: apps/v1 |
||||
|
metadata: |
||||
|
name: test-project |
||||
|
labels: |
||||
|
app.kubernetes.io/name: test-project |
||||
|
app.kubernetes.io/part-of: single-project |
||||
|
spec: |
||||
|
replicas: 1 |
||||
|
selector: |
||||
|
matchLabels: |
||||
|
app.kubernetes.io/name: test-project |
||||
|
template: |
||||
|
metadata: |
||||
|
labels: |
||||
|
app.kubernetes.io/name: test-project |
||||
|
app.kubernetes.io/part-of: single-project |
||||
|
spec: |
||||
|
containers: |
||||
|
- name: test-project |
||||
|
image: test-project:1.0.0 |
||||
|
imagePullPolicy: Always |
||||
|
env: |
||||
|
- name: ASPNETCORE_URLS |
||||
|
value: http://*:5000 |
||||
|
ports: |
||||
|
- containerPort: 5001 |
||||
|
- containerPort: 5000 |
||||
|
... |
||||
|
--- |
||||
|
kind: Service |
||||
|
apiVersion: v1 |
||||
|
metadata: |
||||
|
name: test-project |
||||
|
labels: |
||||
|
app.kubernetes.io/name: test-project |
||||
|
app.kubernetes.io/part-of: single-project |
||||
|
spec: |
||||
|
selector: |
||||
|
app.kubernetes.io/name: test-project |
||||
|
type: ClusterIP |
||||
|
ports: |
||||
|
- name: test-project |
||||
|
protocol: TCP |
||||
|
port: 5001 |
||||
|
targetPort: 5001 |
||||
|
- name: test-project |
||||
|
protocol: TCP |
||||
|
port: 5000 |
||||
|
targetPort: 5000 |
||||
|
... |
||||
Loading…
Reference in new issue