diff --git a/samples/frontend-backend/tye.yaml b/samples/frontend-backend/tye.yaml index 74569f0a..8868a09a 100644 --- a/samples/frontend-backend/tye.yaml +++ b/samples/frontend-backend/tye.yaml @@ -4,9 +4,5 @@ name: frontend-backend services: - name: backend project: backend/backend.csproj - bindings: - - port: 5050 - name: frontend project: frontend/frontend.csproj - bindings: - - port: 5051 diff --git a/src/Tye.Core/CombineStep.cs b/src/Tye.Core/CombineStep.cs index c482c3fc..88849358 100644 --- a/src/Tye.Core/CombineStep.cs +++ b/src/Tye.Core/CombineStep.cs @@ -20,6 +20,18 @@ namespace Tye return Task.CompletedTask; } + // Compute ASPNETCORE_URLS based on the bindings exposed by *this* project. + foreach (var binding in service.Service.Bindings) + { + if (binding.Protocol == "http" || binding.Protocol == null) + { + var port = binding.Port ?? 80; + var urls = $"http://*{(port == 80 ? "" : (":" + port.ToString()))}"; + service.Service.Environment.Add("ASPNETCORE_URLS", urls); + break; + } + } + // Process bindings and turn them into environment variables and secrets. There's // some duplication with the code in m8s (Application.cs) for populating environments. // @@ -47,6 +59,13 @@ namespace Tye bindings.Bindings.Add(new EnvironmentVariableInputBinding($"CONNECTIONSTRING__{configName}", binding.ConnectionString)); } + if (binding.Protocol == "https") + { + // We skip https for now in deployment, because the E2E requires certificates + // and we haven't done those features yet. + continue; + } + if (!string.IsNullOrEmpty(binding.Protocol)) { bindings.Bindings.Add(new EnvironmentVariableInputBinding($"SERVICE__{configName}__PROTOCOL", binding.Protocol)); diff --git a/src/Tye.Core/DockerContainerBuilder.cs b/src/Tye.Core/DockerContainerBuilder.cs index c26a23df..d4bf23b1 100644 --- a/src/Tye.Core/DockerContainerBuilder.cs +++ b/src/Tye.Core/DockerContainerBuilder.cs @@ -86,7 +86,7 @@ namespace Tye throw new CommandException("'docker build' failed."); } - output.WriteInfoLine($"Created Docker Image: {container.ImageName}:{container.ImageTag}"); + output.WriteInfoLine($"Created Docker Image: '{container.ImageName}:{container.ImageTag}'"); service.Outputs.Add(new DockerImageOutput(container.ImageName!, container.ImageTag!)); } } diff --git a/src/Tye.Core/KubernetesManifestGenerator.cs b/src/Tye.Core/KubernetesManifestGenerator.cs index 262f1f8c..d73fcc9b 100644 --- a/src/Tye.Core/KubernetesManifestGenerator.cs +++ b/src/Tye.Core/KubernetesManifestGenerator.cs @@ -61,13 +61,23 @@ namespace Tye // We figure out the port based on bindings foreach (var binding in service.Service.Bindings) { - var port = new YamlMappingNode(); - ports.Add(port); + if (binding.Protocol == "https") + { + // We skip https for now in deployment, because the E2E requires certificates + // and we haven't done those features yet. + continue; + } - port.Add("name", binding.Name ?? "web"); - port.Add("protocol", "TCP"); // we use assume TCP. YOLO - port.Add("port", binding.Port?.ToString() ?? "80"); - port.Add("targetPort", binding.Port?.ToString() ?? "80"); + if (binding.Port != null) + { + var port = new YamlMappingNode(); + ports.Add(port); + + port.Add("name", binding.Name ?? binding.Protocol ?? "http"); + port.Add("protocol", "TCP"); // we use assume TCP. YOLO + port.Add("port", binding.Port.Value.ToString()); + port.Add("targetPort", binding.Port.Value.ToString()); + } } return new KubernetesServiceOutput(service.Service.Name, new YamlDocument(root)); @@ -170,19 +180,6 @@ namespace Tye }); } - foreach (var binding in service.Service.Bindings) - { - if (binding.Protocol == "http" || binding.Protocol == null) - { - var port = binding.Port ?? 80; - env.Add(new YamlMappingNode() - { - { "name", "ASPNETCORE_URLS" }, - { "value", $"http://*{(binding.Port == 80 ? "" : (":" + binding.Port.ToString()))}" }, - }); - } - } - if (bindings is object) { foreach (var binding in bindings.Bindings.OfType()) @@ -219,9 +216,19 @@ namespace Tye foreach (var binding in service.Service.Bindings) { - var containerPort = new YamlMappingNode(); - ports.Add(containerPort); - containerPort.Add("containerPort", binding.Port?.ToString() ?? "80"); + if (binding.Protocol == "https") + { + // We skip https for now in deployment, because the E2E requires certificates + // and we haven't done those features yet. + continue; + } + + if (binding.Port != null) + { + var containerPort = new YamlMappingNode(); + ports.Add(containerPort); + containerPort.Add("containerPort", binding.Port.Value.ToString()); + } } } } diff --git a/src/Tye.Hosting/ProxyService.cs b/src/Tye.Hosting/ProxyService.cs index e2d3a61b..a8e29950 100644 --- a/src/Tye.Hosting/ProxyService.cs +++ b/src/Tye.Hosting/ProxyService.cs @@ -86,9 +86,9 @@ namespace Tye.Hosting } _logger.LogInformation( - "Mapping external port {ExternalPort} to internal port(s) {InternalPorts} for {ServiceName} binding {BindingName}", - binding.Port, - string.Join(", ", ports.Select(p => p.ToString())), + "Mapping external port {ExternalPort} to internal port(s) {InternalPorts} for {ServiceName} binding {BindingName}", + binding.Port, + string.Join(", ", ports.Select(p => p.ToString())), service.Description.Name, binding.Name ?? binding.Protocol); diff --git a/src/Tye.Hosting/TyeHost.cs b/src/Tye.Hosting/TyeHost.cs index ca805822..33a75c59 100644 --- a/src/Tye.Hosting/TyeHost.cs +++ b/src/Tye.Hosting/TyeHost.cs @@ -41,6 +41,8 @@ namespace Tye.Hosting _args = args; } + public Tye.Hosting.Model.Application Application => _application; + public WebApplication? DashboardWebApplication { get; set; } // An additional sink that output will be piped to. Useful for testing. diff --git a/src/tye/GenerateHost.cs b/src/tye/GenerateHost.cs index fb6382f7..ce070a0c 100644 --- a/src/tye/GenerateHost.cs +++ b/src/tye/GenerateHost.cs @@ -47,6 +47,8 @@ namespace Tye var outputFilePath = Path.GetFullPath(Path.Combine(application.RootDirectory, $"{applicationName}-generate-{environment}.yaml")); output.WriteInfoLine($"Writing output to '{outputFilePath}'."); { + File.Delete(outputFilePath); + using var stream = File.OpenWrite(outputFilePath); using var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true); diff --git a/src/tye/Program.DeployCommand.cs b/src/tye/Program.DeployCommand.cs index 05a27b4f..54ab7771 100644 --- a/src/tye/Program.DeployCommand.cs +++ b/src/tye/Program.DeployCommand.cs @@ -102,13 +102,29 @@ namespace Tye foreach (var configBinding in configService.Bindings) { - service.Bindings.Add(new ServiceBinding(configBinding.Name ?? service.Name) + var binding = new ServiceBinding(configBinding.Name ?? service.Name) { ConnectionString = configBinding.ConnectionString, Host = configBinding.Host, Port = configBinding.Port, Protocol = configBinding.Protocol, - }); + }; + + binding.Protocol ??= "http"; + + if (binding.Port == null && configBinding.AutoAssignPort) + { + if (binding.Protocol == "http" || binding.Protocol == null) + { + binding.Port = 80; + } + else if (binding.Protocol == "https") + { + binding.Port = 443; + } + } + + service.Bindings.Add(binding); } var serviceEntry = new ServiceEntry(service, configService.Name); @@ -117,7 +133,7 @@ namespace Tye var container = new ContainerInfo() { - UseMultiphaseDockerfile = false, + UseMultiphaseDockerfile = true, }; service.GeneratedAssets.Container = container; services.Add(serviceEntry); diff --git a/test/E2ETest/TyeRunTests.cs b/test/E2ETest/TyeRunTests.cs index 540d9baa..542c4df1 100644 --- a/test/E2ETest/TyeRunTests.cs +++ b/test/E2ETest/TyeRunTests.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; @@ -36,9 +35,7 @@ namespace E2ETest DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath); var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "test-project.csproj")); - - var application = ConfigFactory.FromFile(projectFile); - using var host = new TyeHost(application.ToHostingApplication(), Array.Empty()) + using var host = new TyeHost(ConfigFactory.FromFile(projectFile).ToHostingApplication(), Array.Empty()) { Sink = sink, }; @@ -60,8 +57,8 @@ namespace E2ETest var dashboardResponse = await client.GetStringAsync(dashboardUri); // Only one service for single application. - var service = application.Services.First(); - var binding = service.Bindings.First(); + var service = host.Application.Services.First().Value; + var binding = service.Description.Bindings.First(); var protocol = binding.Protocol?.Length != 0 ? binding.Protocol : "http"; var hostName = binding.Host != null && binding.Host.Length != 0 ? binding.Host : "localhost"; @@ -84,11 +81,11 @@ namespace E2ETest { // If we failed, there's a good chance the service isn't running. Let's get the logs either way and put // them in the output. - var request = new HttpRequestMessage(HttpMethod.Get, new Uri(dashboardUri, $"/api/v1/logs/{service.Name}")); + var request = new HttpRequestMessage(HttpMethod.Get, new Uri(dashboardUri, $"/api/v1/logs/{service.Description.Name}")); var response = await client.SendAsync(request); var text = await response.Content.ReadAsStringAsync(); - output.WriteLine($"Logs for service: {service.Name}"); + output.WriteLine($"Logs for service: {service.Description.Name}"); output.WriteLine(text); } } @@ -106,9 +103,7 @@ namespace E2ETest DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath); var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml")); - - var application = ConfigFactory.FromFile(projectFile); - using var host = new TyeHost(application.ToHostingApplication(), Array.Empty()) + using var host = new TyeHost(ConfigFactory.FromFile(projectFile).ToHostingApplication(), Array.Empty()) { Sink = sink, }; @@ -127,8 +122,8 @@ namespace E2ETest var dashboardUri = new Uri(host.DashboardWebApplication!.Addresses.First()); var dashboardResponse = await client.GetStringAsync(dashboardUri); - await CheckServiceIsUp(application, client, "backend", dashboardUri); - await CheckServiceIsUp(application, client, "frontend", dashboardUri); + await CheckServiceIsUp(host.Application, client, "backend", dashboardUri); + await CheckServiceIsUp(host.Application, client, "frontend", dashboardUri); } finally { @@ -136,11 +131,11 @@ namespace E2ETest } } - private async Task CheckServiceIsUp(ConfigApplication application, HttpClient client, string serviceName, Uri dashboardUri) + private async Task CheckServiceIsUp(Tye.Hosting.Model.Application application, HttpClient client, string serviceName, Uri dashboardUri) { // make sure backend is up before frontend - var service = application.Services.Where(a => a.Name == serviceName).First(); - var binding = service.Bindings.First(); + var service = application.Services.Where(a => a.Value.Description.Name == serviceName).First().Value; + var binding = service.Description.Bindings.First(); var protocol = binding.Protocol != null && binding.Protocol.Length != 0 ? binding.Protocol : "http"; var hostName = binding.Host != null && binding.Host.Length != 0 ? binding.Host : "localhost"; @@ -168,13 +163,13 @@ namespace E2ETest { // If we failed, there's a good chance the service isn't running. Let's get the logs either way and put // them in the output. - foreach (var s in application.Services) + foreach (var s in application.Services.Values) { - var request = new HttpRequestMessage(HttpMethod.Get, new Uri(dashboardUri, $"/api/v1/logs/{s.Name}")); + var request = new HttpRequestMessage(HttpMethod.Get, new Uri(dashboardUri, $"/api/v1/logs/{s.Description.Name}")); var response = await client.SendAsync(request); var text = await response.Content.ReadAsStringAsync(); - output.WriteLine($"Logs for service: {s.Name}"); + output.WriteLine($"Logs for service: {s.Description.Name}"); output.WriteLine(text); } } diff --git a/test/E2ETest/testassets/generate/frontend-backend.yaml b/test/E2ETest/testassets/generate/frontend-backend.yaml index 5cef23ad..5b2ef157 100644 --- a/test/E2ETest/testassets/generate/frontend-backend.yaml +++ b/test/E2ETest/testassets/generate/frontend-backend.yaml @@ -22,13 +22,15 @@ spec: imagePullPolicy: Always env: - name: ASPNETCORE_URLS - value: http://*:5050 + value: 'http://*' + - name: SERVICE__FRONTEND__PROTOCOL + value: 'http' - name: SERVICE__FRONTEND__PORT - value: '5051' + value: '80' - name: SERVICE__FRONTEND__HOST value: 'frontend' ports: - - containerPort: 5050 + - containerPort: 80 ... --- kind: Service @@ -45,8 +47,8 @@ spec: ports: - name: backend protocol: TCP - port: 5050 - targetPort: 5050 + port: 80 + targetPort: 80 ... --- kind: Deployment @@ -73,13 +75,15 @@ spec: imagePullPolicy: Always env: - name: ASPNETCORE_URLS - value: http://*:5051 + value: 'http://*' + - name: SERVICE__BACKEND__PROTOCOL + value: 'http' - name: SERVICE__BACKEND__PORT - value: '5050' + value: '80' - name: SERVICE__BACKEND__HOST value: 'backend' ports: - - containerPort: 5051 + - containerPort: 80 ... --- kind: Service @@ -96,6 +100,6 @@ spec: ports: - name: frontend protocol: TCP - port: 5051 - targetPort: 5051 + port: 80 + targetPort: 80 ... diff --git a/test/E2ETest/testassets/generate/multi-project.yaml b/test/E2ETest/testassets/generate/multi-project.yaml index 224dac61..630dc259 100644 --- a/test/E2ETest/testassets/generate/multi-project.yaml +++ b/test/E2ETest/testassets/generate/multi-project.yaml @@ -22,7 +22,9 @@ spec: imagePullPolicy: Always env: - name: ASPNETCORE_URLS - value: http://*:7000 + value: 'http://*:7000' + - name: SERVICE__FRONTEND__PROTOCOL + value: 'http' - name: SERVICE__FRONTEND__PORT value: '8000' - name: SERVICE__FRONTEND__HOST @@ -84,7 +86,9 @@ spec: imagePullPolicy: Always env: - name: ASPNETCORE_URLS - value: http://*:8000 + value: 'http://*:8000' + - name: SERVICE__BACKEND__PROTOCOL + value: 'http' - name: SERVICE__BACKEND__PORT value: '7000' - name: SERVICE__BACKEND__HOST @@ -145,10 +149,14 @@ spec: image: test/worker:1.0.0 imagePullPolicy: Always env: + - name: SERVICE__BACKEND__PROTOCOL + value: 'http' - name: SERVICE__BACKEND__PORT value: '7000' - name: SERVICE__BACKEND__HOST value: 'backend' + - name: SERVICE__FRONTEND__PROTOCOL + value: 'http' - name: SERVICE__FRONTEND__PORT value: '8000' - name: SERVICE__FRONTEND__HOST diff --git a/test/E2ETest/testassets/generate/single-project-noregistry.yaml b/test/E2ETest/testassets/generate/single-project-noregistry.yaml index 72598a15..f5d5d0ec 100644 --- a/test/E2ETest/testassets/generate/single-project-noregistry.yaml +++ b/test/E2ETest/testassets/generate/single-project-noregistry.yaml @@ -22,10 +22,9 @@ spec: imagePullPolicy: Always env: - name: ASPNETCORE_URLS - value: http://*:5000 + value: 'http://*' ports: - - containerPort: 5001 - - containerPort: 5000 + - containerPort: 80 ... --- kind: Service @@ -42,10 +41,6 @@ spec: ports: - name: test-project protocol: TCP - port: 5001 - targetPort: 5001 - - name: test-project - protocol: TCP - port: 5000 - targetPort: 5000 + port: 80 + targetPort: 80 ... diff --git a/test/E2ETest/testassets/generate/single-project.yaml b/test/E2ETest/testassets/generate/single-project.yaml index bab5a1d2..0717d032 100644 --- a/test/E2ETest/testassets/generate/single-project.yaml +++ b/test/E2ETest/testassets/generate/single-project.yaml @@ -22,10 +22,9 @@ spec: imagePullPolicy: Always env: - name: ASPNETCORE_URLS - value: http://*:5000 + value: 'http://*' ports: - - containerPort: 5001 - - containerPort: 5000 + - containerPort: 80 ... --- kind: Service @@ -42,10 +41,6 @@ spec: ports: - name: test-project protocol: TCP - port: 5001 - targetPort: 5001 - - name: test-project - protocol: TCP - port: 5000 - targetPort: 5000 + port: 80 + targetPort: 80 ...