Skip to content

Commit 8ad9299

Browse files
Fix YarpCluster construction and add constructor that takes IResourceWithServiceDiscovery (#10060)
* Rename YarpResource.Destinations to YarpResource.Clusters * Fix destination generation in YarpCluster * Add methods for YARP that take an IResourceWithServiceDiscovery * In the YarpCluster ctor, take an IResourceWithServiceDiscovery directly
1 parent 2f03451 commit 8ad9299

File tree

7 files changed

+190
-17
lines changed

7 files changed

+190
-17
lines changed

playground/yarp/Yarp.AppHost/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
var gateway = builder.AddYarp("gateway")
1313
.WithConfiguration(yarp =>
1414
{
15-
yarp.AddRoute(frontendService.GetEndpoint("http"));
16-
yarp.AddRoute("/api/{**catch-all}", backendService.GetEndpoint("http"))
15+
yarp.AddRoute(frontendService);
16+
yarp.AddRoute("/api/{**catch-all}", backendService)
1717
.WithTransformPathRemovePrefix("/api");
1818
});
1919

src/Aspire.Hosting.Yarp/ConfigurationBuilder/IYarpConfigurationBuilder.cs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,16 @@ public interface IYarpConfigurationBuilder
2222
/// <summary>
2323
/// Add a new cluster to YARP.
2424
/// </summary>
25-
/// <param name="endpoint">The endpoint used by this cluster.</param>
25+
/// <param name="endpoint">The endpoint target for this cluster.</param>
2626
/// <returns></returns>
2727
public YarpCluster AddCluster(EndpointReference endpoint);
28+
29+
/// <summary>
30+
/// Add a new cluster to YARP.
31+
/// </summary>
32+
/// <param name="resource">The resource target for this cluster.</param>
33+
/// <returns></returns>
34+
public YarpCluster AddCluster(IResourceBuilder<IResourceWithServiceDiscovery> resource);
2835
}
2936

3037
/// <summary>
@@ -45,6 +52,28 @@ public static YarpRoute AddRoute(this IYarpConfigurationBuilder builder, YarpClu
4552
return builder.AddRoute(CatchAllPath, cluster);
4653
}
4754

55+
/// <summary>
56+
/// Add a new catch all route to YARP that will target the cluster in parameter.
57+
/// </summary>
58+
/// <param name="builder">The builder instance.</param>
59+
/// <param name="endpoint">The target endpoint for this route.</param>
60+
/// <returns></returns>
61+
public static YarpRoute AddRoute(this IYarpConfigurationBuilder builder, EndpointReference endpoint)
62+
{
63+
return builder.AddRoute(CatchAllPath, endpoint);
64+
}
65+
66+
/// <summary>
67+
/// Add a new catch all route to YARP that will target the cluster in parameter.
68+
/// </summary>
69+
/// <param name="builder">The builder instance.</param>
70+
/// <param name="resource">The target resource for this route.</param>
71+
/// <returns></returns>
72+
public static YarpRoute AddRoute(this IYarpConfigurationBuilder builder, IResourceBuilder<IResourceWithServiceDiscovery> resource)
73+
{
74+
return builder.AddRoute(CatchAllPath, resource);
75+
}
76+
4877
/// <summary>
4978
/// Add a new route to YARP that will target the cluster in parameter.
5079
/// </summary>
@@ -59,13 +88,15 @@ public static YarpRoute AddRoute(this IYarpConfigurationBuilder builder, string
5988
}
6089

6190
/// <summary>
62-
/// Add a new catch all route to YARP that will target the cluster in parameter.
91+
/// Add a new route to YARP that will target the cluster in parameter.
6392
/// </summary>
6493
/// <param name="builder">The builder instance.</param>
65-
/// <param name="endpoint">The target endpoint for this route.</param>
94+
/// <param name="path">The path to match for this route.</param>
95+
/// <param name="resource">The target endpoint for this route.</param>
6696
/// <returns></returns>
67-
public static YarpRoute AddRoute(this IYarpConfigurationBuilder builder, EndpointReference endpoint)
97+
public static YarpRoute AddRoute(this IYarpConfigurationBuilder builder, string path, IResourceBuilder<IResourceWithServiceDiscovery> resource)
6898
{
69-
return builder.AddRoute(CatchAllPath, endpoint);
99+
var cluster = builder.AddCluster(resource);
100+
return builder.AddRoute(path, cluster);
70101
}
71102
}

