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