< Summary

Information
Class: Elsa.Hosting.Management.Services.ConfiguredApplicationInstanceNameProvider
Assembly: Elsa.Hosting.Management
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Hosting.Management/Services/ConfiguredApplicationInstanceNameProvider.cs
Line coverage
100%
Covered lines: 57
Uncovered lines: 0
Coverable lines: 57
Total lines: 131
Line coverage: 100%
Branch coverage
97%
Covered branches: 37
Total branches: 38
Branch coverage: 97.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%66100%
GetName()100%11100%
ResolveConfiguredInstanceName(...)100%66100%
IsValidConfiguredInstanceName(...)100%1414100%
IsAsciiLetterOrDigit(...)91.66%1212100%
ShortenConfiguredInstanceName(...)100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Hosting.Management/Services/ConfiguredApplicationInstanceNameProvider.cs

#LineLine coverage
 1using Elsa.Hosting.Management.Contracts;
 2using Elsa.Hosting.Management.Options;
 3using Microsoft.Extensions.Logging;
 4using Microsoft.Extensions.Options;
 5using System.Security.Cryptography;
 6
 7namespace Elsa.Hosting.Management.Services;
 8
 9/// <summary>
 10/// Resolves the application instance name from <see cref="ApplicationInstanceOptions"/>, allowing a
 11/// stable name to be configured so that per-instance transport entities are reused across restarts
 12/// instead of accumulating. Falls back to a random name when no stable name is configured, which
 13/// preserves the previous default behaviour.
 14/// </summary>
 15/// <remarks>
 16/// Resolution order:
 17/// <list type="number">
 18/// <item><description><see cref="ApplicationInstanceOptions.InstanceName"/> when set.</description></item>
 19/// <item><description>The environment variable named by <see cref="ApplicationInstanceOptions.InstanceNameEnvironmentVa
 20/// <item><description>A randomly generated name (legacy behaviour).</description></item>
 21/// </list>
 22/// </remarks>
 23public class ConfiguredApplicationInstanceNameProvider : IApplicationInstanceNameProvider
 24{
 25    internal const int AzureServiceBusSubscriptionNameMaxLength = 50;
 26    internal const string TriggerChangeTokenSignalEndpointNameSuffix = "-elsa-tct";
 27    private const int ShortenedNameHashLength = 16;
 128    internal static readonly int ConfiguredInstanceNameMaxLength = AzureServiceBusSubscriptionNameMaxLength - TriggerCha
 29
 30    private readonly string _instanceName;
 31
 32    /// <summary>
 33    /// Initializes a new instance of the <see cref="ConfiguredApplicationInstanceNameProvider"/> class.
 34    /// </summary>
 2035    public ConfiguredApplicationInstanceNameProvider(
 2036        IOptions<ApplicationInstanceOptions> options,
 2037        RandomIntIdentityGenerator randomIdentityGenerator,
 2038        ILogger<ConfiguredApplicationInstanceNameProvider> logger)
 39    {
 2040        var value = options.Value;
 41
 2042        if (!string.IsNullOrWhiteSpace(value.InstanceName))
 43        {
 1144            _instanceName = ResolveConfiguredInstanceName(value.InstanceName, $"{nameof(ApplicationInstanceOptions)}.{na
 645            return;
 46        }
 47
 948        if (!string.IsNullOrWhiteSpace(value.InstanceNameEnvironmentVariable))
 49        {
 750            var environmentVariable = value.InstanceNameEnvironmentVariable.Trim();
 751            var fromEnvironment = Environment.GetEnvironmentVariable(environmentVariable);
 52
 753            if (!string.IsNullOrWhiteSpace(fromEnvironment))
 54            {
 555                _instanceName = ResolveConfiguredInstanceName(fromEnvironment, $"environment variable '{environmentVaria
 556                return;
 57            }
 58
 259            logger.LogWarning(
 260                "The configured instance-name environment variable '{EnvironmentVariable}' is not set or empty. Falling 
 261                "A random name causes per-instance transport entities (such as the Azure Service Bus change-token subscr
 262                "which can accumulate until the transport's per-topic limit is reached.",
 263                environmentVariable);
 64        }
 65
 466        _instanceName = randomIdentityGenerator.GenerateId();
 467    }
 68
 69    /// <inheritdoc />
 1570    public string GetName() => _instanceName;
 71
 72    private static string ResolveConfiguredInstanceName(string value, string source, ILogger logger)
 73    {
 1674        var instanceName = value.Trim();
 1675        var isTooLong = instanceName.Length > ConfiguredInstanceNameMaxLength;
 1676        var hasInvalidCharacters = !IsValidConfiguredInstanceName(instanceName);
 77
 1678        if (hasInvalidCharacters)
 79        {
 580            var errors = new List<string>
 581            {
 582                $"The configured application instance name from {source} contains invalid characters. " +
 583                "Use only letters, numbers, periods, hyphens, or underscores, and start and end the value with a letter 
 584            };
 85
 586            if (isTooLong)
 87            {
 188                errors.Add(
 189                    $"The configured application instance name from {source} is {instanceName.Length} characters long, b
 190                    $"The value is used to create per-instance transport entities such as '{instanceName}{TriggerChangeT
 91            }
 92
 593            throw new InvalidOperationException(string.Join(" ", errors));
 94        }
 95
 1196        if (!isTooLong)
 797            return instanceName;
 98
 499        var shortenedName = ShortenConfiguredInstanceName(instanceName);
 100
 4101        logger.LogWarning(
 4102            "The configured application instance name from {Source} is {Length} characters long, exceeding the {MaxLengt
 4103            "Using deterministic shortened instance name '{ShortenedName}' instead.",
 4104            source,
 4105            instanceName.Length,
 4106            ConfiguredInstanceNameMaxLength,
 4107            shortenedName);
 108
 4109        return shortenedName;
 110    }
 111
 112    private static bool IsValidConfiguredInstanceName(string instanceName)
 113    {
 16114        return instanceName.Length > 0
 16115            && IsAsciiLetterOrDigit(instanceName[0])
 16116            && IsAsciiLetterOrDigit(instanceName[^1])
 334117            && instanceName.All(c => IsAsciiLetterOrDigit(c) || c is '.' or '-' or '_');
 118    }
 119
 120    private static bool IsAsciiLetterOrDigit(char value) =>
 349121        value is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or >= '0' and <= '9';
 122
 123    private static string ShortenConfiguredInstanceName(string instanceName)
 124    {
 4125        var hash = Convert.ToHexString(SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(instanceName))).ToLowerInvaria
 4126        var prefixLength = ConfiguredInstanceNameMaxLength - hash.Length - 1;
 4127        var prefix = instanceName[..prefixLength];
 128
 4129        return $"{prefix}-{hash}";
 130    }
 131}