* Specify ingress IP in Debug

* Quotes yaml values

* Stop auto-defaulting specific looback addresses

* Update tye YAML schema

* Format whitespace
This commit is contained in:
Jean Llorca 2021-10-08 21:23:57 +02:00 коммит произвёл GitHub
Родитель bbf5475806
Коммит 7ea78aaddd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 151 добавлений и 2 удалений

Просмотреть файл

@ -526,6 +526,11 @@ The port of the binding.
The protocol (`http` or `https`).
#### `ip` (`string`)
The optional IP adress to bind to. Can be '*' for all addresses.
Default is localhost.
## IngressRule
`IngressRule` elements appear in an array within the `rules` property of the `Ingress` element. Rules configure the routing behavior of the ingress proxy.

Просмотреть файл

@ -461,6 +461,7 @@ namespace Microsoft.Tye
Name = configBinding.Name,
Port = configBinding.Port,
Protocol = configBinding.Protocol ?? "http",
IPAddress = configBinding.IPAddress,
};
ingress.Bindings.Add(binding);
}

Просмотреть файл

@ -9,5 +9,6 @@ namespace Microsoft.Tye.ConfigModel
public string? Name { get; set; }
public int? Port { get; set; }
public string? Protocol { get; set; } // HTTP or HTTPS
public string? IPAddress { get; set; } // Can be * or any address to listen on
}
}

Просмотреть файл

@ -153,6 +153,9 @@
<data name="MustBeAnInteger" xml:space="preserve">
<value>"{value}" value must be an integer.</value>
</data>
<data name="MustBeAnIPAddress" xml:space="preserve">
<value>"{value}" value must be an IP address, "*" or "localhost".</value>
</data>
<data name="MustBePositive" xml:space="preserve">
<value>"{value}" value cannot be negative.</value>
</data>

Просмотреть файл

@ -9,5 +9,6 @@ namespace Microsoft.Tye
public string? Name { get; set; }
public int? Port { get; set; }
public string? Protocol { get; set; } // HTTP or HTTPS
public string? IPAddress { get; set; }
}
}

Просмотреть файл

@ -2,7 +2,9 @@
// 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.Net;
using Microsoft.Tye.ConfigModel;
using YamlDotNet.RepresentationModel;
@ -144,6 +146,17 @@ namespace Tye.Serialization
binding.Port = port;
break;
case "ip":
if (YamlParser.GetScalarValue(key, child.Value) is string ipString
&& (IPAddress.TryParse(ipString, out var ip) || ipString == "*" || ipString.Equals("localhost", StringComparison.OrdinalIgnoreCase)))
{
binding.IPAddress = ipString;
}
else
{
throw new TyeYamlException(child.Value.Start, CoreStrings.FormatMustBeAnIPAddress(key));
}
break;
case "protocol":
binding.Protocol = YamlParser.GetScalarValue(key, child.Value);
break;

Просмотреть файл

@ -76,7 +76,8 @@ namespace Microsoft.Tye.Hosting
var port = binding.ReplicaPorts[i];
ports.Add(port);
var url = $"{binding.Protocol}://localhost:{port}";
var url = $"{binding.Protocol}://{binding.IPAddress ?? "localhost"}:{port}";
urls.Add(url);
}

Просмотреть файл

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Net;
namespace Microsoft.Tye.Hosting.Model
{
@ -13,6 +14,7 @@ namespace Microsoft.Tye.Hosting.Model
public int? Port { get; set; }
public int? ContainerPort { get; set; }
public string? Host { get; set; }
public string? IPAddress { get; set; }
public string? Protocol { get; set; }
public List<int> ReplicaPorts { get; } = new List<int>();
}

Просмотреть файл

@ -463,6 +463,10 @@
"protocol": {
"description": "The protocol used by the binding",
"type": "string"
},
"ip": {
"description": "The ip address the ingress listens on.",
"type": "string"
}
}
},

Просмотреть файл

