diff --git a/src/CommunityToolkit.Aspire.Hosting.Ngrok/NgrokEndpoint.cs b/src/CommunityToolkit.Aspire.Hosting.Ngrok/NgrokEndpoint.cs
index 563b89dd..eabcb6c3 100644
--- a/src/CommunityToolkit.Aspire.Hosting.Ngrok/NgrokEndpoint.cs
+++ b/src/CommunityToolkit.Aspire.Hosting.Ngrok/NgrokEndpoint.cs
@@ -5,4 +5,5 @@ namespace Aspire.Hosting.ApplicationModel;
///
/// A unique name for this endpoint's configuration.
/// The address you would like to use to forward traffic to your upstream service. Leave empty to get a randomly assigned address.
-public sealed record NgrokEndpoint(string EndpointName, string? Url);
\ No newline at end of file
+/// An optional dictionary of labels to apply to the endpoint.
+public sealed record NgrokEndpoint(string EndpointName, string? Url, IDictionary? Labels = null);
\ No newline at end of file
diff --git a/src/CommunityToolkit.Aspire.Hosting.Ngrok/NgrokExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.Ngrok/NgrokExtensions.cs
index 12ba5ecb..dc05ade6 100644
--- a/src/CommunityToolkit.Aspire.Hosting.Ngrok/NgrokExtensions.cs
+++ b/src/CommunityToolkit.Aspire.Hosting.Ngrok/NgrokExtensions.cs
@@ -21,13 +21,15 @@ public static class NgrokExtensions
/// The folder where temporary ngrok configuration files will be stored; defaults to .ngrok
/// The port of the endpoint for this resource, defaults to a randomly assigned port.
/// The name of the endpoint for this resource, defaults to http.
+ /// The output version of the ngrok configuration file.
/// A reference to the .
public static IResourceBuilder AddNgrok(
this IDistributedApplicationBuilder builder,
[ResourceName] string name,
string? configurationFolder = null,
int? endpointPort = null,
- [EndpointName] string? endpointName = null)
+ [EndpointName] string? endpointName = null,
+ int? configurationVersion = null)
{
ArgumentNullException.ThrowIfNull(builder);
if (configurationFolder is not null)
@@ -38,6 +40,11 @@ public static IResourceBuilder AddNgrok(
ArgumentOutOfRangeException.ThrowIfLessThan(endpointPort.Value, 1, nameof(endpointPort));
ArgumentOutOfRangeException.ThrowIfGreaterThan(endpointPort.Value, 65535, nameof(endpointPort));
}
+ if (configurationVersion is not null)
+ {
+ ArgumentOutOfRangeException.ThrowIfLessThan(configurationVersion.Value, 2, nameof(configurationVersion));
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(configurationVersion.Value, 3, nameof(configurationVersion));
+ }
configurationFolder ??= Path.Combine(builder.AppHostDirectory, ".ngrok");
if (!Directory.Exists(configurationFolder))
@@ -55,7 +62,7 @@ public static IResourceBuilder AddNgrok(
.OfType()
.SelectMany(annotation => annotation.Endpoints.Select(ngrokEndpoint => (endpointRefernce: annotation.Resource.GetEndpoint(ngrokEndpoint.EndpointName), ngrokEndpoint)))
.ToList();
- await CreateNgrokConfigurationFileAsync(configurationFolder, name, endpointTuples);
+ await CreateNgrokConfigurationFileAsync(configurationFolder, name, endpointTuples, configurationVersion ?? 3);
resourceBuilder.WithArgs(
"start", endpointTuples.Count > 0 ? "--all" : "--none",
@@ -104,25 +111,26 @@ public static IResourceBuilder WithTunnelEndpoint(
this IResourceBuilder builder,
IResourceBuilder resource,
string endpointName,
- string? ngrokUrl = null) where TResource : IResourceWithEndpoints
+ string? ngrokUrl = null,
+ IDictionary? labels = null) where TResource : IResourceWithEndpoints
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(resource);
ArgumentException.ThrowIfNullOrWhiteSpace(endpointName);
if (ngrokUrl is not null)
ArgumentException.ThrowIfNullOrWhiteSpace(ngrokUrl);
-
+
var existingAnnotation = builder.Resource.Annotations
.OfType()
.SingleOrDefault(a => a.Resource.Name == resource.Resource.Name);
if (existingAnnotation is not null)
{
- existingAnnotation.Endpoints.Add(new NgrokEndpoint(endpointName, ngrokUrl));
+ existingAnnotation.Endpoints.Add(new NgrokEndpoint(endpointName, ngrokUrl, labels));
}
else
{
var newAnnotation = new NgrokEndpointAnnotation(resource.Resource);
- newAnnotation.Endpoints.Add(new NgrokEndpoint(endpointName, ngrokUrl));
+ newAnnotation.Endpoints.Add(new NgrokEndpoint(endpointName, ngrokUrl, labels));
builder.Resource.Annotations.Add(newAnnotation);
}
@@ -132,25 +140,48 @@ public static IResourceBuilder WithTunnelEndpoint(
private static async Task CreateNgrokConfigurationFileAsync(
string configurationFolder,
string name,
- IList<(EndpointReference, NgrokEndpoint)> endpointTuples)
+ IList<(EndpointReference, NgrokEndpoint)> endpointTuples,
+ int configurationVersion)
{
var ngrokConfig = new StringBuilder();
- ngrokConfig.AppendLine("version: 3");
+ ngrokConfig.AppendLine($"version: {configurationVersion}");
ngrokConfig.AppendLine();
- ngrokConfig.AppendLine("agent:");
- ngrokConfig.AppendLine( " log: stdout");
- if (endpointTuples.Count > 0)
+ switch (configurationVersion)
{
- ngrokConfig.AppendLine();
- ngrokConfig.AppendLine("endpoints:");
- foreach (var (endpointReference, ngrokEndpoint) in endpointTuples)
- {
- ngrokConfig.AppendLine($" - name: {endpointReference.Resource.Name}-{endpointReference.EndpointName}");
- if (!string.IsNullOrWhiteSpace(ngrokEndpoint.Url))
- ngrokConfig.AppendLine($" url: {ngrokEndpoint.Url}");
- ngrokConfig.AppendLine(" upstream:");
- ngrokConfig.AppendLine($" url: {GetUpstreamUrl(endpointReference)}");
- }
+ case 2:
+ ngrokConfig.AppendLine("tunnels:");
+ foreach (var (endpointReference, ngrokEndpoint) in endpointTuples)
+ {
+ ngrokConfig.AppendLine($" {endpointReference.Resource.Name}-{endpointReference.EndpointName}:");
+ if (ngrokEndpoint.Labels is null)
+ continue;
+ ngrokConfig.AppendLine(" labels:");
+ foreach (var label in ngrokEndpoint.Labels)
+ {
+ ngrokConfig.AppendLine($" - {label.Key}={label.Value}");
+ }
+ ngrokConfig.AppendLine($" addr: {GetUpstreamUrl(endpointReference)}");
+ }
+ break;
+ case 3:
+ ngrokConfig.AppendLine("agent:");
+ ngrokConfig.AppendLine( " log: stdout");
+ if (endpointTuples.Count > 0)
+ {
+ ngrokConfig.AppendLine();
+ ngrokConfig.AppendLine("endpoints:");
+ foreach (var (endpointReference, ngrokEndpoint) in endpointTuples)
+ {
+ ngrokConfig.AppendLine($" - name: {endpointReference.Resource.Name}-{endpointReference.EndpointName}");
+ if (!string.IsNullOrWhiteSpace(ngrokEndpoint.Url))
+ ngrokConfig.AppendLine($" url: {ngrokEndpoint.Url}");
+ ngrokConfig.AppendLine(" upstream:");
+ ngrokConfig.AppendLine($" url: {GetUpstreamUrl(endpointReference)}");
+ }
+ }
+ break;
+ default:
+ break;
}
await File.WriteAllTextAsync(Path.Combine(configurationFolder, $"{name}.yml"), ngrokConfig.ToString());
diff --git a/src/CommunityToolkit.Aspire.Hosting.Ngrok/PublicAPI.Shipped.txt b/src/CommunityToolkit.Aspire.Hosting.Ngrok/PublicAPI.Shipped.txt
index 3b5032d8..206a9eb5 100644
--- a/src/CommunityToolkit.Aspire.Hosting.Ngrok/PublicAPI.Shipped.txt
+++ b/src/CommunityToolkit.Aspire.Hosting.Ngrok/PublicAPI.Shipped.txt
@@ -2,18 +2,20 @@
Aspire.Hosting.ApplicationModel.NgrokResource
Aspire.Hosting.ApplicationModel.NgrokResource.NgrokResource(string! name) -> void
Aspire.Hosting.ApplicationModel.NgrokEndpoint
-Aspire.Hosting.ApplicationModel.NgrokEndpoint.NgrokEndpoint(string! EndpointName, string? Url) -> void
+Aspire.Hosting.ApplicationModel.NgrokEndpoint.NgrokEndpoint(string! EndpointName, string? Url, System.Collections.Generic.IDictionary? Labels = null) -> void
Aspire.Hosting.ApplicationModel.NgrokEndpoint.EndpointName.get -> string!
Aspire.Hosting.ApplicationModel.NgrokEndpoint.EndpointName.init -> void
Aspire.Hosting.ApplicationModel.NgrokEndpoint.Url.get -> string?
Aspire.Hosting.ApplicationModel.NgrokEndpoint.Url.init -> void
+Aspire.Hosting.ApplicationModel.NgrokEndpoint.Labels.get -> System.Collections.Generic.IDictionary?
+Aspire.Hosting.ApplicationModel.NgrokEndpoint.Labels.init -> void
Aspire.Hosting.ApplicationModel.NgrokEndpointAnnotation
Aspire.Hosting.ApplicationModel.NgrokEndpointAnnotation.NgrokEndpointAnnotation(Aspire.Hosting.ApplicationModel.IResourceWithEndpoints! Resource) -> void
Aspire.Hosting.ApplicationModel.NgrokEndpointAnnotation.Resource.get -> Aspire.Hosting.ApplicationModel.IResourceWithEndpoints!
Aspire.Hosting.ApplicationModel.NgrokEndpointAnnotation.Resource.init -> void
Aspire.Hosting.ApplicationModel.NgrokEndpointAnnotation.Endpoints.get -> System.Collections.Generic.ICollection!
Aspire.Hosting.NgrokExtensions
-static Aspire.Hosting.NgrokExtensions.AddNgrok(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, string? configurationFolder = null, int? endpointPort = null, string? endpointName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
+static Aspire.Hosting.NgrokExtensions.AddNgrok(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, string? configurationFolder = null, int? endpointPort = null, string? endpointName = null, int? configurationVersion = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
static Aspire.Hosting.NgrokExtensions.WithAuthToken(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! ngrokAuthToken) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
static Aspire.Hosting.NgrokExtensions.WithAuthToken(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, Aspire.Hosting.ApplicationModel.IResourceBuilder! ngrokAuthToken) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
-static Aspire.Hosting.NgrokExtensions.WithTunnelEndpoint(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, Aspire.Hosting.ApplicationModel.IResourceBuilder! resource, string! endpointName, string? ngrokUrl = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
+static Aspire.Hosting.NgrokExtensions.WithTunnelEndpoint(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, Aspire.Hosting.ApplicationModel.IResourceBuilder! resource, string! endpointName, string? ngrokUrl = null, System.Collections.Generic.IDictionary? labels = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
diff --git a/tests/CommunityToolkit.Aspire.Hosting.Ngrok.Tests/WithTunnelEndpointTests.cs b/tests/CommunityToolkit.Aspire.Hosting.Ngrok.Tests/WithTunnelEndpointTests.cs
index be5d26fa..cbef007d 100644
--- a/tests/CommunityToolkit.Aspire.Hosting.Ngrok.Tests/WithTunnelEndpointTests.cs
+++ b/tests/CommunityToolkit.Aspire.Hosting.Ngrok.Tests/WithTunnelEndpointTests.cs
@@ -66,6 +66,49 @@ public void WithTunnelEndpointSetsAnnotationUrl()
Assert.Equal("custom-url", endpoint.Url);
}
+
+ [Fact]
+ public void WithTunnelEndpointSetsLabelsToNullByDefault()
+ {
+ var builder = DistributedApplication.CreateBuilder();
+
+ var api = builder
+ .AddProject("api");
+
+ builder.AddNgrok("ngrok")
+ .WithTunnelEndpoint(api,"http");
+
+ using var app = builder.Build();
+
+ var appModel = app.Services.GetRequiredService();
+ var resource = Assert.Single(appModel.Resources.OfType());
+ var annotation = Assert.Single(resource.Annotations.OfType());
+ var endpoint = Assert.Single(annotation.Endpoints);
+
+ Assert.Null(endpoint.Labels);
+ }
+
+ [Fact]
+ public void WithTunnelEndpointSetsLabels()
+ {
+ var builder = DistributedApplication.CreateBuilder();
+
+ var api = builder
+ .AddProject("api");
+
+ builder.AddNgrok("ngrok")
+ .WithTunnelEndpoint(api,"http", "custom-url", new Dictionary() { ["key"] = "value" });
+
+ using var app = builder.Build();
+
+ var appModel = app.Services.GetRequiredService();
+ var resource = Assert.Single(appModel.Resources.OfType());
+ var annotation = Assert.Single(resource.Annotations.OfType());
+ var endpoint = Assert.Single(annotation.Endpoints);
+
+ Assert.Equal("key", endpoint.Labels!.Keys.First());
+ Assert.Equal("value", endpoint.Labels!["key"]);
+ }
[Fact]
public void WithTunnelNullResourceBuilderThrows()