File: DevTunnelResource.cs
Web Access
Project: src\src\Aspire.Hosting.DevTunnels\Aspire.Hosting.DevTunnels.csproj (Aspire.Hosting.DevTunnels)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Hosting.ApplicationModel;
 
namespace Aspire.Hosting.DevTunnels;
 
/// <summary>
/// A resource representing a persistent dev tunnel that runs for the life of the AppHost.
/// </summary>
/// <param name="name"></param>
/// <param name="tunnelId"></param>
/// <param name="command"></param>
/// <param name="workingDirectory"></param>
/// <param name="options"></param>
public sealed class DevTunnelResource(string name, string tunnelId, string command, string workingDirectory, DevTunnelOptions? options = null)
    : ExecutableResource(name, command, workingDirectory)
{
    /// <summary>
    /// Options controlling how this tunnel is created and managed.
    /// </summary>
    public DevTunnelOptions Options { get; } = options ?? new DevTunnelOptions();
 
    /// <summary>
    /// The unique ID for the dev tunnel.
    /// </summary>
    public string TunnelId { get; init; } = tunnelId;
 
    internal List<DevTunnelPortResource> Ports { get; } = [];
 
    internal DevTunnelStatus? LastKnownStatus { get; set; }
 
    internal DevTunnelAccessStatus? LastKnownAccessStatus { get; set; }
}
 
/// <summary>
/// A resource representing a single forwarded endpoint/port on a dev tunnel.
/// Contains an endpoint that resolves to the public tunnel URL of this port.
/// </summary>
public sealed class DevTunnelPortResource : Resource, IResourceWithServiceDiscovery, IResourceWithWaitSupport
{
    /// <summary>
    /// The name of the endpoint within this resource that represents the public URL of the tunnel for this port.
    /// </summary>
    public const string TunnelEndpointName = "tunnel";
 
    /// <summary>
    /// Initializes a new instance of the <see cref="DevTunnelPortResource"/> class, representing a single forwarded endpoint/port on a dev tunnel.
    /// </summary>
    /// <param name="name">The name of the port resource.</param>
    /// <param name="tunnel">The parent <see cref="DevTunnelResource"/> this port belongs to.</param>
    /// <param name="targetEndpoint">The endpoint to be forwarded through the tunnel.</param>
    /// <param name="options">Options controlling how this port is exposed.</param>
    public DevTunnelPortResource(
        string name,
        DevTunnelResource tunnel,
        EndpointReference targetEndpoint,
        DevTunnelPortOptions? options = null) : base(name)
    {
        DevTunnel = tunnel;
        Options = options ?? new DevTunnelPortOptions();
        TargetEndpoint = targetEndpoint;
        TunnelEndpointAnnotation = new EndpointAnnotation(
            System.Net.Sockets.ProtocolType.Tcp,
            "https",
            transport: null,
            name: TunnelEndpointName,
            isProxied: false);
        TunnelEndpoint = new(targetEndpoint.Resource, TunnelEndpointAnnotation);
    }
 
    /// <summary>
    /// The dev tunnel this port belongs to.
    /// </summary>
    public DevTunnelResource DevTunnel { get; }
 
    /// <summary>
    /// Options controlling how this port is exposed.
    /// </summary>
    public DevTunnelPortOptions Options { get; }
 
    internal EndpointReference TunnelEndpoint { get; }
 
    internal EndpointAnnotation TunnelEndpointAnnotation { get; }
 
    internal TaskCompletionSource TunnelEndpointAllocatedTcs { get; } = new();
 
    internal Task TunnelEndpointAllocatedTask => TunnelEndpointAllocatedTcs.Task;
 
    internal EndpointReference TargetEndpoint { get; init; }
 
    internal TaskCompletionSource TargetEndpointAllocatedTcs { get; set; } = new();
 
    internal Task TargetEndpointAllocatedTask => TargetEndpointAllocatedTcs.Task;
 
    internal DevTunnelStatus.DevTunnelPort? LastKnownStatus { get; set; }
 
    internal DevTunnelAccessStatus? LastKnownAccessStatus { get; set; }
}