@ -212,6 +212,7 @@ namespace Microsoft.Tye
Name = binding.Name,
Port = binding.Port,
Protocol = binding.Protocol,
IPAddress = binding.IPAddress,
});
}

Просмотреть файл

@ -45,4 +45,4 @@
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.6.1" />
</ItemGroup>
</Project>
</Project>

Просмотреть файл

@ -9,6 +9,8 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Serialization;
@ -767,6 +769,101 @@ services:
});
}
[Fact]
public async Task IngressSpecificIPTest()
{
var allIps = GetLiveIPAddresses().ToList();
var testIp = allIps[new Random().Next(allIps.Count)];
await TestIngressIP($"'{testIp}'", new[] { testIp }, allIps.Where(ip => ip != testIp).Take(1));
}
[Fact]
public async Task IngressAllIPv6Test()
{
var ipV6 = GetLiveIPAddresses(AddressFamily.InterNetworkV6).FirstOrDefault();
if (ipV6 == null) return;
await TestIngressIP($"'{IPAddress.IPv6Any}'", ipV6);
}
[Fact]
public async Task IngressAllIPv4Test()
{
var ipV4 = GetLiveIPAddresses(AddressFamily.InterNetwork).FirstOrDefault();
if (ipV4 == null) return;
var ipV6 = GetLiveIPAddresses(AddressFamily.InterNetworkV6).FirstOrDefault();
var failIp = ipV6 == null ? Enumerable.Empty<IPAddress>() : new[] { ipV6 };
await TestIngressIP($"'{IPAddress.Any}'", new[] { ipV4 }, failIp);
}
[Fact]
public async Task IngressAllIPTest()
{
await TestIngressIP($"'*'", GetLiveIPAddresses().FirstOrDefault());
}
private static IEnumerable<IPAddress> GetLiveIPAddresses(AddressFamily? family = null)
{
return from ni in NetworkInterface.GetAllNetworkInterfaces()
where ni.OperationalStatus == OperationalStatus.Up
let prop = ni.GetIPProperties()
from unicast in prop.UnicastAddresses
let addr = unicast.Address
where addr != IPAddress.IPv6Loopback && (family == null || addr.AddressFamily == family)
select addr;
}
private Task TestIngressIP(string ipSetting, params IPAddress[] mustAnswer) => TestIngressIP(ipSetting, mustAnswer, Enumerable.Empty<IPAddress>());
private async Task TestIngressIP(string ipSetting, IEnumerable<IPAddress> mustAnswer, IEnumerable<IPAddress> mustFail)
{
#if !DEBUG
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return; //disables running this test on windows as it stucks the test runner on the firewall open prompt
#endif
if (!mustAnswer.Any() && !mustFail.Any())
return; // no IP to test against
using var projectDirectory = CopyTestProjectDirectory("apps-with-ingress");
var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-ip_test.yaml"));
File.WriteAllText(projectFile.FullName, File.ReadAllText(projectFile.FullName).Replace("__TEST_IP_STRING__", ipSetting));
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 = true
};
var client = new HttpClient(new RetryHandler(handler));
await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
{
foreach (var ip in mustAnswer.Concat(mustFail))
{
try
{
var ingressUri = await GetServiceUrl(client, uri, "ingress");
var reqUri = new UriBuilder(ingressUri + "/index.html")
{
Host = ip.ToString()
};
var htmlRequest = new HttpRequestMessage(HttpMethod.Get, reqUri.Uri);
htmlRequest.Headers.Host = "ui.example.com";
var htmlResponse = await client.SendAsync(htmlRequest);
htmlResponse.EnsureSuccessStatusCode();
}
catch (Exception) when (mustFail.Contains(ip))
{
// this is an expected failure
}
}
});
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task NginxIngressTest()

Просмотреть файл

@ -0,0 +1,20 @@
# 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-allip-ui
ingress:
- name: ingress
bindings:
- port: 8080
ip: __TEST_IP_STRING__
rules:
- host: ui.example.com
service: appC-ui
services:
- name: appC-ui
project: ApplicationC-UI/ApplicationC-UI.csproj
replicas: 2