src/Aspire.Hosting.Yarp/ConfigurationBuilder/YarpCluster.cs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,62 @@ namespace Aspire.Hosting.Yarp;
1010
/// <summary>
1111
/// Represents a cluster for YARP routes
1212
/// </summary>
13-
public class YarpCluster(EndpointReference endpoint)
13+
public class YarpCluster
1414
{
15-
internal ClusterConfig ClusterConfig { get; private set; } = new()
15+
/// <summary>
16+
/// Construct a new YarpCluster targeting the endpoint in parameter.
17+
/// </summary>
18+
/// <param name="endpoint">The endpoint to target.</param>
19+
public YarpCluster(EndpointReference endpoint)
20+
: this(endpoint.Resource.Name, $"{endpoint.Scheme}://_{endpoint.EndpointName}.{endpoint.Resource.Name}")
21+
{
22+
}
23+
24+
/// <summary>
25+
/// Construct a new YarpCluster targeting the resource in parameter.
26+
/// </summary>
27+
/// <param name="resource">The resource to target.</param>
28+
public YarpCluster(IResourceWithServiceDiscovery resource)
29+
: this(resource.Name, BuildEndpointUri(resource))
30+
{
31+
}
32+
33+
private YarpCluster(string resourceName, string endpointUri)
1634
{
17-
ClusterId = $"cluster_{endpoint.Resource.Name}_{Guid.NewGuid().ToString("N")}",
18-
Destinations = new Dictionary<string, DestinationConfig>(StringComparer.OrdinalIgnoreCase)
35+
ClusterConfig = new()
1936
{
20-
{ "destination1", new DestinationConfig { Address = $"{endpoint.Scheme}://{endpoint.Resource.Name}" } },
21-
}
22-
};
37+
ClusterId = $"cluster_{resourceName}_{Guid.NewGuid().ToString("N")}",
38+
Destinations = new Dictionary<string, DestinationConfig>(StringComparer.OrdinalIgnoreCase)
39+
{
40+
{ "destination1", new DestinationConfig { Address = endpointUri } }
41+
}
42+
};
43+
}
44+
45+
internal ClusterConfig ClusterConfig { get; private set; }
2346

2447
internal void Configure(Func<ClusterConfig, ClusterConfig> configure)
2548
{
2649
ClusterConfig = configure(ClusterConfig);
2750
}
51+
52+
private static string BuildEndpointUri(IResourceWithServiceDiscovery resource)
53+
{
54+
var resourceName = resource.Name;
55+
56+
var httpsEndpoint = resource.GetEndpoint("https");
57+
var httpEndpoint = resource.GetEndpoint("http");
58+
59+
var scheme = (httpsEndpoint.Exists, httpEndpoint.Exists) switch
60+
{
61+
(true, true) => "https+http",
62+
(true, false) => "https",
63+
(false, true) => "http",
64+
_ => throw new ArgumentException("Cannot find a http or https endpoint for this resource.", nameof(resource))
65+
};
66+
67+
return $"{scheme}://{resourceName}";
68+
}
2869
}
2970

3071
/// <summary>

src/Aspire.Hosting.Yarp/ConfigurationBuilder/YarpConfigurationBuilder.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,17 @@ public YarpRoute AddRoute(string path, YarpCluster cluster)
2626
public YarpCluster AddCluster(EndpointReference endpoint)
2727
{
2828
var destination = new YarpCluster(endpoint);
29-
_parent.Resource.Destinations.Add(destination);
29+
_parent.Resource.Clusters.Add(destination);
3030
_parent.WithReference(endpoint);
3131
return destination;
3232
}
33+
34+
/// <inheritdoc/>
35+
public YarpCluster AddCluster(IResourceBuilder<IResourceWithServiceDiscovery> resource)
36+
{
37+
var destination = new YarpCluster(resource.Resource);
38+
_parent.Resource.Clusters.Add(destination);
39+
_parent.WithReference(resource);
40+
return destination;
41+
}
3342
}

src/Aspire.Hosting.Yarp/YarpResource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ public class YarpResource(string name) : ContainerResource(name)
1818

1919
internal List<YarpRoute> Routes { get; } = new();
2020

21-
internal List<YarpCluster> Destinations { get; } = new();
21+
internal List<YarpCluster> Clusters { get; } = new();
2222
}

