Skip to content
This repository has been archived by the owner on Nov 20, 2023. It is now read-only.

Commit

Permalink
Allow specifying docker file for tye run (#448)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkotalik authored May 7, 2020
1 parent 1c7f770 commit af82001
Show file tree
Hide file tree
Showing 22 changed files with 731 additions and 8 deletions.
2 changes: 2 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ stages:
steps:
- checkout: self
clean: true
- script: docker version
- script: docker images
- script: eng\common\cibuild.cmd -configuration $(_BuildConfig) -prepareMachine $(_OfficialBuildArgs)
displayName: Build and Publish
- task: PublishBuildArtifacts@1
Expand Down
8 changes: 5 additions & 3 deletions src/Microsoft.Tye.Core/ApplicationFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,14 @@ public static async Task<ApplicationBuilder> CreateAsync(OutputContext output, F
// Do k8s by default.
project.ManifestInfo = new KubernetesManifestInfo();
}
else if (!string.IsNullOrEmpty(configService.Image))
else if (!string.IsNullOrEmpty(configService.Image) || !string.IsNullOrEmpty(configService.DockerFile))
{
var container = new ContainerServiceBuilder(configService.Name!, configService.Image)
var container = new ContainerServiceBuilder(configService.Name!, configService.Image!)
{
Args = configService.Args,
Replicas = configService.Replicas ?? 1
Replicas = configService.Replicas ?? 1,
DockerFile = configService.DockerFile != null ? Path.Combine(source.DirectoryName, configService.DockerFile) : null,
DockerFileContext = configService.DockerFileContext != null ? Path.Combine(source.DirectoryName, configService.DockerFileContext) : null
};
service = container;
}
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.Tye.Core/ConfigModel/ConfigService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class ConfigService
public string Name { get; set; } = default!;
public bool External { get; set; }
public string? Image { get; set; }
public string? DockerFile { get; set; }
public string? DockerFileContext { get; set; }
public string? Project { get; set; }
public string? Include { get; set; }
public string? Repository { get; set; }
Expand Down
4 changes: 4 additions & 0 deletions src/Microsoft.Tye.Core/ContainerServiceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public ContainerServiceBuilder(string name, string image)

public string? Args { get; set; }

public string? DockerFile { get; set; }

public string? DockerFileContext { get; set; }

public int Replicas { get; set; } = 1;

public List<EnvironmentVariableBuilder> EnvironmentVariables { get; } = new List<EnvironmentVariableBuilder>();
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.Tye.Core/Serialization/ConfigServiceParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ private static void HandleServiceNameMapping(YamlMappingNode yamlMappingNode, Co
case "image":
service.Image = YamlParser.GetScalarValue(key, child.Value);
break;
case "dockerFile":
service.DockerFile = YamlParser.GetScalarValue(key, child.Value);
break;
case "dockerFileContext":
service.DockerFileContext = YamlParser.GetScalarValue(key, child.Value);
break;
case "project":
service.Project = YamlParser.GetScalarValue(key, child.Value);
break;
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.Tye.Hosting/DockerImagePuller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public async Task StartAsync(Application application)

foreach (var s in application.Services)
{
if (s.Value.Description.RunInfo is DockerRunInfo docker)
if (s.Value.Description.RunInfo is DockerRunInfo docker && docker.DockerFile == null)
{
images.Add(docker.Image);
}
Expand Down
29 changes: 26 additions & 3 deletions src/Microsoft.Tye.Hosting/DockerRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public async Task StartAsync(Application application)

if (!string.IsNullOrEmpty(application.Network))
{
var dockerNetworkResult = await ProcessUtil.RunAsync("docker", $"network ls --filter \"name={application.Network}\" --format \"{{{{.ID}}}}\"");
var dockerNetworkResult = await ProcessUtil.RunAsync("docker", $"network ls --filter \"name={application.Network}\" --format \"{{{{.ID}}}}\"", throwOnError: false);
if (dockerNetworkResult.ExitCode != 0)
{
_logger.LogError("{Network}: Run docker network ls command failed", application.Network);
Expand Down Expand Up @@ -131,7 +131,14 @@ public async Task StartAsync(Application application)

_logger.LogInformation("Running docker command {Command}", command);

await ProcessUtil.RunAsync("docker", command);
var dockerNetworkResult = await ProcessUtil.RunAsync("docker", command, throwOnError: false);

if (dockerNetworkResult.ExitCode != 0)
{
_logger.LogInformation("Running docker command with exception info {ExceptionStdOut} {ExceptionStdErr}", dockerNetworkResult.StandardOutput, dockerNetworkResult.StandardError);

throw new CommandException("Run docker network create command failed");
}
}

// Stash information outside of the application services
Expand Down Expand Up @@ -195,6 +202,22 @@ private async Task StartContainerAsync(Application application, Service service,
var volumes = "";
var workingDirectory = docker.WorkingDirectory != null ? $"-w {docker.WorkingDirectory}" : "";
var hostname = "host.docker.internal";
var dockerImage = docker.Image ?? service.Description.Name;

if (docker.DockerFile != null)
{
var dockerBuildResult = await ProcessUtil.RunAsync(
$"docker",
$"build \"{docker.DockerFileContext?.DirectoryName ?? docker.DockerFile.DirectoryName}\" -t {dockerImage} -f \"{docker.DockerFile}\"",
docker.WorkingDirectory,
throwOnError: false);

if (dockerBuildResult.ExitCode != 0)
{
_logger.LogInformation("Running docker command with exception info {ExceptionStdOut} {ExceptionStdErr}", dockerBuildResult.StandardOutput, dockerBuildResult.StandardError);
throw new CommandException("'docker build' failed.");
}
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Expand Down Expand Up @@ -297,7 +320,7 @@ async Task RunDockerContainer(IEnumerable<(int ExternalPort, int Port, int? Cont
}
}

var command = $"run -d {workingDirectory} {volumes} {environmentArguments} {portString} --name {replica} --restart=unless-stopped {docker.Image} {docker.Args ?? ""}";
var command = $"run -d {workingDirectory} {volumes} {environmentArguments} {portString} --name {replica} --restart=unless-stopped {dockerImage} {docker.Args ?? ""}";

_logger.LogInformation("Running image {Image} for {Replica}", docker.Image, replica);

Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.Tye.Hosting/Model/DockerRunInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.IO;

namespace Microsoft.Tye.Hosting.Model
{
Expand All @@ -25,5 +26,9 @@ public DockerRunInfo(string image, string? args)
public string? Args { get; }

public string Image { get; }

public FileInfo? DockerFile { get; set; }

public FileInfo? DockerFileContext { get; set; }
}
}
10 changes: 10 additions & 0 deletions src/tye/ApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Tye.Extensions;
using Microsoft.Tye.Hosting.Model;
Expand Down Expand Up @@ -46,6 +47,15 @@ public static Application ToHostingApplication(this ApplicationBuilder applicati
{
var dockerRunInfo = new DockerRunInfo(container.Image, container.Args);

if (!string.IsNullOrEmpty(container.DockerFile))
{
dockerRunInfo.DockerFile = new FileInfo(container.DockerFile);
if (!string.IsNullOrEmpty(container.DockerFileContext))
{
dockerRunInfo.DockerFileContext = new FileInfo(container.DockerFileContext);
}
}

foreach (var mapping in container.Volumes)
{
dockerRunInfo.VolumeMappings.Add(new DockerVolume(mapping.Source, mapping.Name, mapping.Target));
Expand Down
77 changes: 77 additions & 0 deletions test/E2ETest/TyeRunTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,83 @@ public async Task MultiRepo_WorksWithCloning()
});
}

[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task DockerFileTest()
{
using var projectDirectory = CopyTestProjectDirectory("dockerfile");

var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);

var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};

var client = new HttpClient(new RetryHandler(handler));

await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");

var backendResponse = await client.GetAsync(backendUri);
var frontendResponse = await client.GetAsync(frontendUri);

Assert.True(backendResponse.IsSuccessStatusCode);
Assert.True(frontendResponse.IsSuccessStatusCode);
});
}

[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task DockerFileChangeContextTest()
{
using var projectDirectory = CopyTestProjectDirectory("dockerfile");

File.Move(Path.Combine(projectDirectory.DirectoryPath, "backend", "Dockerfile"), Path.Combine(projectDirectory.DirectoryPath, "Dockerfile"));

var content = @"
name: frontend-backend
services:
- name: backend
dockerFile: Dockerfile
dockerFileContext: backend/
bindings:
- containerPort: 80
protocol: http
- name: frontend
project: frontend/frontend.csproj";

var projectFile = Path.Combine(projectDirectory.DirectoryPath, "tye.yaml");
await File.WriteAllTextAsync(projectFile, content);
var outputContext = new OutputContext(_sink, Verbosity.Debug);
var application = await ApplicationFactory.CreateAsync(outputContext, new FileInfo(projectFile));

var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};

var client = new HttpClient(new RetryHandler(handler));

await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
var frontendUri = await GetServiceUrl(client, uri, "frontend");
var backendUri = await GetServiceUrl(client, uri, "backend");

var backendResponse = await client.GetAsync(backendUri);
var frontendResponse = await client.GetAsync(frontendUri);

Assert.True(backendResponse.IsSuccessStatusCode);
Assert.True(frontendResponse.IsSuccessStatusCode);
});
}

private async Task<string> GetServiceUrl(HttpClient client, Uri uri, string serviceName)
{
var serviceResult = await client.GetStringAsync($"{uri}api/v1/services/{serviceName}");
Expand Down
18 changes: 18 additions & 0 deletions test/E2ETest/testassets/projects/dockerfile/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM node:10

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

RUN npm install
# If you are building your code for production
# RUN npm ci --only=production

# Bundle app source
COPY . .

CMD [ "node", "app.js" ]
10 changes: 10 additions & 0 deletions test/E2ETest/testassets/projects/dockerfile/backend/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
var express = require('express');
var http = require('http');
var app = express();

app.get('/', function (req, res) {
res.send('Hello World!');
});
http.createServer(app).listen(80, function () {
console.log('Example app listening on port 80!');
});
Loading

0 comments on commit af82001

Please sign in to comment.