diff --git a/src/Microsoft.Tye.Core/ApplicationFactory.cs b/src/Microsoft.Tye.Core/ApplicationFactory.cs index be87ef463..cd486c9ec 100644 --- a/src/Microsoft.Tye.Core/ApplicationFactory.cs +++ b/src/Microsoft.Tye.Core/ApplicationFactory.cs @@ -133,7 +133,8 @@ public static async Task CreateAsync(OutputContext output, F Replicas = configService.Replicas ?? 1, DockerFile = Path.Combine(source.DirectoryName, configService.DockerFile), // Supplying an absolute path with trailing slashes fails for DockerFileContext when calling docker build, so trim trailing slash. - DockerFileContext = GetDockerFileContext(source, configService) + DockerFileContext = GetDockerFileContext(source, configService), + BuildArgs = configService.DockerFileArgs }; service = dockerFile; diff --git a/src/Microsoft.Tye.Core/ConfigModel/ConfigService.cs b/src/Microsoft.Tye.Core/ConfigModel/ConfigService.cs index 5f9a84e0a..3d615b68b 100644 --- a/src/Microsoft.Tye.Core/ConfigModel/ConfigService.cs +++ b/src/Microsoft.Tye.Core/ConfigModel/ConfigService.cs @@ -22,6 +22,7 @@ public class ConfigService public bool External { get; set; } public string? Image { get; set; } public string? DockerFile { get; set; } + public Dictionary DockerFileArgs { get; set; } = new Dictionary(); public string? DockerFileContext { get; set; } public string? Project { get; set; } public string? Include { get; set; } diff --git a/src/Microsoft.Tye.Core/DockerFileServiceBuilder.cs b/src/Microsoft.Tye.Core/DockerFileServiceBuilder.cs index 73a796f9a..07f5e2555 100644 --- a/src/Microsoft.Tye.Core/DockerFileServiceBuilder.cs +++ b/src/Microsoft.Tye.Core/DockerFileServiceBuilder.cs @@ -2,6 +2,8 @@ // 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; + namespace Microsoft.Tye { public class DockerFileServiceBuilder : ProjectServiceBuilder @@ -14,6 +16,7 @@ public DockerFileServiceBuilder(string name, string image) public string Image { get; set; } public string? DockerFile { get; set; } + public Dictionary BuildArgs { get; set; } = new Dictionary(); public string? DockerFileContext { get; set; } } diff --git a/src/Microsoft.Tye.Core/Serialization/ConfigServiceParser.cs b/src/Microsoft.Tye.Core/Serialization/ConfigServiceParser.cs index 7c126188f..167d91d89 100644 --- a/src/Microsoft.Tye.Core/Serialization/ConfigServiceParser.cs +++ b/src/Microsoft.Tye.Core/Serialization/ConfigServiceParser.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Linq; using Microsoft.Tye.ConfigModel; using YamlDotNet.RepresentationModel; @@ -46,6 +47,14 @@ private static void HandleServiceNameMapping(YamlMappingNode yamlMappingNode, Co case "dockerFile": service.DockerFile = YamlParser.GetScalarValue(key, child.Value); break; + case "dockerFileArgs": + if (child.Value.NodeType != YamlNodeType.Sequence) + { + throw new TyeYamlException(child.Value.Start, CoreStrings.FormatExpectedYamlSequence(key)); + } + + HandleDockerFileArgs((child.Value as YamlSequenceNode)!, service.DockerFileArgs); + break; case "dockerFileContext": service.DockerFileContext = YamlParser.GetScalarValue(key, child.Value); break; @@ -399,6 +408,14 @@ private static void HandleBuildProperties(YamlSequenceNode yamlSequenceNode, Lis buildProperties.Add(buildProperty); } } + private static void HandleDockerFileArgs(YamlSequenceNode yamlSequenceNode, Dictionary dockerArguments) + { + foreach (var child in yamlSequenceNode.Children) + { + YamlParser.ThrowIfNotYamlMapping(child); + HandleServiceDockerArgsNameMapping((YamlMappingNode)child, dockerArguments); + } + } private static void HandleServiceConfiguration(YamlSequenceNode yamlSequenceNode, List configuration) { @@ -459,5 +476,20 @@ private static void HandleServiceTags(YamlSequenceNode yamlSequenceNode, List dockerArguments) + { + foreach (var child in yamlMappingNode!.Children) + { + var key = YamlParser.GetScalarValue(child.Key); + var value = YamlParser.GetScalarValue(key, child.Value); + + if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) + { + throw new TyeYamlException(child.Key.Start, CoreStrings.FormatUnrecognizedKey(key)); + } + + dockerArguments.Add(key, value); + } + } } } diff --git a/src/Microsoft.Tye.Hosting/DockerRunner.cs b/src/Microsoft.Tye.Hosting/DockerRunner.cs index c02d914ba..b688354d5 100644 --- a/src/Microsoft.Tye.Hosting/DockerRunner.cs +++ b/src/Microsoft.Tye.Hosting/DockerRunner.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Net; using System.Runtime.InteropServices; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -468,9 +469,16 @@ void Log(string data) service.Logs.OnNext(data); } + var arguments = new StringBuilder($"build \"{docker.DockerFileContext?.FullName}\" -t {dockerImage} -f \"{docker.DockerFile}\""); + + foreach (var buildArg in docker.BuildArgs) + { + arguments.Append($" --build-arg {buildArg.Key}={buildArg.Value}"); + } + var dockerBuildResult = await ProcessUtil.RunAsync( $"docker", - $"build \"{docker.DockerFileContext?.FullName}\" -t {dockerImage} -f \"{docker.DockerFile}\"", + arguments.ToString(), outputDataReceived: Log, errorDataReceived: Log, workingDirectory: docker.WorkingDirectory, diff --git a/src/Microsoft.Tye.Hosting/Model/DockerRunInfo.cs b/src/Microsoft.Tye.Hosting/Model/DockerRunInfo.cs index 7ea1d1706..e2b8155b9 100644 --- a/src/Microsoft.Tye.Hosting/Model/DockerRunInfo.cs +++ b/src/Microsoft.Tye.Hosting/Model/DockerRunInfo.cs @@ -26,6 +26,7 @@ public DockerRunInfo(string image, string? args) public List VolumeMappings { get; } = new List(); public string? Args { get; } + public Dictionary BuildArgs { get; set; } = new Dictionary(); public string Image { get; } diff --git a/src/tye/ApplicationBuilderExtensions.cs b/src/tye/ApplicationBuilderExtensions.cs index 80c2065f7..c9a48386d 100644 --- a/src/tye/ApplicationBuilderExtensions.cs +++ b/src/tye/ApplicationBuilderExtensions.cs @@ -51,7 +51,8 @@ public static Application ToHostingApplication(this ApplicationBuilder applicati { var dockerRunInfo = new DockerRunInfo(dockerFile.Image, dockerFile.Args) { - IsAspNet = dockerFile.IsAspNet + IsAspNet = dockerFile.IsAspNet, + BuildArgs = dockerFile.BuildArgs }; if (!string.IsNullOrEmpty(dockerFile.DockerFile)) diff --git a/test/E2ETest/Microsoft.Tye.E2ETests.csproj b/test/E2ETest/Microsoft.Tye.E2ETests.csproj index cf36fe6f9..1caa1af90 100644 --- a/test/E2ETest/Microsoft.Tye.E2ETests.csproj +++ b/test/E2ETest/Microsoft.Tye.E2ETests.csproj @@ -32,7 +32,7 @@ - + \ No newline at end of file diff --git a/test/E2ETest/TyeBuildTests.Dockerfile.cs b/test/E2ETest/TyeBuildTests.Dockerfile.cs index e7a39f383..b31aab23d 100644 --- a/test/E2ETest/TyeBuildTests.Dockerfile.cs +++ b/test/E2ETest/TyeBuildTests.Dockerfile.cs @@ -86,5 +86,129 @@ public async Task TyeBuild_SinglePhase_ExistingDockerfile() await DockerAssert.DeleteDockerImagesAsync(output, imageName); } } + + [ConditionalFact] + [SkipIfDockerNotRunning] + public async Task TyeBuild_SinglePhase_ExistingDockerfileWithBuildArgs() + { + var projectName = "single-phase-dockerfile-args"; + var environment = "production"; + var imageName = "test/web"; + + await DockerAssert.DeleteDockerImagesAsync(output, imageName); + + using var projectDirectory = CopyTestProjectDirectory(projectName); + Assert.True(File.Exists(Path.Combine(projectDirectory.DirectoryPath, "Dockerfile")), "Dockerfile should exist."); + + var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml")); + + var outputContext = new OutputContext(sink, Verbosity.Debug); + var application = await ApplicationFactory.CreateAsync(outputContext, projectFile); + + application.Registry = new ContainerRegistry("test"); + + try + { + await BuildHost.ExecuteBuildAsync(outputContext, application, environment, interactive: false); + + Assert.Single(application.Services.Single().Outputs.OfType()); + var builder = (DockerFileServiceBuilder)application.Services.First(); + var valuePair = builder.BuildArgs.First(); + Assert.Equal("pat", valuePair.Key); + Assert.Equal("thisisapat", valuePair.Value); + + await DockerAssert.AssertImageExistsAsync(output, imageName); + } + finally + { + await DockerAssert.DeleteDockerImagesAsync(output, imageName); + } + } + + [ConditionalFact] + [SkipIfDockerNotRunning] + public async Task TyeBuild_SinglePhase_ExistingDockerfileWithBuildArgsDuplicateArgs() + { + var projectName = "single-phase-dockerfile-args"; + var environment = "production"; + var imageName = "test/web"; + + await DockerAssert.DeleteDockerImagesAsync(output, imageName); + + using var projectDirectory = CopyTestProjectDirectory(projectName); + Assert.True(File.Exists(Path.Combine(projectDirectory.DirectoryPath, "Dockerfile")), "Dockerfile should exist."); + + var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml")); + + var outputContext = new OutputContext(sink, Verbosity.Debug); + var application = await ApplicationFactory.CreateAsync(outputContext, projectFile); + + application.Registry = new ContainerRegistry("test"); + + try + { + await BuildHost.ExecuteBuildAsync(outputContext, application, environment, interactive: false); + + Assert.Single(application.Services.Single().Outputs.OfType()); + var builder = (DockerFileServiceBuilder)application.Services.First(); + var valuePair = builder.BuildArgs.First(); + Assert.Equal("pat", valuePair.Key); + Assert.Equal("thisisapat", valuePair.Value); + + await DockerAssert.AssertImageExistsAsync(output, imageName); + } + finally + { + await DockerAssert.DeleteDockerImagesAsync(output, imageName); + } + } + + [ConditionalFact] + [SkipIfDockerNotRunning] + public async Task TyeBuild_SinglePhase_ExistingDockerfileWithBuildArgsMultiArgs() + { + var projectName = "single-phase-dockerfile-multi-args"; + var environment = "production"; + var imageName = "test/web"; + + await DockerAssert.DeleteDockerImagesAsync(output, imageName); + + using var projectDirectory = CopyTestProjectDirectory(projectName); + Assert.True(File.Exists(Path.Combine(projectDirectory.DirectoryPath, "Dockerfile")), "Dockerfile should exist."); + + var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml")); + + var outputContext = new OutputContext(sink, Verbosity.Debug); + var application = await ApplicationFactory.CreateAsync(outputContext, projectFile); + + application.Registry = new ContainerRegistry("test"); + + try + { + await BuildHost.ExecuteBuildAsync(outputContext, application, environment, interactive: false); + + Assert.Single(application.Services.Single().Outputs.OfType()); + var builder = (DockerFileServiceBuilder)application.Services.First(); + var valuePair = builder.BuildArgs.ElementAt(0); + + Assert.Equal(3, builder.BuildArgs.Count); + Assert.Equal("pat", valuePair.Key); + Assert.Equal("thisisapat", valuePair.Value); + + valuePair = builder.BuildArgs.ElementAt(1); + Assert.Equal("number_of_replicas", valuePair.Key); + Assert.Equal("2", valuePair.Value); + + valuePair = builder.BuildArgs.ElementAt(2); + Assert.Equal("number_of_shards", valuePair.Key); + Assert.Equal("5", valuePair.Value); + + await DockerAssert.AssertImageExistsAsync(output, imageName); + } + finally + { + await DockerAssert.DeleteDockerImagesAsync(output, imageName); + } + } } } diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/Dockerfile b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/Dockerfile new file mode 100644 index 000000000..74281d45f --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/Dockerfile @@ -0,0 +1,4 @@ +FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 +WORKDIR /app +COPY . /app +ENTRYPOINT ["dotnet", "single-phase-dockerfile-args.dll"] \ No newline at end of file diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/Program.cs b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/Program.cs new file mode 100644 index 000000000..365723c47 --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace single_phase_dockerfile +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/Properties/launchSettings.json b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/Properties/launchSettings.json new file mode 100644 index 000000000..38b22161f --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:15313", + "sslPort": 44306 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "single_phase_dockerfile": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/Startup.cs b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/Startup.cs new file mode 100644 index 000000000..50563158c --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/Startup.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace single_phase_dockerfile +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapGet("/", context => + { + return context.Response.WriteAsync("Hello World!"); + }); + }); + } + } +} diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/appsettings.json b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/appsettings.json new file mode 100644 index 000000000..d9d9a9bff --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/single-phase-dockerfile-args.csproj b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/single-phase-dockerfile-args.csproj new file mode 100644 index 000000000..ffa96582b --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/single-phase-dockerfile-args.csproj @@ -0,0 +1,8 @@ + + + + netcoreapp3.1 + single_phase_dockerfile_args + + + diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/tye.yaml b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/tye.yaml new file mode 100644 index 000000000..e74d41433 --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args-duplicates/tye.yaml @@ -0,0 +1,13 @@ +# tye application configuration file +# read all about it at https://github.com/dotnet/tye +# +# when you've given us a try, we'd love to know what you think: +# https://aka.ms/AA7q20u +# +name: single-phase-dockerfile-args +services: +- name: web + dockerFile: Dockerfile + dockerFileArgs: + - pat: thisisapat + - pat: thisisapat diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args/Dockerfile b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/Dockerfile new file mode 100644 index 000000000..74281d45f --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/Dockerfile @@ -0,0 +1,4 @@ +FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 +WORKDIR /app +COPY . /app +ENTRYPOINT ["dotnet", "single-phase-dockerfile-args.dll"] \ No newline at end of file diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args/Program.cs b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/Program.cs new file mode 100644 index 000000000..365723c47 --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace single_phase_dockerfile +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args/Properties/launchSettings.json b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/Properties/launchSettings.json new file mode 100644 index 000000000..38b22161f --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:15313", + "sslPort": 44306 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "single_phase_dockerfile": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args/Startup.cs b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/Startup.cs new file mode 100644 index 000000000..50563158c --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/Startup.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace single_phase_dockerfile +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapGet("/", context => + { + return context.Response.WriteAsync("Hello World!"); + }); + }); + } + } +} diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args/appsettings.json b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/appsettings.json new file mode 100644 index 000000000..d9d9a9bff --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args/single-phase-dockerfile-args.csproj b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/single-phase-dockerfile-args.csproj new file mode 100644 index 000000000..ffa96582b --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/single-phase-dockerfile-args.csproj @@ -0,0 +1,8 @@ + + + + netcoreapp3.1 + single_phase_dockerfile_args + + + diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-args/tye.yaml b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/tye.yaml new file mode 100644 index 000000000..058ad72af --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-args/tye.yaml @@ -0,0 +1,12 @@ +# tye application configuration file +# read all about it at https://github.com/dotnet/tye +# +# when you've given us a try, we'd love to know what you think: +# https://aka.ms/AA7q20u +# +name: single-phase-dockerfile-args +services: +- name: web + dockerFile: Dockerfile + dockerFileArgs: + - pat: thisisapat diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/Dockerfile b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/Dockerfile new file mode 100644 index 000000000..74281d45f --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/Dockerfile @@ -0,0 +1,4 @@ +FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 +WORKDIR /app +COPY . /app +ENTRYPOINT ["dotnet", "single-phase-dockerfile-args.dll"] \ No newline at end of file diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/Program.cs b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/Program.cs new file mode 100644 index 000000000..365723c47 --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace single_phase_dockerfile +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/Properties/launchSettings.json b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/Properties/launchSettings.json new file mode 100644 index 000000000..38b22161f --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:15313", + "sslPort": 44306 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "single_phase_dockerfile": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/Startup.cs b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/Startup.cs new file mode 100644 index 000000000..50563158c --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/Startup.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace single_phase_dockerfile +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapGet("/", context => + { + return context.Response.WriteAsync("Hello World!"); + }); + }); + } + } +} diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/appsettings.json b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/appsettings.json new file mode 100644 index 000000000..d9d9a9bff --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/single-phase-dockerfile-multi-args.csproj b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/single-phase-dockerfile-multi-args.csproj new file mode 100644 index 000000000..ffa96582b --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/single-phase-dockerfile-multi-args.csproj @@ -0,0 +1,8 @@ + + + + netcoreapp3.1 + single_phase_dockerfile_args + + + diff --git a/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/tye.yaml b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/tye.yaml new file mode 100644 index 000000000..d961eb887 --- /dev/null +++ b/test/E2ETest/testassets/projects/single-phase-dockerfile-multi-args/tye.yaml @@ -0,0 +1,14 @@ +# tye application configuration file +# read all about it at https://github.com/dotnet/tye +# +# when you've given us a try, we'd love to know what you think: +# https://aka.ms/AA7q20u +# +name: single-phase-dockerfile-args +services: +- name: web + dockerFile: Dockerfile + dockerFileArgs: + - pat: thisisapat + number_of_replicas: 2 + number_of_shards: 5 diff --git a/test/UnitTests/TyeDeserializationValidationTests.cs b/test/UnitTests/TyeDeserializationValidationTests.cs index 2c8137776..b3ecb2aeb 100644 --- a/test/UnitTests/TyeDeserializationValidationTests.cs +++ b/test/UnitTests/TyeDeserializationValidationTests.cs @@ -273,5 +273,20 @@ public void LivenessProbeSuccessThresholdMustBeOne() var exception = Assert.Throws(() => app.Validate()); Assert.Contains(errorMessage, exception.Message); } + + [Fact] + public void DockerFileWithArgs() + { + var input = @" +services: + - name: web + dockerFile: Dockerfile + dockerFileArgs: + - pat: thisisapat"; + + using var parser = new YamlParser(input); + var app = parser.ParseConfigApplication(); + app.Validate(); + } } }