src/Aspire.Hosting.Yarp/YarpResourceExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public static IResourceBuilder<YarpResource> AddYarp(
5151
{
5252
yarpBuilder.Resource.ConfigurationBuilder.AddRoute(route.RouteConfig);
5353
}
54-
foreach (var destination in yarpBuilder.Resource.Destinations)
54+
foreach (var destination in yarpBuilder.Resource.Clusters)
5555
{
5656
yarpBuilder.Resource.ConfigurationBuilder.AddCluster(destination.ClusterConfig);
5757
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Hosting.ApplicationModel;
5+
using Aspire.Hosting.Utils;
6+
7+
namespace Aspire.Hosting.Yarp.Tests;
8+
9+
public class YarpClusterTests(ITestOutputHelper testOutputHelper)
10+
{
11+
[Fact]
12+
public void Create_YarpCluster_From_Endpoints_With_Names()
13+
{
14+
using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper);
15+
var resource = builder.AddResource(new TestResource("ServiceA"))
16+
.WithHttpEndpoint(name: "testendpoint")
17+
.WithHttpsEndpoint(name: "anotherendpoint");
18+
19+
var httpEndpoint = resource.GetEndpoint("testendpoint");
20+
var httpsEndpoint = resource.GetEndpoint("anotherendpoint");
21+
22+
var httpCluster = new YarpCluster(httpEndpoint);
23+
var httpDestination = httpCluster.ClusterConfig.Destinations!.FirstOrDefault();
24+
Assert.Equal($"http://_testendpoint.ServiceA", httpDestination.Value.Address);
25+
26+
var httpsCluster = new YarpCluster(httpsEndpoint);
27+
var httpsDestination = httpsCluster.ClusterConfig.Destinations!.FirstOrDefault();
28+
Assert.Equal($"https://_anotherendpoint.ServiceA", httpsDestination.Value.Address);
29+
}
30+
31+
[Fact]
32+
public void Create_YarpCluster_From_Endpoints_Without_Names()
33+
{
34+
using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper);
35+
var resource = builder.AddResource(new TestResource("ServiceC"))
36+
.WithHttpEndpoint()
37+
.WithHttpsEndpoint();
38+
39+
var httpEndpoint = resource.GetEndpoint("http");
40+
var httpsEndpoint = resource.GetEndpoint("https");
41+
42+
var httpCluster = new YarpCluster(httpEndpoint);
43+
var httpDestination = httpCluster.ClusterConfig.Destinations!.FirstOrDefault();
44+
Assert.Equal($"http://_http.ServiceC", httpDestination.Value.Address);
45+
46+
var httpsCluster = new YarpCluster(httpsEndpoint);
47+
var httpsDestination = httpsCluster.ClusterConfig.Destinations!.FirstOrDefault();
48+
Assert.Equal($"https://_https.ServiceC", httpsDestination.Value.Address);
49+
}
50+
51+
[Fact]
52+
public void Create_YarpCluster_From_Resource_With_One_Endpoint()
53+
{
54+
using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper);
55+
56+
var httpService = builder.AddResource(new TestResource("ServiceC"))
57+
.WithHttpEndpoint()
58+
.WithHttpEndpoint(name: "grpc");
59+
60+
var httpCluster = new YarpCluster(httpService.Resource);
61+
var httpDestination = httpCluster.ClusterConfig.Destinations!.FirstOrDefault();
62+
Assert.Equal($"http://ServiceC", httpDestination.Value.Address);
63+
64+
var httpsService = builder.AddResource(new TestResource("ServiceD"))
65+
.WithHttpsEndpoint()
66+
.WithHttpsEndpoint(name: "grpc");
67+
68+
var httpsCluster = new YarpCluster(httpsService.Resource);
69+
var httpsDestination = httpsCluster.ClusterConfig.Destinations!.FirstOrDefault();
70+
Assert.Equal($"https://ServiceD", httpsDestination.Value.Address);
71+
}
72+
73+
[Fact]
74+
public void Create_YarpCluster_From_Resource_With_Both_Endpoints()
75+
{
76+
using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper);
77+
var serviceA = builder.AddResource(new TestResource("ServiceA"))
78+
.WithHttpEndpoint()
79+
.WithHttpsEndpoint();
80+
81+
var clusterA = new YarpCluster(serviceA.Resource);
82+
var httpDestination = clusterA.ClusterConfig.Destinations!.FirstOrDefault();
83+
Assert.Equal($"https+http://ServiceA", httpDestination.Value.Address);
84+
}
85+
86+
private sealed class TestResource(string name) : IResourceWithServiceDiscovery
87+
{
88+
public string Name => name;
89+
90+
public ResourceAnnotationCollection Annotations { get; } = new ResourceAnnotationCollection();
91+
}
92+
}

0 commit comments

Comments
 (0)