File: KubernetesServiceResourceExtensions.cs
Web Access
Project: src\src\Aspire.Hosting.Kubernetes\Aspire.Hosting.Kubernetes.csproj (Aspire.Hosting.Kubernetes)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Globalization;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Kubernetes.Extensions;
using static Aspire.Hosting.Kubernetes.KubernetesResource;
 
namespace Aspire.Hosting.Kubernetes;
 
internal static class KubernetesServiceResourceExtensions
{
    internal static async Task<object> ProcessValueAsync(this KubernetesResource resource, KubernetesEnvironmentContext context, DistributedApplicationExecutionContext executionContext, object value)
    {
        while (true)
        {
            if (value is string s)
            {
                return s;
            }
 
            if (value is EndpointReference ep)
            {
                var referencedResource = ep.Resource == resource
                    ? resource
                    : await context.CreateKubernetesResourceAsync(ep.Resource, executionContext, default).ConfigureAwait(false);
 
                var mapping = referencedResource.EndpointMappings[ep.EndpointName];
 
                var url = GetEndpointValue(mapping, EndpointProperty.Url);
 
                return url;
            }
 
            if (value is ParameterResource param)
            {
                return AllocateParameter(param, resource.TargetResource);
            }
 
            if (value is ConnectionStringReference cs)
            {
                value = cs.Resource.ConnectionStringExpression;
                continue;
            }
 
            if (value is IResourceWithConnectionString csrs)
            {
                value = csrs.ConnectionStringExpression;
                continue;
            }
 
            if (value is EndpointReferenceExpression epExpr)
            {
                var referencedResource = epExpr.Endpoint.Resource == resource
                    ? resource
                    : await context.CreateKubernetesResourceAsync(epExpr.Endpoint.Resource, executionContext, default).ConfigureAwait(false);
 
                var mapping = referencedResource.EndpointMappings[epExpr.Endpoint.EndpointName];
 
                var val = GetEndpointValue(mapping, epExpr.Property);
 
                return val;
            }
 
            if (value is ReferenceExpression expr)
            {
                if (expr is { Format: "{0}", ValueProviders.Count: 1 })
                {
                    return (await resource.ProcessValueAsync(context, executionContext, expr.ValueProviders[0]).ConfigureAwait(false)).ToString() ?? string.Empty;
                }
 
                var args = new object[expr.ValueProviders.Count];
                var index = 0;
 
                foreach (var vp in expr.ValueProviders)
                {
                    var val = await resource.ProcessValueAsync(context, executionContext, vp).ConfigureAwait(false);
                    args[index++] = val ?? throw new InvalidOperationException("Value is null");
                }
 
                return string.Format(CultureInfo.InvariantCulture, expr.Format, args);
            }
 
            // If we don't know how to process the value, we just return it as an external reference
            if (value is IManifestExpressionProvider r)
            {
                context.Logger.NotSupportedResourceWarning(nameof(value), r.GetType().Name);
 
                return ResolveUnknownValue(r, resource.TargetResource);
            }
 
            throw new NotSupportedException($"Unsupported value type: {value.GetType().Name}");
        }
    }
 
    private static string GetEndpointValue(EndpointMapping mapping, EndpointProperty property)
    {
        var (scheme, host, port, _, _) = mapping;
 
        return property switch
        {
            EndpointProperty.Url => GetHostValue($"{scheme}://", suffix: $":{port}"),
            EndpointProperty.Host or EndpointProperty.IPV4Host => GetHostValue(),
            EndpointProperty.Port => port,
            EndpointProperty.HostAndPort => GetHostValue(suffix: $":{port}"),
            EndpointProperty.TargetPort => port,
            EndpointProperty.Scheme => scheme,
            _ => throw new NotSupportedException(),
        };
 
        string GetHostValue(string? prefix = null, string? suffix = null)
        {
            return $"{prefix}{host}{suffix}";
        }
    }
 
    private static HelmExpressionWithValue AllocateParameter(ParameterResource parameter, IResource resource)
    {
        var formattedName = parameter.Name.ToManifestFriendlyResourceName();
 
        var expression = parameter.Secret ?
            formattedName.ToHelmSecretExpression(resource.Name) :
            formattedName.ToHelmConfigExpression(resource.Name);
 
        var value = parameter.Default is null || parameter.Secret ? null : parameter.Value;
        return new(expression, value);
    }
 
    private static HelmExpressionWithValue ResolveUnknownValue(IManifestExpressionProvider parameter, IResource resource)
    {
        var formattedName = parameter.ValueExpression.Replace("{", "")
            .Replace("}", "")
            .Replace(".", "_")
            .ToManifestFriendlyResourceName();
 
        var helmExpression = parameter.ValueExpression.ContainsHelmSecretExpression() ?
            formattedName.ToHelmSecretExpression(resource.Name) :
            formattedName.ToHelmConfigExpression(resource.Name);
 
        return new(helmExpression, parameter.ValueExpression);
    }
}