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

Merge ingresses with the same name or binding into 1 ingress service #1461

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Microsoft.Tye.Core/IngressBindingBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
80 changes: 62 additions & 18 deletions src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -51,30 +51,17 @@
}
</td>
<td>
@if (service.Description.Bindings.Any())
@if (GetUrls(service) is IEnumerable<string> 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);
<span class="binding"><a href="@url" target="_blank">@url</a></span>
foreach (var r in b.Routes)
{
var routeUrl = url + r;
<span class="binding"><a href="@routeUrl" target="_blank">@routeUrl</a></span>
}
}
else
{
<span class="binding">@GetUrl(b)</span>
}
<span class="binding"><a href="@url" target="_blank">@url</a></span>
}
else
{
<span class="binding">@b.ConnectionString</span>
<span class="binding">@url</span>
}
}
}
Expand All @@ -101,17 +88,74 @@
@code {

private List<IDisposable> _subscriptions = new List<IDisposable>();
private List<ServiceDescription> _ingressDescriptions = new List<ServiceDescription>();
private const string INGRESS_NAME = "INGRESS";

string GetUrl(ServiceBinding b)
{
return $"{(b.Protocol ?? "tcp")}://{b.Host ?? "localhost"}:{b.Port}";
}

IEnumerable<string> 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<IngressRule>())
{
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);
}
}
}

Expand Down
59 changes: 44 additions & 15 deletions src/tye/ApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IngressRule>();

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<IngressRule>()));
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)
Expand Down
10 changes: 10 additions & 0 deletions test/E2ETest/Microsoft.Tye.E2ETests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@
<Content Include="testassets\**\*" CopyToOutputDirectory="PreserveNewest" />
<Compile Remove="testassets\**\*" />
<None Remove="testassets\generate\apps-with-ingress.1.18.yaml" />
<None Remove="testassets\projects\apps-with-ingress\ApplicationA\tye-bindingconflict.yaml" />
<None Remove="testassets\projects\apps-with-ingress\ApplicationA\tye-nameconflict.yaml" />
<None Remove="testassets\projects\apps-with-ingress\ApplicationA\tye-ruleconflict.yaml" />
<None Remove="testassets\projects\apps-with-ingress\ApplicationB\tye-bindingconflict.yaml" />
<None Remove="testassets\projects\apps-with-ingress\ApplicationB\tye-nameconflict.yaml" />
<None Remove="testassets\projects\apps-with-ingress\ApplicationB\tye-ruleconflict.yaml" />
<None Remove="testassets\projects\apps-with-ingress\ApplicationB\tye.yaml" />
<None Remove="testassets\projects\apps-with-ingress\tye-mergeingress-bindingconflict.yaml" />
<None Remove="testassets\projects\apps-with-ingress\tye-mergeingress-nameconflict.yaml" />
<None Remove="testassets\projects\apps-with-ingress\tye-mergeingress-ruleconflict.yaml" />
<None Remove="testassets\projects\non-standard-dashboard-port\test-project\appsettings.Development.json" />
<None Remove="testassets\projects\non-standard-dashboard-port\test-project\appsettings.json" />
<None Remove="testassets\projects\non-standard-dashboard-port\test-project\Properties\launchSettings.json" />
Expand Down
Loading