diff --git a/src/Aspire.Hosting/BuiltInDistributedApplicationEventSubscriptionHandlers.cs b/src/Aspire.Hosting/BuiltInDistributedApplicationEventSubscriptionHandlers.cs index b3375df8cde..d86815793db 100644 --- a/src/Aspire.Hosting/BuiltInDistributedApplicationEventSubscriptionHandlers.cs +++ b/src/Aspire.Hosting/BuiltInDistributedApplicationEventSubscriptionHandlers.cs @@ -1,11 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#pragma warning disable ASPIREUSERSECRETS001 + using Aspire; using Aspire.Hosting; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Dcp; +using Aspire.Hosting.Resources; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; internal static class BuiltInDistributedApplicationEventSubscriptionHandlers { @@ -78,4 +82,29 @@ public static Task UpdateContainerRegistryAsync(BeforeStartEvent @event, Distrib return Task.CompletedTask; } + + public static Task WarnPersistentContainersWithoutUserSecrets(BeforeStartEvent beforeStartEvent, CancellationToken _) + { + var userSecretsManager = beforeStartEvent.Services.GetRequiredService(); + + if (userSecretsManager.IsAvailable) + { + return Task.CompletedTask; + } + + var logger = beforeStartEvent.Services.GetRequiredService().CreateLogger("Aspire.Hosting"); + + foreach (var resource in beforeStartEvent.Model.Resources) + { + if (resource.GetContainerLifetimeType() == ContainerLifetime.Persistent) + { + if (logger.IsEnabled(LogLevel.Warning)) + { + logger.LogWarning(MessageStrings.PersistentContainerWithoutUserSecrets, resource.Name); + } + } + } + + return Task.CompletedTask; + } } diff --git a/src/Aspire.Hosting/DistributedApplicationBuilder.cs b/src/Aspire.Hosting/DistributedApplicationBuilder.cs index 6e1b56b6b90..94fbc88e410 100644 --- a/src/Aspire.Hosting/DistributedApplicationBuilder.cs +++ b/src/Aspire.Hosting/DistributedApplicationBuilder.cs @@ -496,6 +496,7 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options) _innerBuilder.Services.AddSingleton(); Eventing.Subscribe(BuiltInDistributedApplicationEventSubscriptionHandlers.InitializeDcpAnnotations); + Eventing.Subscribe(BuiltInDistributedApplicationEventSubscriptionHandlers.WarnPersistentContainersWithoutUserSecrets); } // Publishing support diff --git a/src/Aspire.Hosting/Resources/MessageStrings.Designer.cs b/src/Aspire.Hosting/Resources/MessageStrings.Designer.cs index 3d907c91785..7cf2f852b13 100644 --- a/src/Aspire.Hosting/Resources/MessageStrings.Designer.cs +++ b/src/Aspire.Hosting/Resources/MessageStrings.Designer.cs @@ -159,6 +159,15 @@ internal static string RequiredCommandValidationFailedWithLink { } } + /// + /// Looks up a localized string similar to Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets.. + /// + internal static string PersistentContainerWithoutUserSecrets { + get { + return ResourceManager.GetString("PersistentContainerWithoutUserSecrets", resourceCulture); + } + } + /// /// Looks up a localized string similar to Resource '{0}' may fail to start: {1}. /// diff --git a/src/Aspire.Hosting/Resources/MessageStrings.resx b/src/Aspire.Hosting/Resources/MessageStrings.resx index a5789a05605..52c49da3d72 100644 --- a/src/Aspire.Hosting/Resources/MessageStrings.resx +++ b/src/Aspire.Hosting/Resources/MessageStrings.resx @@ -150,6 +150,9 @@ Command '{0}' validation failed: {1}. For installation instructions, see: {2} + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' may fail to start: {1} diff --git a/src/Aspire.Hosting/Resources/xlf/MessageStrings.cs.xlf b/src/Aspire.Hosting/Resources/xlf/MessageStrings.cs.xlf index 4deec3220e3..a3fdc3e6823 100644 --- a/src/Aspire.Hosting/Resources/xlf/MessageStrings.cs.xlf +++ b/src/Aspire.Hosting/Resources/xlf/MessageStrings.cs.xlf @@ -37,6 +37,11 @@ Chybějící příkaz + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + + Required command '{0}' was not found on PATH or at the specified location. Požadovaný příkaz {0} nebyl nalezen v cestě PATH nebo v zadaném umístění. diff --git a/src/Aspire.Hosting/Resources/xlf/MessageStrings.de.xlf b/src/Aspire.Hosting/Resources/xlf/MessageStrings.de.xlf index 76b6524613b..cf2fadc9712 100644 --- a/src/Aspire.Hosting/Resources/xlf/MessageStrings.de.xlf +++ b/src/Aspire.Hosting/Resources/xlf/MessageStrings.de.xlf @@ -37,6 +37,11 @@ Fehlender Befehl + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + + Required command '{0}' was not found on PATH or at the specified location. Der erforderliche Befehl „{0}“ wurde weder im PATH noch an dem angegebenen Speicherort gefunden. diff --git a/src/Aspire.Hosting/Resources/xlf/MessageStrings.es.xlf b/src/Aspire.Hosting/Resources/xlf/MessageStrings.es.xlf index e2efab7237e..b136e3bedd8 100644 --- a/src/Aspire.Hosting/Resources/xlf/MessageStrings.es.xlf +++ b/src/Aspire.Hosting/Resources/xlf/MessageStrings.es.xlf @@ -37,6 +37,11 @@ Falta un comando + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + + Required command '{0}' was not found on PATH or at the specified location. No se encontró el comando necesario "{0}" en PATH o en la ubicación especificada. diff --git a/src/Aspire.Hosting/Resources/xlf/MessageStrings.fr.xlf b/src/Aspire.Hosting/Resources/xlf/MessageStrings.fr.xlf index ef6eb00bf54..da5c1596622 100644 --- a/src/Aspire.Hosting/Resources/xlf/MessageStrings.fr.xlf +++ b/src/Aspire.Hosting/Resources/xlf/MessageStrings.fr.xlf @@ -37,6 +37,11 @@ Commande manquante + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + + Required command '{0}' was not found on PATH or at the specified location. La commande requise « {0} » est introuvable dans le CHEMIN ou à l’emplacement spécifié. diff --git a/src/Aspire.Hosting/Resources/xlf/MessageStrings.it.xlf b/src/Aspire.Hosting/Resources/xlf/MessageStrings.it.xlf index e702b524c82..c2c77371ec2 100644 --- a/src/Aspire.Hosting/Resources/xlf/MessageStrings.it.xlf +++ b/src/Aspire.Hosting/Resources/xlf/MessageStrings.it.xlf @@ -37,6 +37,11 @@ Comando mancante + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + + Required command '{0}' was not found on PATH or at the specified location. Non è possibile trovare il comando richiesto '{0}' in PATH o nel percorso specificato. diff --git a/src/Aspire.Hosting/Resources/xlf/MessageStrings.ja.xlf b/src/Aspire.Hosting/Resources/xlf/MessageStrings.ja.xlf index 1acc9dcdc8f..c35685a1571 100644 --- a/src/Aspire.Hosting/Resources/xlf/MessageStrings.ja.xlf +++ b/src/Aspire.Hosting/Resources/xlf/MessageStrings.ja.xlf @@ -37,6 +37,11 @@ コマンドがありません + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + + Required command '{0}' was not found on PATH or at the specified location. 必要なコマンド '{0}' が PATH または指定された場所に見つかりませんでした。 diff --git a/src/Aspire.Hosting/Resources/xlf/MessageStrings.ko.xlf b/src/Aspire.Hosting/Resources/xlf/MessageStrings.ko.xlf index a73bb026ef0..026deb82a38 100644 --- a/src/Aspire.Hosting/Resources/xlf/MessageStrings.ko.xlf +++ b/src/Aspire.Hosting/Resources/xlf/MessageStrings.ko.xlf @@ -37,6 +37,11 @@ 명령이 없음 + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + + Required command '{0}' was not found on PATH or at the specified location. PATH 또는 지정된 위치에서 필수 명령 '{0}'을(를) 찾을 수 없습니다. diff --git a/src/Aspire.Hosting/Resources/xlf/MessageStrings.pl.xlf b/src/Aspire.Hosting/Resources/xlf/MessageStrings.pl.xlf index f2469fa3c3f..195db7f7a37 100644 --- a/src/Aspire.Hosting/Resources/xlf/MessageStrings.pl.xlf +++ b/src/Aspire.Hosting/Resources/xlf/MessageStrings.pl.xlf @@ -37,6 +37,11 @@ Brakujące polecenie + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + + Required command '{0}' was not found on PATH or at the specified location. Nie znaleziono wymaganego polecenia „{0}” w ścieżce PATH lub w podanej lokalizacji. diff --git a/src/Aspire.Hosting/Resources/xlf/MessageStrings.pt-BR.xlf b/src/Aspire.Hosting/Resources/xlf/MessageStrings.pt-BR.xlf index 784af7d1dc6..4b5107c4ea5 100644 --- a/src/Aspire.Hosting/Resources/xlf/MessageStrings.pt-BR.xlf +++ b/src/Aspire.Hosting/Resources/xlf/MessageStrings.pt-BR.xlf @@ -37,6 +37,11 @@ Comando ausente + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + + Required command '{0}' was not found on PATH or at the specified location. O comando obrigatório ''{0}'' não foi encontrado em PATH ou no local especificado. diff --git a/src/Aspire.Hosting/Resources/xlf/MessageStrings.ru.xlf b/src/Aspire.Hosting/Resources/xlf/MessageStrings.ru.xlf index 7c7793a4d5e..af620a54968 100644 --- a/src/Aspire.Hosting/Resources/xlf/MessageStrings.ru.xlf +++ b/src/Aspire.Hosting/Resources/xlf/MessageStrings.ru.xlf @@ -37,6 +37,11 @@ Отсутствует команда + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + + Required command '{0}' was not found on PATH or at the specified location. Требуемая команда "{0}" не найдена в PATH или в указанном расположении. diff --git a/src/Aspire.Hosting/Resources/xlf/MessageStrings.tr.xlf b/src/Aspire.Hosting/Resources/xlf/MessageStrings.tr.xlf index 206fcad9692..c772590f9db 100644 --- a/src/Aspire.Hosting/Resources/xlf/MessageStrings.tr.xlf +++ b/src/Aspire.Hosting/Resources/xlf/MessageStrings.tr.xlf @@ -37,6 +37,11 @@ Eksik komut + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + + Required command '{0}' was not found on PATH or at the specified location. Gerekli '{0}' komutu PATH'de veya belirtilen konumda bulunamadı. diff --git a/src/Aspire.Hosting/Resources/xlf/MessageStrings.zh-Hans.xlf b/src/Aspire.Hosting/Resources/xlf/MessageStrings.zh-Hans.xlf index 717dce1422b..eaf4a0df442 100644 --- a/src/Aspire.Hosting/Resources/xlf/MessageStrings.zh-Hans.xlf +++ b/src/Aspire.Hosting/Resources/xlf/MessageStrings.zh-Hans.xlf @@ -37,6 +37,11 @@ 缺少命令 + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + + Required command '{0}' was not found on PATH or at the specified location. 在 PATH 或指定位置找不到所需命令 "{0}"。 diff --git a/src/Aspire.Hosting/Resources/xlf/MessageStrings.zh-Hant.xlf b/src/Aspire.Hosting/Resources/xlf/MessageStrings.zh-Hant.xlf index 83e7465d816..85a5e15960b 100644 --- a/src/Aspire.Hosting/Resources/xlf/MessageStrings.zh-Hant.xlf +++ b/src/Aspire.Hosting/Resources/xlf/MessageStrings.zh-Hant.xlf @@ -37,6 +37,11 @@ 遺漏命令 + + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + Resource '{0}' has a persistent lifetime but the AppHost project does not have user secrets configured. Generated parameter values (such as passwords) may change on each restart, causing persistent containers to be recreated. Run 'dotnet user-secrets init' in the AppHost directory to configure user secrets. + + Required command '{0}' was not found on PATH or at the specified location. 在 PATH 或指定的位置找不到必要的命令 '{0}'。 diff --git a/tests/Aspire.Hosting.Tests/PersistentContainerWarningTests.cs b/tests/Aspire.Hosting.Tests/PersistentContainerWarningTests.cs new file mode 100644 index 00000000000..33e1e5df28e --- /dev/null +++ b/tests/Aspire.Hosting.Tests/PersistentContainerWarningTests.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma warning disable ASPIREUSERSECRETS001 + +using Aspire.Hosting.UserSecrets; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; + +namespace Aspire.Hosting.Tests; + +public class PersistentContainerWarningTests +{ + [Fact] + public async Task PersistentContainerWithoutUserSecrets_LogsWarning() + { + var testSink = new TestSink(); + + var services = new ServiceCollection(); + services.AddSingleton(NoopUserSecretsManager.Instance); + services.AddLogging(logging => logging.AddProvider(new TestLoggerProvider(testSink))); + var serviceProvider = services.BuildServiceProvider(); + + var resources = new ResourceCollection(); + var container = new ContainerResource("my-container"); + container.Annotations.Add(new ContainerLifetimeAnnotation { Lifetime = ContainerLifetime.Persistent }); + resources.Add(container); + + var model = new DistributedApplicationModel(resources); + var beforeStartEvent = new BeforeStartEvent(serviceProvider, model); + + await BuiltInDistributedApplicationEventSubscriptionHandlers.WarnPersistentContainersWithoutUserSecrets(beforeStartEvent, CancellationToken.None); + + Assert.Contains(testSink.Writes, w => w.LogLevel == LogLevel.Warning && w.Message?.Contains("my-container") == true); + } + + [Fact] + public async Task PersistentContainerWithUserSecrets_DoesNotLogWarning() + { + var testSink = new TestSink(); + + var services = new ServiceCollection(); + services.AddSingleton(new FakeAvailableUserSecretsManager()); + services.AddLogging(logging => logging.AddProvider(new TestLoggerProvider(testSink))); + var serviceProvider = services.BuildServiceProvider(); + + var resources = new ResourceCollection(); + var container = new ContainerResource("my-container"); + container.Annotations.Add(new ContainerLifetimeAnnotation { Lifetime = ContainerLifetime.Persistent }); + resources.Add(container); + + var model = new DistributedApplicationModel(resources); + var beforeStartEvent = new BeforeStartEvent(serviceProvider, model); + + await BuiltInDistributedApplicationEventSubscriptionHandlers.WarnPersistentContainersWithoutUserSecrets(beforeStartEvent, CancellationToken.None); + + Assert.DoesNotContain(testSink.Writes, w => w.LogLevel == LogLevel.Warning); + } + + [Fact] + public async Task SessionContainerWithoutUserSecrets_DoesNotLogWarning() + { + var testSink = new TestSink(); + + var services = new ServiceCollection(); + services.AddSingleton(NoopUserSecretsManager.Instance); + services.AddLogging(logging => logging.AddProvider(new TestLoggerProvider(testSink))); + var serviceProvider = services.BuildServiceProvider(); + + var resources = new ResourceCollection(); + resources.Add(new ContainerResource("my-container")); + + var model = new DistributedApplicationModel(resources); + var beforeStartEvent = new BeforeStartEvent(serviceProvider, model); + + await BuiltInDistributedApplicationEventSubscriptionHandlers.WarnPersistentContainersWithoutUserSecrets(beforeStartEvent, CancellationToken.None); + + Assert.DoesNotContain(testSink.Writes, w => w.LogLevel == LogLevel.Warning); + } + + private sealed class FakeAvailableUserSecretsManager : IUserSecretsManager + { + public bool IsAvailable => true; + public string FilePath => string.Empty; + public bool TrySetSecret(string name, string value) => true; + public void GetOrSetSecret(Microsoft.Extensions.Configuration.IConfigurationManager configuration, string name, Func valueGenerator) { } + public Task SaveStateAsync(System.Text.Json.Nodes.JsonObject state, CancellationToken cancellationToken = default) => Task.CompletedTask; + } +}