File: Dcp\AppResource.cs
Web Access
Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting)
// 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;
using Aspire.Hosting.Dcp.Model;
using System.Diagnostics;
 
namespace Aspire.Hosting.Dcp;
 
internal interface IAppResource : IDisposable
{
    CustomResource DcpResource { get; }
    string DcpResourceName { get; }
    string DcpResourceKind { get; }
    SemaphoreSlim SerializedOpSemaphore { get; }
    Task Initialized { get; }
 
    void MarkInitialized();
}
 
[DebuggerDisplay("DcpResourceName = {DcpResourceName}, DcpResourceKind = {DcpResourceKind}")]
internal class AppResource<TDcpResource> : IAppResource, IDisposable, IEquatable<AppResource<TDcpResource>> where TDcpResource : CustomResource, IKubernetesStaticMetadata
{
    public TDcpResource DcpResource { get; }
    public string DcpResourceName => DcpResource.Metadata.Name;
    public string DcpResourceKind => TDcpResource.ObjectKind;
 
    // Semaphore to serialize operations on this resource. For example, it can be used to ensure 
    // that resources are not restarted concurrently, nor can they be restarted before their initial setup is complete.
    public SemaphoreSlim SerializedOpSemaphore { get; } = new SemaphoreSlim(1, 1);
 
    public Task Initialized => _initializedTcs.Task;
 
    private readonly TaskCompletionSource _initializedTcs;
 
    CustomResource IAppResource.DcpResource => DcpResource;
 
    public AppResource(TDcpResource dcpResource)
    {
        DcpResource = dcpResource;
        _initializedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
    }
 
    public bool Equals(AppResource<TDcpResource>? other)
    {
        if (other is null)
        {
            return false;
        }
        var dr = DcpResource;
        var odr = other.DcpResource;
        return dr.GetType().Equals(odr.GetType()) &&
            dr.Metadata.Name == odr.Metadata.Name &&
            dr.Metadata.NamespaceProperty == odr.Metadata.NamespaceProperty;
    }
 
    public void MarkInitialized()
    {
        _initializedTcs.TrySetResult();
    }
 
    public void Dispose()
    {
        SerializedOpSemaphore.Dispose();
    }
}
 
[DebuggerDisplay("ModelResource = {ModelResource}, DcpResourceName = {DcpResourceName}, DcpResourceKind = {DcpResourceKind}")]
internal class RenderedModelResource<TDcpResource> : AppResource<TDcpResource>, IResourceReference where TDcpResource : CustomResource, IKubernetesStaticMetadata
{
    public IResource ModelResource { get; }
 
    public RenderedModelResource(IResource modelResource, TDcpResource dcpResource) : base(dcpResource)
    {
        ModelResource = modelResource;
    }
 
    public virtual List<ServiceWithModelResource> ServicesProduced { get; } = [];
    public virtual List<ServiceWithModelResource> ServicesConsumed { get; } = [];
}
 
internal sealed class ServiceWithModelResource : RenderedModelResource<Service>
{
    public Service Service => DcpResource;
    public EndpointAnnotation EndpointAnnotation { get; }
 
    public override List<ServiceWithModelResource> ServicesProduced
    {
        get { throw new InvalidOperationException("Service resources do not produce any services"); }
    }
    public override List<ServiceWithModelResource> ServicesConsumed
    {
        get { throw new InvalidOperationException("Service resources do not consume any services"); }
    }
 
    public ServiceWithModelResource(IResource modelResource, Service service, EndpointAnnotation sba) : base(modelResource, service)
    {
        EndpointAnnotation = sba;
    }
}
 
internal interface IResourceReference
{
    IResource ModelResource { get; }
    string DcpResourceName { get; }
}