diff --git a/src/Microsoft.Tye.Core/IngressBindingBuilder.cs b/src/Microsoft.Tye.Core/IngressBindingBuilder.cs index 6153b3423..eccc6742a 100644 --- a/src/Microsoft.Tye.Core/IngressBindingBuilder.cs +++ b/src/Microsoft.Tye.Core/IngressBindingBuilder.cs @@ -10,5 +10,13 @@ public sealed class IngressBindingBuilder public int? Port { get; set; } public string? Protocol { get; set; } // HTTP or HTTPS public string? IPAddress { get; set; } + + public override string ToString() + { + return (string.IsNullOrEmpty(Name) ? "" : "[" + Name + "] -> ") + + (string.IsNullOrEmpty(Protocol) ? "" : Protocol + "://") + + (string.IsNullOrEmpty(IPAddress) ? "*" : IPAddress) + + (Port == null || Port == 0 ? "" : ":" + Port); + } } } diff --git a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor index cf06ba18b..7956075d0 100644 --- a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor +++ b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor @@ -51,30 +51,17 @@ } - @if (service.Description.Bindings.Any()) + @if (GetUrls(service) is IEnumerable urls && urls.Any()) { - foreach (var b in service.Description.Bindings) + foreach (var url in urls) { - if (b.Port != null) + if (url.StartsWith("http://") || url.StartsWith("https://")) { - if (b.Protocol == "http" || b.Protocol == "https") - { - var url = GetUrl(b); - @url - foreach (var r in b.Routes) - { - var routeUrl = url + r; - @routeUrl - } - } - else - { - @GetUrl(b) - } + @url } else { - @b.ConnectionString + @url } } } @@ -101,17 +88,74 @@ @code { private List _subscriptions = new List(); + private List _ingressDescriptions = new List(); + private const string INGRESS_NAME = "INGRESS"; string GetUrl(ServiceBinding b) { return $"{(b.Protocol ?? "tcp")}://{b.Host ?? "localhost"}:{b.Port}"; } + IEnumerable GetUrls(Service service) + { + foreach (var binding in service.Description.Bindings) + { + if (binding.Port != null) + { + var url = GetUrl(binding); + yield return url; + foreach (var r in binding.Routes) + { + yield return url + r; + } + } + else if (!string.IsNullOrEmpty(binding.ConnectionString)) + { + yield return binding.ConnectionString; + } + } + + foreach (ServiceDescription description in _ingressDescriptions) + { + foreach (IngressRule rule in (description?.RunInfo as IngressRunInfo)?.Rules ?? Enumerable.Empty()) + { + if (rule.Service == service.Description.Name) + { + ServiceBinding? mainBinding = description?.Bindings.FirstOrDefault(b => b.Protocol == "http") ?? + description?.Bindings.FirstOrDefault(b => b.Protocol == "https"); + + string? host = rule.Host; + if (string.IsNullOrEmpty(host)) + { + host = mainBinding?.Host ?? mainBinding?.IPAddress ?? "localhost"; + } + int port = mainBinding?.Port ?? 80; + + string url = GetUrl(new ServiceBinding + { + Port = port, + Host = host, + Protocol = mainBinding?.Protocol ?? "http" + }); + if (!string.IsNullOrEmpty(rule.Path)) + { + url += rule.Path; + } + yield return url; + } + } + } + } + protected override void OnInitialized() { foreach (var a in application.Services.Values) { _subscriptions.Add(a.ReplicaEvents.Subscribe(OnReplicaChanged)); + if (a.Description.RunInfo is IngressRunInfo) + { + _ingressDescriptions.Add(a.Description); + } } } diff --git a/src/tye/ApplicationBuilderExtensions.cs b/src/tye/ApplicationBuilderExtensions.cs index 6f1ff5c45..55e81cf77 100644 --- a/src/tye/ApplicationBuilderExtensions.cs +++ b/src/tye/ApplicationBuilderExtensions.cs @@ -193,32 +193,61 @@ public static Application ToHostingApplication(this ApplicationBuilder applicati // Ingress get turned into services for hosting foreach (var ingress in application.Ingress) { - var rules = new List(); - - foreach (var rule in ingress.Rules) + services.TryGetValue(ingress.Name, out Service? service); + var description = service?.Description ?? new ServiceDescription(ingress.Name, new IngressRunInfo(new List())); + if (!(description.RunInfo is IngressRunInfo runinfo)) { - rules.Add(new IngressRule(rule.Host, rule.Path, rule.Service!, rule.PreservePath)); + throw new CommandException($"Service '{ingress.Name}' was already added but not as an ingress service."); } - var runInfo = new IngressRunInfo(rules); + description.Replicas = Math.Max(ingress.Replicas, description.Replicas); - var description = new ServiceDescription(ingress.Name, runInfo) + foreach (var rule in ingress.Rules) { - Replicas = ingress.Replicas, - }; + if (runinfo.Rules.FirstOrDefault(r => (r.Host ?? String.Empty).Equals(rule.Host ?? String.Empty, StringComparison.InvariantCultureIgnoreCase) + && (r.Path ?? String.Empty).Equals(rule.Path ?? String.Empty, StringComparison.InvariantCultureIgnoreCase)) + is IngressRule existing) + { + throw new CommandException($"Cannot add rule for service {rule.Service} to ingress '{ingress.Name}', it already has a rule for service {existing.Service} with Host {rule.Host} and Path {rule.Path}."); + } + runinfo.Rules.Add(new IngressRule(rule.Host, rule.Path, rule.Service!, rule.PreservePath)); + } foreach (var binding in ingress.Bindings) { - description.Bindings.Add(new ServiceBinding() + var existing = description.Bindings.FirstOrDefault(b => !string.IsNullOrEmpty(b.Name) && b.Name == binding.Name); + if (existing != null) { - Name = binding.Name, - Port = binding.Port, - Protocol = binding.Protocol, - IPAddress = binding.IPAddress, - }); + //if we're using an existing binding based on name, the other properties should match + if (existing.Port != binding.Port || existing.Protocol != binding.Protocol || existing.IPAddress != binding.IPAddress) + { + throw new CommandException($"Ingress {ingress.Name} already has a binding with name {binding.Name} but with different settings."); + } + } + else + { + existing = description.Bindings.FirstOrDefault(b => b.Port == binding.Port && (b.Protocol ?? "http") == binding.Protocol && b.IPAddress == binding.IPAddress); + if (existing != null && !(existing.Name ?? string.Empty).Equals(binding.Name ?? string.Empty, StringComparison.InvariantCultureIgnoreCase)) + { + throw new CommandException($"Ingress {ingress.Name} already has a binding with the same ipaddress and/or port, {binding} cannot be added."); + } + } + if (existing == null) + { + description.Bindings.Add(new ServiceBinding() + { + Name = binding.Name, + Port = binding.Port, + Protocol = binding.Protocol, + IPAddress = binding.IPAddress, + }); + } } - services.Add(ingress.Name, new Service(description, ServiceSource.Host)); + if (service == null) + { + services.Add(ingress.Name, new Service(description, ServiceSource.Host)); + } } return new Application(application.Name, application.Source, application.DashboardPort, services, application.ContainerEngine) diff --git a/test/E2ETest/Microsoft.Tye.E2ETests.csproj b/test/E2ETest/Microsoft.Tye.E2ETests.csproj index 552d54f0b..341e4e139 100644 --- a/test/E2ETest/Microsoft.Tye.E2ETests.csproj +++ b/test/E2ETest/Microsoft.Tye.E2ETests.csproj @@ -32,6 +32,16 @@ + + + + + + + + + + diff --git a/test/E2ETest/TyeRunTests.cs b/test/E2ETest/TyeRunTests.cs index 09c09434e..e6568b7b2 100644 --- a/test/E2ETest/TyeRunTests.cs +++ b/test/E2ETest/TyeRunTests.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.CommandLine.IO; +using System.Data; using System.IO; using System.Linq; using System.Net; @@ -19,7 +20,6 @@ using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Primitives; using Microsoft.Tye; using Microsoft.Tye.Hosting; using Microsoft.Tye.Hosting.Model; @@ -1345,6 +1345,168 @@ public async Task RunWithDotnetEnvVarsDoesNotGetOverriddenByDefaultDotnetEnvVars }); } + [Fact] + public async Task MergedIngressRunTest() + { + using var projectDirectory = CopyTestProjectDirectory("apps-with-ingress"); + + var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-mergeingress.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)); + var wsClient = new ClientWebSocket(); + + await RunHostingApplication(application, new HostOptions(), async (app, uri) => + { + var ingressUri = await GetServiceUrl(client, uri, "ingress"); + var appAUri = await GetServiceUrl(client, uri, "appa"); + var appBUri = await GetServiceUrl(client, uri, "appb"); + + var appAResponse = await client.GetAsync(appAUri); + var appBResponse = await client.GetAsync(appBUri); + + Assert.True(appAResponse.IsSuccessStatusCode); + Assert.True(appBResponse.IsSuccessStatusCode); + + var responseA = await client.GetAsync(ingressUri + "/A"); + var responseB = await client.GetAsync(ingressUri + "/B"); + + Assert.StartsWith("Hello from Application A", await responseA.Content.ReadAsStringAsync()); + Assert.StartsWith("Hello from Application B", await responseB.Content.ReadAsStringAsync()); + + var requestA = new HttpRequestMessage(HttpMethod.Get, ingressUri); + requestA.Headers.Host = "a.example.com"; + var requestB = new HttpRequestMessage(HttpMethod.Get, ingressUri); + requestB.Headers.Host = "b.example.com"; + + responseA = await client.SendAsync(requestA); + responseB = await client.SendAsync(requestB); + + Assert.StartsWith("Hello from Application A", await responseA.Content.ReadAsStringAsync()); + Assert.StartsWith("Hello from Application B", await responseB.Content.ReadAsStringAsync()); + + // checking preservePath behavior + var responsePreservePath = await client.GetAsync(ingressUri + "/C/test"); + Assert.Contains("Hit path /C/test", await responsePreservePath.Content.ReadAsStringAsync()); + + string GetWebSocketUri(string uri) + { + if (uri.StartsWith("http")) + { + return "ws" + uri.Substring(4); + } + else if (uri.StartsWith("https")) + { + return "wss" + uri.Substring(5); + } + + throw new NotSupportedException(); + } + + // Check the websocket endpoint + var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + var wsUri = GetWebSocketUri(ingressUri); + await wsClient.ConnectAsync(new Uri(wsUri + "/A/ws"), cts.Token); + var data = Encoding.UTF8.GetBytes("Hello World"); + await wsClient.SendAsync(data, WebSocketMessageType.Text, endOfMessage: true, cts.Token); + var receiveBuffer = new byte[4096]; + var result = await wsClient.ReceiveAsync(receiveBuffer.AsMemory(), cts.Token); + Assert.True(result.EndOfMessage); + Assert.Equal(WebSocketMessageType.Text, result.MessageType); + Assert.Equal(data.Length, result.Count); + Assert.Equal(data, receiveBuffer.AsMemory(0, result.Count).ToArray()); + await wsClient.CloseAsync(WebSocketCloseStatus.NormalClosure, "", cts.Token); + }); + } + + [Fact] + public async Task MergedIngressQueryAndContentProxyingTest() + { + using var projectDirectory = CopyTestProjectDirectory("apps-with-ingress"); + + var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-mergeingress.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 ingressUri = await GetServiceUrl(client, uri, "ingress"); + + var request = new HttpRequestMessage(HttpMethod.Post, ingressUri + "/A/data?key1=value1&key2=value2"); + request.Content = new StringContent("some content"); + + var response = await client.SendAsync(request); + var responseContent = + JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync()); + + Assert.Equal("some content", responseContent!["content"]); + Assert.Equal("?key1=value1&key2=value2", responseContent["query"]); + }); + } + + [Fact] + public async Task IngressMergeConflictOnNameProducesCorrectErrorMessage() + { + using var projectDirectory = TestHelpers.CopyTestProjectDirectory("apps-with-ingress"); + var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-mergeingress-nameconflict.yaml")); + var outputContext = new OutputContext(_sink, Verbosity.Debug); + + var application = await ApplicationFactory.CreateAsync(outputContext, projectFile); + var exception = Assert.Throws(() => application.ToHostingApplication()); + + var ingressName = "ingress"; + var bindingName = "example"; + Assert.Equal($"Ingress {ingressName} already has a binding with name {bindingName} but with different settings.", exception.Message); + } + + [Fact] + public async Task IngressMergeConflictOnRuleProducesCorrectErrorMessage() + { + using var projectDirectory = TestHelpers.CopyTestProjectDirectory("apps-with-ingress"); + var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-mergeingress-ruleconflict.yaml")); + var outputContext = new OutputContext(_sink, Verbosity.Debug); + + var application = await ApplicationFactory.CreateAsync(outputContext, projectFile); + var exception = Assert.Throws(() => application.ToHostingApplication()); + + var ingressName = "ingress"; + var ruleService = "appB".ToLowerInvariant(); + var existingService = "appA".ToLowerInvariant(); + var ruleHost = ""; + var rulePath = "/A"; + Assert.Equal($"Cannot add rule for service {ruleService} to ingress '{ingressName}', it already has a rule for service {existingService} with Host {ruleHost} and Path {rulePath}.", exception.Message); + } + + [Fact] + public async Task IngressMergeConflictOnBindingProducesCorrectErrorMessage() + { + using var projectDirectory = TestHelpers.CopyTestProjectDirectory("apps-with-ingress"); + var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-mergeingress-bindingconflict.yaml")); + var outputContext = new OutputContext(_sink, Verbosity.Debug); + + var application = await ApplicationFactory.CreateAsync(outputContext, projectFile); + var exception = Assert.Throws(() => application.ToHostingApplication()); + + var ingressName = "ingress"; + var binding = new IngressBindingBuilder { Port = 8080, Name = "example2", Protocol = "http" }; + Assert.Equal($"Ingress {ingressName} already has a binding with the same ipaddress and/or port, {binding} cannot be added.", exception.Message); + } + private async Task GetServiceUrl(HttpClient client, Uri uri, string serviceName) { var serviceResult = await client.GetStringAsync($"{uri}api/v1/services/{serviceName}"); diff --git a/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationA/tye-bindingconflict.yaml b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationA/tye-bindingconflict.yaml new file mode 100644 index 000000000..752d6bb97 --- /dev/null +++ b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationA/tye-bindingconflict.yaml @@ -0,0 +1,25 @@ +# 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: apps-with-ingress +ingress: + - name: ingress + bindings: + - port: 8080 + name: example + rules: + - path: /A + service: appA + - path: /C + service: appA + preservePath: true + - host: a.example.com + service: appA + +services: +- name: appA + project: ./ApplicationA.csproj + replicas: 2 diff --git a/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationA/tye-nameconflict.yaml b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationA/tye-nameconflict.yaml new file mode 100644 index 000000000..3b6f11494 --- /dev/null +++ b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationA/tye-nameconflict.yaml @@ -0,0 +1,26 @@ +# 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: apps-with-ingress +ingress: + - name: ingress + bindings: + - port: 8080 + name: example + ip: 192.168.1.1 + rules: + - path: /A + service: appA + - path: /C + service: appA + preservePath: true + - host: a.example.com + service: appA + +services: +- name: appA + project: ./ApplicationA.csproj + replicas: 2 diff --git a/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationA/tye-ruleconflict.yaml b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationA/tye-ruleconflict.yaml new file mode 100644 index 000000000..752d6bb97 --- /dev/null +++ b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationA/tye-ruleconflict.yaml @@ -0,0 +1,25 @@ +# 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: apps-with-ingress +ingress: + - name: ingress + bindings: + - port: 8080 + name: example + rules: + - path: /A + service: appA + - path: /C + service: appA + preservePath: true + - host: a.example.com + service: appA + +services: +- name: appA + project: ./ApplicationA.csproj + replicas: 2 diff --git a/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationA/tye.yaml b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationA/tye.yaml new file mode 100644 index 000000000..bdab44409 --- /dev/null +++ b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationA/tye.yaml @@ -0,0 +1,24 @@ +# 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: apps-with-ingress +ingress: + - name: ingress + bindings: + - port: 8080 + rules: + - path: /A + service: appA + - path: /C + service: appA + preservePath: true + - host: a.example.com + service: appA + +services: +- name: appA + project: ./ApplicationA.csproj + replicas: 2 diff --git a/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationB/tye-bindingconflict.yaml b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationB/tye-bindingconflict.yaml new file mode 100644 index 000000000..2840aba2e --- /dev/null +++ b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationB/tye-bindingconflict.yaml @@ -0,0 +1,22 @@ +# 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: apps-with-ingress +ingress: + - name: ingress + bindings: + - port: 8080 + name: example2 + rules: + - path: /B + service: appB + - host: b.example.com + service: appB + +services: +- name: appB + project: ./ApplicationB.csproj + replicas: 2 \ No newline at end of file diff --git a/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationB/tye-nameconflict.yaml b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationB/tye-nameconflict.yaml new file mode 100644 index 000000000..95ed72651 --- /dev/null +++ b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationB/tye-nameconflict.yaml @@ -0,0 +1,22 @@ +# 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: apps-with-ingress +ingress: + - name: ingress + bindings: + - port: 8080 + name: example + rules: + - path: /B + service: appB + - host: b.example.com + service: appB + +services: +- name: appB + project: ./ApplicationB.csproj + replicas: 2 \ No newline at end of file diff --git a/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationB/tye-ruleconflict.yaml b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationB/tye-ruleconflict.yaml new file mode 100644 index 000000000..3eda92b32 --- /dev/null +++ b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationB/tye-ruleconflict.yaml @@ -0,0 +1,22 @@ +# 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: apps-with-ingress +ingress: + - name: ingress + bindings: + - port: 8080 + name: example + rules: + - path: /A + service: appB + - host: b.example.com + service: appB + +services: +- name: appB + project: ./ApplicationB.csproj + replicas: 2 \ No newline at end of file diff --git a/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationB/tye.yaml b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationB/tye.yaml new file mode 100644 index 000000000..3dbc88827 --- /dev/null +++ b/test/E2ETest/testassets/projects/apps-with-ingress/ApplicationB/tye.yaml @@ -0,0 +1,21 @@ +# 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: apps-with-ingress +ingress: + - name: ingress + bindings: + - port: 8080 + rules: + - path: /B + service: appB + - host: b.example.com + service: appB + +services: +- name: appB + project: ./ApplicationB.csproj + replicas: 2 \ No newline at end of file diff --git a/test/E2ETest/testassets/projects/apps-with-ingress/tye-mergeingress-bindingconflict.yaml b/test/E2ETest/testassets/projects/apps-with-ingress/tye-mergeingress-bindingconflict.yaml new file mode 100644 index 000000000..b4bee743d --- /dev/null +++ b/test/E2ETest/testassets/projects/apps-with-ingress/tye-mergeingress-bindingconflict.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: apps-with-ingress + +services: +- name: appA + include: ApplicationA/tye-bindingconflict.yaml +- name: appB + include: ApplicationB/tye-bindingconflict.yaml diff --git a/test/E2ETest/testassets/projects/apps-with-ingress/tye-mergeingress-nameconflict.yaml b/test/E2ETest/testassets/projects/apps-with-ingress/tye-mergeingress-nameconflict.yaml new file mode 100644 index 000000000..b657eee2b --- /dev/null +++ b/test/E2ETest/testassets/projects/apps-with-ingress/tye-mergeingress-nameconflict.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: apps-with-ingress + +services: +- name: appA + include: ApplicationA/tye-nameconflict.yaml +- name: appB + include: ApplicationB/tye-nameconflict.yaml diff --git a/test/E2ETest/testassets/projects/apps-with-ingress/tye-mergeingress-ruleconflict.yaml b/test/E2ETest/testassets/projects/apps-with-ingress/tye-mergeingress-ruleconflict.yaml new file mode 100644 index 000000000..52434a78d --- /dev/null +++ b/test/E2ETest/testassets/projects/apps-with-ingress/tye-mergeingress-ruleconflict.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: apps-with-ingress + +services: +- name: appA + include: ApplicationA/tye-ruleconflict.yaml +- name: appB + include: ApplicationB/tye-ruleconflict.yaml diff --git a/test/E2ETest/testassets/projects/apps-with-ingress/tye-mergeingress.yaml b/test/E2ETest/testassets/projects/apps-with-ingress/tye-mergeingress.yaml new file mode 100644 index 000000000..efcc2a110 --- /dev/null +++ b/test/E2ETest/testassets/projects/apps-with-ingress/tye-mergeingress.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: apps-with-ingress + +services: +- name: appA + include: ApplicationA/tye.yaml +- name: appB + include: ApplicationB/tye.yaml