diff --git a/samples/voting/tye.yaml b/samples/voting/tye.yaml index 6d39dcb0..8734d36c 100644 --- a/samples/voting/tye.yaml +++ b/samples/voting/tye.yaml @@ -8,8 +8,6 @@ services: - port: 6379 - name: worker project: worker/worker.csproj - bindings: - - autoAssignPort: true - name: postgres image: postgres env: diff --git a/src/Microsoft.Tye.Core/ApplicationFactory.cs b/src/Microsoft.Tye.Core/ApplicationFactory.cs index 3ebcf0d2..4b929f5e 100644 --- a/src/Microsoft.Tye.Core/ApplicationFactory.cs +++ b/src/Microsoft.Tye.Core/ApplicationFactory.cs @@ -96,26 +96,48 @@ namespace Microsoft.Tye builder.Services.Add(service); - foreach (var configBinding in configService.Bindings) + // If there are no bindings and we're in ASP.NET Core project then add an HTTP and HTTPS binding + if (configService.Bindings.Count == 0 && + service is ProjectServiceBuilder project2 && + project2.Frameworks.Any(f => f.Name == "Microsoft.AspNetCore.App")) { - var binding = new BindingBuilder() + // HTTP is the default binding + service.Bindings.Add(new BindingBuilder() { - Name = configBinding.Name, - AutoAssignPort = configBinding.AutoAssignPort, - ConnectionString = configBinding.ConnectionString, - Host = configBinding.Host, - ContainerPort = configBinding.ContainerPort, - Port = configBinding.Port, - Protocol = configBinding.Protocol, - }; + AutoAssignPort = true, + Protocol = "http" + }); - // Assume HTTP for projects only (containers may be different) - if (binding.ConnectionString == null && configService.Project != null) + service.Bindings.Add(new BindingBuilder() { - binding.Protocol ??= "http"; - } + Name = "https", + AutoAssignPort = true, + Protocol = "https" + }); + } + else + { + foreach (var configBinding in configService.Bindings) + { + var binding = new BindingBuilder() + { + Name = configBinding.Name, + AutoAssignPort = configBinding.AutoAssignPort, + ConnectionString = configBinding.ConnectionString, + Host = configBinding.Host, + ContainerPort = configBinding.ContainerPort, + Port = configBinding.Port, + Protocol = configBinding.Protocol, + }; - service.Bindings.Add(binding); + // Assume HTTP for projects only (containers may be different) + if (binding.ConnectionString == null && configService.Project != null) + { + binding.Protocol ??= "http"; + } + + service.Bindings.Add(binding); + } } foreach (var configEnvVar in configService.Configuration) diff --git a/src/Microsoft.Tye.Core/ConfigModel/ConfigFactory.cs b/src/Microsoft.Tye.Core/ConfigModel/ConfigFactory.cs index bff20d73..ecf0aaf6 100644 --- a/src/Microsoft.Tye.Core/ConfigModel/ConfigFactory.cs +++ b/src/Microsoft.Tye.Core/ConfigModel/ConfigFactory.cs @@ -2,11 +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; using System.Collections.Generic; using System.IO; -using System.Text.Json; -using Microsoft.Build.Construction; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -42,11 +39,13 @@ namespace Microsoft.Tye.ConfigModel Source = file, }; - var service = CreateService(file); - if (service is object) + var service = new ConfigService() { - application.Services.Add(service); - } + Name = Path.GetFileNameWithoutExtension(file.Name).ToLowerInvariant(), + Project = file.FullName.Replace('\\', '/'), + }; + + application.Services.Add(service); return application; } @@ -62,11 +61,13 @@ namespace Microsoft.Tye.ConfigModel // throughout the code, because we load them dynamically. foreach (var projectFile in ProjectReader.EnumerateProjects(file)) { - var description = CreateService(projectFile); - if (description != null) + var service = new ConfigService() { - application.Services.Add(description); - } + Name = Path.GetFileNameWithoutExtension(projectFile.Name).ToLowerInvariant(), + Project = projectFile.FullName.Replace('\\', '/'), + }; + + application.Services.Add(service); } return application; @@ -82,82 +83,22 @@ namespace Microsoft.Tye.ConfigModel var application = deserializer.Deserialize(reader); application.Source = file; + // Deserialization makes all collection properties null so make sure they are non-null so + // other code doesn't need to react foreach (var service in application.Services) { - if (service.Project == null) - { - continue; - } - - if (!TryGetLaunchProfile(new FileInfo(Path.Combine(file.DirectoryName, service.Project)), out var launchProfile)) - { - continue; - } - - // Bindings can be null here from deserialization. service.Bindings ??= new List(); - - PopulateFromLaunchProfile(service, launchProfile); + service.Configuration ??= new List(); + service.Volumes ??= new List(); } - return application; - } - - private static bool TryGetLaunchProfile(FileInfo file, out JsonElement launchProfile) - { - var launchSettingsPath = Path.Combine(file.DirectoryName, "Properties", "launchSettings.json"); - if (!File.Exists(launchSettingsPath)) + foreach (var ingress in application.Ingress) { - launchProfile = default; - return false; + ingress.Bindings ??= new List(); + ingress.Rules ??= new List(); } - // If there's a launchSettings.json, then use it to get addresses - var root = JsonSerializer.Deserialize(File.ReadAllText(launchSettingsPath)); - var key = NameSanitizer.SanitizeToIdentifier(Path.GetFileNameWithoutExtension(file.Name)); - var profiles = root.GetProperty("profiles"); - return profiles.TryGetProperty(key, out launchProfile); - } - - private static ConfigService? CreateService(FileInfo file) - { - if (!TryGetLaunchProfile(file, out var launchProfile)) - { - return null; - } - - var service = new ConfigService() - { - Name = Path.GetFileNameWithoutExtension(file.Name).ToLowerInvariant(), - Project = file.FullName.Replace('\\', '/'), - }; - - PopulateFromLaunchProfile(service, launchProfile); - - return service; - } - - private static void PopulateFromLaunchProfile(ConfigService service, JsonElement launchProfile) - { - if (service.Bindings.Count == 0 && launchProfile.TryGetProperty("applicationUrl", out var applicationUrls)) - { - var addresses = applicationUrls.GetString().Split(';', StringSplitOptions.RemoveEmptyEntries); - foreach (var address in addresses) - { - var uri = new Uri(address); - service.Bindings.Add(new ConfigServiceBinding() - { - // Don't use ports from launch profiles. These are very likely to be the same defaults (5000, 5001) - // that were generated when the project was created, and so they will almost always conflict - // between multiple apps. - AutoAssignPort = true, - Protocol = uri.Scheme - }); - } - } - - // Don't apply environment variables here. We don't want to carry forward settings from - // development into production. + return application; } } } diff --git a/src/Microsoft.Tye.Hosting/HttpProxyService.cs b/src/Microsoft.Tye.Hosting/HttpProxyService.cs index ea05b847..23287752 100644 --- a/src/Microsoft.Tye.Hosting/HttpProxyService.cs +++ b/src/Microsoft.Tye.Hosting/HttpProxyService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; using System.Threading; @@ -72,7 +73,7 @@ namespace Microsoft.Tye.Hosting var port = service.PortMap[binding.Port.Value][i]; ports.Add(port); - var url = $"{binding.Protocol ?? "http"}://localhost:{port}"; + var url = $"{binding.Protocol}://localhost:{port}"; addresses.Add(url); } @@ -100,23 +101,27 @@ namespace Microsoft.Tye.Hosting var uris = new List(); + // HTTP before HTTPS (this might change once we figure out certs...) + var targetBinding = targetServiceDescription.Bindings.FirstOrDefault(b => b.Protocol == "http") ?? + targetServiceDescription.Bindings.FirstOrDefault(b => b.Protocol == "https"); + + if (targetBinding == null) + { + _logger.LogInformation("Service {ServiceName} does not have any HTTP or HTTPs bindings", targetServiceDescription.Name); + continue; + } + // For each of the target service replicas, get the base URL // based on the replica port for (int i = 0; i < targetServiceDescription.Replicas; i++) { - foreach (var binding in targetServiceDescription.Bindings) - { - if (binding.Port == null) - { - continue; - } - - var port = target.PortMap[binding.Port.Value][i]; - var url = $"{binding.Protocol ?? "http"}://localhost:{port}"; - uris.Add(new Uri(url)); - } + var port = target.PortMap[targetBinding.Port!.Value][i]; + var url = $"{targetBinding.Protocol}://localhost:{port}"; + uris.Add(new Uri(url)); } + _logger.LogInformation("Service {ServiceName} is using {Urls}", targetServiceDescription.Name, string.Join(",", uris.Select(u => u.ToString()))); + // The only load balancing strategy here is round robin long count = 0; RequestDelegate del = context =>