File: Configuration\ValidateDashboardOptions.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// 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;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
 
namespace Aspire.Dashboard.Configuration;
 
public sealed class ValidateDashboardOptions : IValidateOptions<DashboardOptions>
{
    private readonly ILogger _logger;
 
    public ValidateDashboardOptions() : this(NullLogger<ValidateDashboardOptions>.Instance)
    {
    }
 
    public ValidateDashboardOptions(ILogger<ValidateDashboardOptions> logger)
    {
        _logger = logger;
    }
 
    public ValidateOptionsResult Validate(string? name, DashboardOptions options)
    {
        _logger.LogDebug($"Validating {nameof(DashboardOptions)}.");
 
        var errorMessages = new List<string>();
 
        if (!options.Frontend.TryParseOptions(out var frontendParseErrorMessage))
        {
            errorMessages.Add(frontendParseErrorMessage);
        }
 
        switch (options.Frontend.AuthMode)
        {
            case FrontendAuthMode.Unsecured:
                break;
            case FrontendAuthMode.OpenIdConnect:
                if (!options.Frontend.OpenIdConnect.TryParseOptions(out var messages))
                {
                    errorMessages.AddRange(messages);
                }
                break;
            case FrontendAuthMode.BrowserToken:
                if (string.IsNullOrEmpty(options.Frontend.BrowserToken))
                {
                    errorMessages.Add($"BrowserToken is required when frontend authentication mode is browser token. Specify a {DashboardConfigNames.DashboardFrontendBrowserTokenName.ConfigKey} value.");
                }
                break;
            case null:
                errorMessages.Add($"Frontend endpoint authentication is not configured. Either specify {DashboardConfigNames.DashboardUnsecuredAllowAnonymousName.ConfigKey}=true, or specify {DashboardConfigNames.DashboardFrontendAuthModeName.ConfigKey}. Possible values: {string.Join(", ", typeof(FrontendAuthMode).GetEnumNames())}");
                break;
            default:
                errorMessages.Add($"Unexpected frontend authentication mode: {options.Otlp.AuthMode}");
                break;
        }
 
        if (options.Frontend.MaxConsoleLogCount <= 0)
        {
            errorMessages.Add($"{DashboardConfigNames.DashboardFrontendMaxConsoleLogCountName.ConfigKey} must be greater than zero.");
        }
 
        if (!options.Otlp.TryParseOptions(out var otlpParseErrorMessage))
        {
            errorMessages.Add(otlpParseErrorMessage);
        }
 
        switch (options.Otlp.AuthMode)
        {
            case OtlpAuthMode.Unsecured:
                break;
            case OtlpAuthMode.ApiKey:
                if (string.IsNullOrEmpty(options.Otlp.PrimaryApiKey))
                {
                    errorMessages.Add($"PrimaryApiKey is required when OTLP authentication mode is API key. Specify a {DashboardConfigNames.DashboardOtlpPrimaryApiKeyName.ConfigKey} value.");
                }
                break;
            case OtlpAuthMode.ClientCertificate:
                for (var i = 0; i < options.Otlp.AllowedCertificates.Count; i++)
                {
                    var allowedCertRule = options.Otlp.AllowedCertificates[i];
                    if (string.IsNullOrEmpty(allowedCertRule.Thumbprint))
                    {
                        errorMessages.Add($"Thumbprint on allow certificate rule is not configured. Specify a {DashboardConfigNames.DashboardOtlpAllowedCertificatesName.ConfigKey}:{i}:Thumbprint value.");
                    }
                }
                break;
            case null:
                errorMessages.Add($"OTLP endpoint authentication is not configured. Either specify {DashboardConfigNames.DashboardUnsecuredAllowAnonymousName.ConfigKey}=true, or specify {DashboardConfigNames.DashboardOtlpAuthModeName.ConfigKey}. Possible values: {string.Join(", ", typeof(OtlpAuthMode).GetEnumNames())}");
                break;
            default:
                errorMessages.Add($"Unexpected OTLP authentication mode: {options.Otlp.AuthMode}");
                break;
        }
 
        if (!options.ResourceServiceClient.TryParseOptions(out var resourceServiceClientParseErrorMessage))
        {
            errorMessages.Add(resourceServiceClientParseErrorMessage);
        }
 
        // Only validate resource service configuration if we have a URI to connect to.
        // If we do not, then the dashboard will run without resources, but still show OTEL data.
        if (options.ResourceServiceClient.GetUri() != null)
        {
            switch (options.ResourceServiceClient.AuthMode)
            {
                case ResourceClientAuthMode.Unsecured:
                    break;
                case ResourceClientAuthMode.ApiKey:
                    if (string.IsNullOrWhiteSpace(options.ResourceServiceClient.ApiKey))
                    {
                        errorMessages.Add($"{DashboardConfigNames.ResourceServiceClientAuthModeName.ConfigKey} is \"{nameof(ResourceClientAuthMode.ApiKey)}\", but no {DashboardConfigNames.ResourceServiceClientApiKeyName.ConfigKey} is configured.");
                    }
                    break;
                case ResourceClientAuthMode.Certificate:
                    switch (options.ResourceServiceClient.ClientCertificate.Source)
                    {
                        case DashboardClientCertificateSource.File:
                            if (string.IsNullOrEmpty(options.ResourceServiceClient.ClientCertificate.FilePath))
                            {
                                errorMessages.Add($"{DashboardConfigNames.ResourceServiceClientCertificateSourceName.ConfigKey} is \"File\", but no {DashboardConfigNames.ResourceServiceClientCertificateFilePathName.ConfigKey} is configured.");
                            }
                            break;
                        case DashboardClientCertificateSource.KeyStore:
                            if (string.IsNullOrEmpty(options.ResourceServiceClient.ClientCertificate.Subject))
                            {
                                errorMessages.Add($"{DashboardConfigNames.ResourceServiceClientCertificateSourceName.ConfigKey} is \"KeyStore\", but no {DashboardConfigNames.ResourceServiceClientCertificateSubjectName.ConfigKey} is configured.");
                            }
                            break;
                        case null:
                            errorMessages.Add($"The resource service client is configured to use certificates, but no certificate source is specified. Specify {DashboardConfigNames.ResourceServiceClientCertificateSourceName.ConfigKey}. Possible values: {string.Join(", ", typeof(DashboardClientCertificateSource).GetEnumNames())}");
                            break;
                        default:
                            errorMessages.Add($"Unexpected resource service client certificate source: {options.ResourceServiceClient.ClientCertificate.Source}");
                            break;
                    }
                    break;
                case null:
                    errorMessages.Add($"Resource service client authentication is not configured. Specify {DashboardConfigNames.ResourceServiceClientAuthModeName.ConfigKey}. Possible values: {string.Join(", ", typeof(ResourceClientAuthMode).GetEnumNames())}");
                    break;
                default:
                    errorMessages.Add($"Unexpected resource service client authentication mode: {options.ResourceServiceClient.AuthMode}");
                    break;
            }
        }
 
        return errorMessages.Count > 0
            ? ValidateOptionsResult.Fail(errorMessages)
            : ValidateOptionsResult.Success;
    }
}