From 8457a0e01e976139a1e05ab99f49b8a59d74effd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 4 Sep 2025 15:05:53 +0000 Subject: [PATCH 01/81] Bump Steeltoe version from 4.0.0 to 4.0.1. > [!TIP] > Close and reopen this pull request to run status checks. --- shared-package.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-package.props b/shared-package.props index 721a23607d..e3a3c7141d 100644 --- a/shared-package.props +++ b/shared-package.props @@ -12,7 +12,7 @@ - 4.0.0 + 4.0.1 pre Broadcom PackageIcon.png From 218cfb491241c77d3176d95639636bd43af2e772 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Fri, 5 Sep 2025 14:40:56 +0200 Subject: [PATCH 02/81] Remove placeholder packages that were added to help migrate from 3.x (#1590) --- .../Steeltoe.Bootstrap.Autoconfig.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Bootstrap.Autoconfig.csproj | 15 - ...eeltoe.CircuitBreaker.Abstractions.targets | 10 - .../PackageReadme.md | 17 - ...teeltoe.CircuitBreaker.Abstractions.csproj | 15 - ...tBreaker.Hystrix.MetricsEventsCore.targets | 10 - .../PackageReadme.md | 17 - ...itBreaker.Hystrix.MetricsEventsCore.csproj | 15 - ...tBreaker.Hystrix.MetricsStreamCore.targets | 10 - .../PackageReadme.md | 17 - ...itBreaker.Hystrix.MetricsStreamCore.csproj | 15 - ...teeltoe.CircuitBreaker.HystrixBase.targets | 10 - .../PackageReadme.md | 17 - ...Steeltoe.CircuitBreaker.HystrixBase.csproj | 15 - ...teeltoe.CircuitBreaker.HystrixCore.targets | 10 - .../PackageReadme.md | 17 - ...Steeltoe.CircuitBreaker.HystrixCore.csproj | 15 - .../Steeltoe.Common.Abstractions.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Common.Abstractions.csproj | 15 - .../Build/Steeltoe.Common.Expression.targets | 9 - .../PackageReadme.md | 17 - .../Steeltoe.Common.Expression.csproj | 15 - .../Build/Steeltoe.Common.Kubernetes.targets | 9 - .../PackageReadme.md | 17 - .../Steeltoe.Common.Kubernetes.csproj | 15 - .../Build/Steeltoe.Common.Retry.targets | 9 - .../Steeltoe.Common.Retry/PackageReadme.md | 17 - .../Steeltoe.Common.Retry.csproj | 15 - .../Build/Steeltoe.Common.Security.targets | 10 - .../Steeltoe.Common.Security/PackageReadme.md | 17 - .../Steeltoe.Common.Security.csproj | 15 - .../Build/Steeltoe.Common.Utils.targets | 9 - .../Steeltoe.Common.Utils/PackageReadme.md | 17 - .../Steeltoe.Common.Utils.csproj | 15 - .../Steeltoe.Connector.Abstractions.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Connector.Abstractions.csproj | 15 - .../Steeltoe.Connector.CloudFoundry.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Connector.CloudFoundry.csproj | 15 - .../Steeltoe.Connector.ConnectorBase.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Connector.ConnectorBase.csproj | 15 - .../Steeltoe.Connector.ConnectorCore.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Connector.ConnectorCore.csproj | 15 - .../Build/Steeltoe.Connector.EF6Core.targets | 9 - .../PackageReadme.md | 17 - .../Steeltoe.Connector.EF6Core.csproj | 15 - .../Build/Steeltoe.Connector.EFCore.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Connector.EFCore.csproj | 15 - .../Steeltoe.Discovery.Abstractions.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Discovery.Abstractions.csproj | 15 - .../Steeltoe.Discovery.ClientBase.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Discovery.ClientBase.csproj | 15 - .../Steeltoe.Discovery.ClientCore.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Discovery.ClientCore.csproj | 15 - .../Steeltoe.Discovery.Kubernetes.targets | 9 - .../PackageReadme.md | 17 - .../Steeltoe.Discovery.Kubernetes.csproj | 15 - ...ensions.Configuration.Abstractions.targets | 10 - .../PackageReadme.md | 17 - ...tensions.Configuration.Abstractions.csproj | 15 - ...ons.Configuration.CloudFoundryBase.targets | 10 - .../PackageReadme.md | 17 - ...ions.Configuration.CloudFoundryBase.csproj | 15 - ...ons.Configuration.CloudFoundryCore.targets | 10 - .../PackageReadme.md | 17 - ...ions.Configuration.CloudFoundryCore.csproj | 15 - ...ons.Configuration.ConfigServerBase.targets | 10 - .../PackageReadme.md | 17 - ...ions.Configuration.ConfigServerBase.csproj | 15 - ...ons.Configuration.ConfigServerCore.targets | 10 - .../PackageReadme.md | 17 - ...ions.Configuration.ConfigServerCore.csproj | 15 - ...guration.Kubernetes.ServiceBinding.targets | 10 - .../PackageReadme.md | 17 - ...iguration.Kubernetes.ServiceBinding.csproj | 15 - ...sions.Configuration.KubernetesBase.targets | 10 - .../PackageReadme.md | 17 - ...nsions.Configuration.KubernetesBase.csproj | 15 - ...sions.Configuration.KubernetesCore.targets | 10 - .../PackageReadme.md | 17 - ...nsions.Configuration.KubernetesCore.csproj | 15 - ...ions.Configuration.PlaceholderBase.targets | 10 - .../PackageReadme.md | 17 - ...sions.Configuration.PlaceholderBase.csproj | 15 - ...ions.Configuration.PlaceholderCore.targets | 10 - .../PackageReadme.md | 17 - ...sions.Configuration.PlaceholderCore.csproj | 15 - ...ions.Configuration.RandomValueBase.targets | 10 - .../PackageReadme.md | 17 - ...sions.Configuration.RandomValueBase.csproj | 15 - ...sions.Configuration.SpringBootBase.targets | 10 - .../PackageReadme.md | 17 - ...nsions.Configuration.SpringBootBase.csproj | 15 - ...sions.Configuration.SpringBootCore.targets | 10 - .../PackageReadme.md | 17 - ...nsions.Configuration.SpringBootCore.csproj | 15 - ...oe.Extensions.Logging.Abstractions.targets | 10 - .../PackageReadme.md | 17 - ...toe.Extensions.Logging.Abstractions.csproj | 15 - ...e.Extensions.Logging.DynamicLogger.targets | 10 - .../PackageReadme.md | 17 - ...oe.Extensions.Logging.DynamicLogger.csproj | 15 - ...ensions.Logging.DynamicSerilogBase.targets | 10 - .../PackageReadme.md | 17 - ...tensions.Logging.DynamicSerilogBase.csproj | 15 - ...ensions.Logging.DynamicSerilogCore.targets | 10 - .../PackageReadme.md | 17 - ...tensions.Logging.DynamicSerilogCore.csproj | 15 - .../Steeltoe.Integration.Abstractions.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Integration.Abstractions.csproj | 15 - ...eeltoe.Integration.IntegrationBase.targets | 10 - .../PackageReadme.md | 17 - ...teeltoe.Integration.IntegrationBase.csproj | 15 - .../Steeltoe.Integration.RabbitMQ.targets | 9 - .../PackageReadme.md | 17 - .../Steeltoe.Integration.RabbitMQ.csproj | 15 - ...eeltoe.Management.CloudFoundryCore.targets | 10 - .../PackageReadme.md | 17 - ...teeltoe.Management.CloudFoundryCore.csproj | 15 - .../Steeltoe.Management.Diagnostics.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Management.Diagnostics.csproj | 15 - .../Steeltoe.Management.EndpointBase.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Management.EndpointBase.csproj | 15 - .../Steeltoe.Management.EndpointCore.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Management.EndpointCore.csproj | 15 - ...Steeltoe.Management.KubernetesCore.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Management.KubernetesCore.csproj | 15 - ...eltoe.Management.OpenTelemetryBase.targets | 10 - .../PackageReadme.md | 17 - ...eeltoe.Management.OpenTelemetryBase.csproj | 15 - .../Steeltoe.Management.TaskCore.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Management.TaskCore.csproj | 15 - .../Steeltoe.Management.TracingBase.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Management.TracingBase.csproj | 15 - .../Steeltoe.Management.TracingCore.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Management.TracingCore.csproj | 15 - .../Steeltoe.Messaging.Abstractions.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Messaging.Abstractions.csproj | 15 - .../Steeltoe.Messaging.MessagingBase.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Messaging.MessagingBase.csproj | 15 - .../Build/Steeltoe.Messaging.RabbitMQ.targets | 9 - .../PackageReadme.md | 17 - .../Steeltoe.Messaging.RabbitMQ.csproj | 15 - ...ty.Authentication.CloudFoundryBase.targets | 10 - .../PackageReadme.md | 17 - ...ity.Authentication.CloudFoundryBase.csproj | 15 - ...ty.Authentication.CloudFoundryCore.targets | 10 - .../PackageReadme.md | 17 - ...ity.Authentication.CloudFoundryCore.csproj | 15 - ...e.Security.Authentication.MtlsCore.targets | 10 - .../PackageReadme.md | 17 - ...oe.Security.Authentication.MtlsCore.csproj | 15 - ...ecurity.DataProtection.CredHubBase.targets | 10 - .../PackageReadme.md | 17 - ...Security.DataProtection.CredHubBase.csproj | 15 - ...ecurity.DataProtection.CredHubCore.targets | 10 - .../PackageReadme.md | 17 - ...Security.DataProtection.CredHubCore.csproj | 15 - ....Security.DataProtection.RedisCore.targets | 10 - .../PackageReadme.md | 17 - ...e.Security.DataProtection.RedisCore.csproj | 15 - .../Steeltoe.Stream.Abstractions.targets | 9 - .../PackageReadme.md | 17 - .../Steeltoe.Stream.Abstractions.csproj | 15 - .../Steeltoe.Stream.Binder.RabbitMQ.targets | 10 - .../PackageReadme.md | 17 - .../Steeltoe.Stream.Binder.RabbitMQ.csproj | 15 - .../Build/Steeltoe.Stream.StreamBase.targets | 9 - .../PackageReadme.md | 17 - .../Steeltoe.Stream.StreamBase.csproj | 15 - src/Steeltoe.All.sln | 380 ------------------ 190 files changed, 3016 deletions(-) delete mode 100644 src/Obsolete/Steeltoe.Bootstrap.Autoconfig/Build/Steeltoe.Bootstrap.Autoconfig.targets delete mode 100644 src/Obsolete/Steeltoe.Bootstrap.Autoconfig/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Bootstrap.Autoconfig/Steeltoe.Bootstrap.Autoconfig.csproj delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.Abstractions/Build/Steeltoe.CircuitBreaker.Abstractions.targets delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.Abstractions/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.Abstractions/Steeltoe.CircuitBreaker.Abstractions.csproj delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore/Build/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore.targets delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore.csproj delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore/Build/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore.targets delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore.csproj delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.HystrixBase/Build/Steeltoe.CircuitBreaker.HystrixBase.targets delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.HystrixBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.HystrixBase/Steeltoe.CircuitBreaker.HystrixBase.csproj delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.HystrixCore/Build/Steeltoe.CircuitBreaker.HystrixCore.targets delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.HystrixCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.CircuitBreaker.HystrixCore/Steeltoe.CircuitBreaker.HystrixCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Common.Abstractions/Build/Steeltoe.Common.Abstractions.targets delete mode 100644 src/Obsolete/Steeltoe.Common.Abstractions/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Common.Abstractions/Steeltoe.Common.Abstractions.csproj delete mode 100644 src/Obsolete/Steeltoe.Common.Expression/Build/Steeltoe.Common.Expression.targets delete mode 100644 src/Obsolete/Steeltoe.Common.Expression/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Common.Expression/Steeltoe.Common.Expression.csproj delete mode 100644 src/Obsolete/Steeltoe.Common.Kubernetes/Build/Steeltoe.Common.Kubernetes.targets delete mode 100644 src/Obsolete/Steeltoe.Common.Kubernetes/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Common.Kubernetes/Steeltoe.Common.Kubernetes.csproj delete mode 100644 src/Obsolete/Steeltoe.Common.Retry/Build/Steeltoe.Common.Retry.targets delete mode 100644 src/Obsolete/Steeltoe.Common.Retry/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Common.Retry/Steeltoe.Common.Retry.csproj delete mode 100644 src/Obsolete/Steeltoe.Common.Security/Build/Steeltoe.Common.Security.targets delete mode 100644 src/Obsolete/Steeltoe.Common.Security/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Common.Security/Steeltoe.Common.Security.csproj delete mode 100644 src/Obsolete/Steeltoe.Common.Utils/Build/Steeltoe.Common.Utils.targets delete mode 100644 src/Obsolete/Steeltoe.Common.Utils/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Common.Utils/Steeltoe.Common.Utils.csproj delete mode 100644 src/Obsolete/Steeltoe.Connector.Abstractions/Build/Steeltoe.Connector.Abstractions.targets delete mode 100644 src/Obsolete/Steeltoe.Connector.Abstractions/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Connector.Abstractions/Steeltoe.Connector.Abstractions.csproj delete mode 100644 src/Obsolete/Steeltoe.Connector.CloudFoundry/Build/Steeltoe.Connector.CloudFoundry.targets delete mode 100644 src/Obsolete/Steeltoe.Connector.CloudFoundry/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Connector.CloudFoundry/Steeltoe.Connector.CloudFoundry.csproj delete mode 100644 src/Obsolete/Steeltoe.Connector.ConnectorBase/Build/Steeltoe.Connector.ConnectorBase.targets delete mode 100644 src/Obsolete/Steeltoe.Connector.ConnectorBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Connector.ConnectorBase/Steeltoe.Connector.ConnectorBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Connector.ConnectorCore/Build/Steeltoe.Connector.ConnectorCore.targets delete mode 100644 src/Obsolete/Steeltoe.Connector.ConnectorCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Connector.ConnectorCore/Steeltoe.Connector.ConnectorCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Connector.EF6Core/Build/Steeltoe.Connector.EF6Core.targets delete mode 100644 src/Obsolete/Steeltoe.Connector.EF6Core/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Connector.EF6Core/Steeltoe.Connector.EF6Core.csproj delete mode 100644 src/Obsolete/Steeltoe.Connector.EFCore/Build/Steeltoe.Connector.EFCore.targets delete mode 100644 src/Obsolete/Steeltoe.Connector.EFCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Connector.EFCore/Steeltoe.Connector.EFCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Discovery.Abstractions/Build/Steeltoe.Discovery.Abstractions.targets delete mode 100644 src/Obsolete/Steeltoe.Discovery.Abstractions/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Discovery.Abstractions/Steeltoe.Discovery.Abstractions.csproj delete mode 100644 src/Obsolete/Steeltoe.Discovery.ClientBase/Build/Steeltoe.Discovery.ClientBase.targets delete mode 100644 src/Obsolete/Steeltoe.Discovery.ClientBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Discovery.ClientBase/Steeltoe.Discovery.ClientBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Discovery.ClientCore/Build/Steeltoe.Discovery.ClientCore.targets delete mode 100644 src/Obsolete/Steeltoe.Discovery.ClientCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Discovery.ClientCore/Steeltoe.Discovery.ClientCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Discovery.Kubernetes/Build/Steeltoe.Discovery.Kubernetes.targets delete mode 100644 src/Obsolete/Steeltoe.Discovery.Kubernetes/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Discovery.Kubernetes/Steeltoe.Discovery.Kubernetes.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.Abstractions/Build/Steeltoe.Extensions.Configuration.Abstractions.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.Abstractions/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.Abstractions/Steeltoe.Extensions.Configuration.Abstractions.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryBase/Build/Steeltoe.Extensions.Configuration.CloudFoundryBase.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryBase/Steeltoe.Extensions.Configuration.CloudFoundryBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryCore/Build/Steeltoe.Extensions.Configuration.CloudFoundryCore.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryCore/Steeltoe.Extensions.Configuration.CloudFoundryCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerBase/Build/Steeltoe.Extensions.Configuration.ConfigServerBase.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerBase/Steeltoe.Extensions.Configuration.ConfigServerBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerCore/Build/Steeltoe.Extensions.Configuration.ConfigServerCore.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerCore/Steeltoe.Extensions.Configuration.ConfigServerCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding/Build/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesBase/Build/Steeltoe.Extensions.Configuration.KubernetesBase.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesBase/Steeltoe.Extensions.Configuration.KubernetesBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesCore/Build/Steeltoe.Extensions.Configuration.KubernetesCore.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesCore/Steeltoe.Extensions.Configuration.KubernetesCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderBase/Build/Steeltoe.Extensions.Configuration.PlaceholderBase.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderBase/Steeltoe.Extensions.Configuration.PlaceholderBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderCore/Build/Steeltoe.Extensions.Configuration.PlaceholderCore.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderCore/Steeltoe.Extensions.Configuration.PlaceholderCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.RandomValueBase/Build/Steeltoe.Extensions.Configuration.RandomValueBase.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.RandomValueBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.RandomValueBase/Steeltoe.Extensions.Configuration.RandomValueBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootBase/Build/Steeltoe.Extensions.Configuration.SpringBootBase.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootBase/Steeltoe.Extensions.Configuration.SpringBootBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootCore/Build/Steeltoe.Extensions.Configuration.SpringBootCore.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootCore/Steeltoe.Extensions.Configuration.SpringBootCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Logging.Abstractions/Build/Steeltoe.Extensions.Logging.Abstractions.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Logging.Abstractions/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Logging.Abstractions/Steeltoe.Extensions.Logging.Abstractions.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Logging.DynamicLogger/Build/Steeltoe.Extensions.Logging.DynamicLogger.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Logging.DynamicLogger/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Logging.DynamicLogger/Steeltoe.Extensions.Logging.DynamicLogger.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogBase/Build/Steeltoe.Extensions.Logging.DynamicSerilogBase.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogBase/Steeltoe.Extensions.Logging.DynamicSerilogBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogCore/Build/Steeltoe.Extensions.Logging.DynamicSerilogCore.targets delete mode 100644 src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogCore/Steeltoe.Extensions.Logging.DynamicSerilogCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Integration.Abstractions/Build/Steeltoe.Integration.Abstractions.targets delete mode 100644 src/Obsolete/Steeltoe.Integration.Abstractions/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Integration.Abstractions/Steeltoe.Integration.Abstractions.csproj delete mode 100644 src/Obsolete/Steeltoe.Integration.IntegrationBase/Build/Steeltoe.Integration.IntegrationBase.targets delete mode 100644 src/Obsolete/Steeltoe.Integration.IntegrationBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Integration.IntegrationBase/Steeltoe.Integration.IntegrationBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Integration.RabbitMQ/Build/Steeltoe.Integration.RabbitMQ.targets delete mode 100644 src/Obsolete/Steeltoe.Integration.RabbitMQ/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Integration.RabbitMQ/Steeltoe.Integration.RabbitMQ.csproj delete mode 100644 src/Obsolete/Steeltoe.Management.CloudFoundryCore/Build/Steeltoe.Management.CloudFoundryCore.targets delete mode 100644 src/Obsolete/Steeltoe.Management.CloudFoundryCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Management.CloudFoundryCore/Steeltoe.Management.CloudFoundryCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Management.Diagnostics/Build/Steeltoe.Management.Diagnostics.targets delete mode 100644 src/Obsolete/Steeltoe.Management.Diagnostics/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Management.Diagnostics/Steeltoe.Management.Diagnostics.csproj delete mode 100644 src/Obsolete/Steeltoe.Management.EndpointBase/Build/Steeltoe.Management.EndpointBase.targets delete mode 100644 src/Obsolete/Steeltoe.Management.EndpointBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Management.EndpointBase/Steeltoe.Management.EndpointBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Management.EndpointCore/Build/Steeltoe.Management.EndpointCore.targets delete mode 100644 src/Obsolete/Steeltoe.Management.EndpointCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Management.EndpointCore/Steeltoe.Management.EndpointCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Management.KubernetesCore/Build/Steeltoe.Management.KubernetesCore.targets delete mode 100644 src/Obsolete/Steeltoe.Management.KubernetesCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Management.KubernetesCore/Steeltoe.Management.KubernetesCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Management.OpenTelemetryBase/Build/Steeltoe.Management.OpenTelemetryBase.targets delete mode 100644 src/Obsolete/Steeltoe.Management.OpenTelemetryBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Management.OpenTelemetryBase/Steeltoe.Management.OpenTelemetryBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Management.TaskCore/Build/Steeltoe.Management.TaskCore.targets delete mode 100644 src/Obsolete/Steeltoe.Management.TaskCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Management.TaskCore/Steeltoe.Management.TaskCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Management.TracingBase/Build/Steeltoe.Management.TracingBase.targets delete mode 100644 src/Obsolete/Steeltoe.Management.TracingBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Management.TracingBase/Steeltoe.Management.TracingBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Management.TracingCore/Build/Steeltoe.Management.TracingCore.targets delete mode 100644 src/Obsolete/Steeltoe.Management.TracingCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Management.TracingCore/Steeltoe.Management.TracingCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Messaging.Abstractions/Build/Steeltoe.Messaging.Abstractions.targets delete mode 100644 src/Obsolete/Steeltoe.Messaging.Abstractions/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Messaging.Abstractions/Steeltoe.Messaging.Abstractions.csproj delete mode 100644 src/Obsolete/Steeltoe.Messaging.MessagingBase/Build/Steeltoe.Messaging.MessagingBase.targets delete mode 100644 src/Obsolete/Steeltoe.Messaging.MessagingBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Messaging.MessagingBase/Steeltoe.Messaging.MessagingBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Messaging.RabbitMQ/Build/Steeltoe.Messaging.RabbitMQ.targets delete mode 100644 src/Obsolete/Steeltoe.Messaging.RabbitMQ/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Messaging.RabbitMQ/Steeltoe.Messaging.RabbitMQ.csproj delete mode 100644 src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryBase/Build/Steeltoe.Security.Authentication.CloudFoundryBase.targets delete mode 100644 src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryBase/Steeltoe.Security.Authentication.CloudFoundryBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryCore/Build/Steeltoe.Security.Authentication.CloudFoundryCore.targets delete mode 100644 src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryCore/Steeltoe.Security.Authentication.CloudFoundryCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Security.Authentication.MtlsCore/Build/Steeltoe.Security.Authentication.MtlsCore.targets delete mode 100644 src/Obsolete/Steeltoe.Security.Authentication.MtlsCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Security.Authentication.MtlsCore/Steeltoe.Security.Authentication.MtlsCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Security.DataProtection.CredHubBase/Build/Steeltoe.Security.DataProtection.CredHubBase.targets delete mode 100644 src/Obsolete/Steeltoe.Security.DataProtection.CredHubBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Security.DataProtection.CredHubBase/Steeltoe.Security.DataProtection.CredHubBase.csproj delete mode 100644 src/Obsolete/Steeltoe.Security.DataProtection.CredHubCore/Build/Steeltoe.Security.DataProtection.CredHubCore.targets delete mode 100644 src/Obsolete/Steeltoe.Security.DataProtection.CredHubCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Security.DataProtection.CredHubCore/Steeltoe.Security.DataProtection.CredHubCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Security.DataProtection.RedisCore/Build/Steeltoe.Security.DataProtection.RedisCore.targets delete mode 100644 src/Obsolete/Steeltoe.Security.DataProtection.RedisCore/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Security.DataProtection.RedisCore/Steeltoe.Security.DataProtection.RedisCore.csproj delete mode 100644 src/Obsolete/Steeltoe.Stream.Abstractions/Build/Steeltoe.Stream.Abstractions.targets delete mode 100644 src/Obsolete/Steeltoe.Stream.Abstractions/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Stream.Abstractions/Steeltoe.Stream.Abstractions.csproj delete mode 100644 src/Obsolete/Steeltoe.Stream.Binder.RabbitMQ/Build/Steeltoe.Stream.Binder.RabbitMQ.targets delete mode 100644 src/Obsolete/Steeltoe.Stream.Binder.RabbitMQ/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Stream.Binder.RabbitMQ/Steeltoe.Stream.Binder.RabbitMQ.csproj delete mode 100644 src/Obsolete/Steeltoe.Stream.StreamBase/Build/Steeltoe.Stream.StreamBase.targets delete mode 100644 src/Obsolete/Steeltoe.Stream.StreamBase/PackageReadme.md delete mode 100644 src/Obsolete/Steeltoe.Stream.StreamBase/Steeltoe.Stream.StreamBase.csproj diff --git a/src/Obsolete/Steeltoe.Bootstrap.Autoconfig/Build/Steeltoe.Bootstrap.Autoconfig.targets b/src/Obsolete/Steeltoe.Bootstrap.Autoconfig/Build/Steeltoe.Bootstrap.Autoconfig.targets deleted file mode 100644 index 19ead75e0d..0000000000 --- a/src/Obsolete/Steeltoe.Bootstrap.Autoconfig/Build/Steeltoe.Bootstrap.Autoconfig.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Bootstrap.Autoconfig/PackageReadme.md b/src/Obsolete/Steeltoe.Bootstrap.Autoconfig/PackageReadme.md deleted file mode 100644 index 274f63fff7..0000000000 --- a/src/Obsolete/Steeltoe.Bootstrap.Autoconfig/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Bootstrap.AutoConfiguration` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Bootstrap.Autoconfig/Steeltoe.Bootstrap.Autoconfig.csproj b/src/Obsolete/Steeltoe.Bootstrap.Autoconfig/Steeltoe.Bootstrap.Autoconfig.csproj deleted file mode 100644 index b725b15a7a..0000000000 --- a/src/Obsolete/Steeltoe.Bootstrap.Autoconfig/Steeltoe.Bootstrap.Autoconfig.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Package for automatically configuring Steeltoe packages that have separately been added to a project. - Autoconfiguration;automatic configuration;application bootstrapping - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.Abstractions/Build/Steeltoe.CircuitBreaker.Abstractions.targets b/src/Obsolete/Steeltoe.CircuitBreaker.Abstractions/Build/Steeltoe.CircuitBreaker.Abstractions.targets deleted file mode 100644 index 178fef2229..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.Abstractions/Build/Steeltoe.CircuitBreaker.Abstractions.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.Abstractions/PackageReadme.md b/src/Obsolete/Steeltoe.CircuitBreaker.Abstractions/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.Abstractions/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.Abstractions/Steeltoe.CircuitBreaker.Abstractions.csproj b/src/Obsolete/Steeltoe.CircuitBreaker.Abstractions/Steeltoe.CircuitBreaker.Abstractions.csproj deleted file mode 100644 index f7982c518f..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.Abstractions/Steeltoe.CircuitBreaker.Abstractions.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Circuit breaker abstractions. - Spring Cloud;Hystrix Client;Circuit Breaker - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore/Build/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore.targets b/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore/Build/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore.targets deleted file mode 100644 index c33c814ae6..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore/Build/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore/PackageReadme.md b/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore.csproj b/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore.csproj deleted file mode 100644 index fdca65766a..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore/Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe Netflix Hystrix Metrics Event Stream ASP.NET Core. - aspnetcore;Circuit Breaker;Spring;Spring Cloud;Spring Cloud Hystrix;Hystrix - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore/Build/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore.targets b/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore/Build/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore.targets deleted file mode 100644 index f694771dbb..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore/Build/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore/PackageReadme.md b/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore.csproj b/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore.csproj deleted file mode 100644 index 4e401f5d36..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore/Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Netflix Hystrix metrics event stream for ASP.NET Core over RabbitMQ. - aspnetcore;Circuit Breaker;Spring;Spring Cloud;Spring Cloud Hystrix;Hystrix;turbine;cloudfoundry - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.HystrixBase/Build/Steeltoe.CircuitBreaker.HystrixBase.targets b/src/Obsolete/Steeltoe.CircuitBreaker.HystrixBase/Build/Steeltoe.CircuitBreaker.HystrixBase.targets deleted file mode 100644 index 77bca67dfb..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.HystrixBase/Build/Steeltoe.CircuitBreaker.HystrixBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.HystrixBase/PackageReadme.md b/src/Obsolete/Steeltoe.CircuitBreaker.HystrixBase/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.HystrixBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.HystrixBase/Steeltoe.CircuitBreaker.HystrixBase.csproj b/src/Obsolete/Steeltoe.CircuitBreaker.HystrixBase/Steeltoe.CircuitBreaker.HystrixBase.csproj deleted file mode 100644 index 124b01eb60..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.HystrixBase/Steeltoe.CircuitBreaker.HystrixBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe's implementation of Netflix's Hystrix, for .NET. - Spring Cloud;Netflix;Hystrix Client;Circuit Breaker - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.HystrixCore/Build/Steeltoe.CircuitBreaker.HystrixCore.targets b/src/Obsolete/Steeltoe.CircuitBreaker.HystrixCore/Build/Steeltoe.CircuitBreaker.HystrixCore.targets deleted file mode 100644 index 5d0b75f6f0..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.HystrixCore/Build/Steeltoe.CircuitBreaker.HystrixCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.HystrixCore/PackageReadme.md b/src/Obsolete/Steeltoe.CircuitBreaker.HystrixCore/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.HystrixCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.CircuitBreaker.HystrixCore/Steeltoe.CircuitBreaker.HystrixCore.csproj b/src/Obsolete/Steeltoe.CircuitBreaker.HystrixCore/Steeltoe.CircuitBreaker.HystrixCore.csproj deleted file mode 100644 index 458c21b763..0000000000 --- a/src/Obsolete/Steeltoe.CircuitBreaker.HystrixCore/Steeltoe.CircuitBreaker.HystrixCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Package for adding Steeltoe Hystrix to ASP.NET Core applications. - aspnetcore;Spring Cloud;Netflix;Hystrix Client;Circuit Breaker - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Common.Abstractions/Build/Steeltoe.Common.Abstractions.targets b/src/Obsolete/Steeltoe.Common.Abstractions/Build/Steeltoe.Common.Abstractions.targets deleted file mode 100644 index 1b9eb0f3b2..0000000000 --- a/src/Obsolete/Steeltoe.Common.Abstractions/Build/Steeltoe.Common.Abstractions.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Common.Abstractions/PackageReadme.md b/src/Obsolete/Steeltoe.Common.Abstractions/PackageReadme.md deleted file mode 100644 index a6390dae27..0000000000 --- a/src/Obsolete/Steeltoe.Common.Abstractions/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Common` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Common.Abstractions/Steeltoe.Common.Abstractions.csproj b/src/Obsolete/Steeltoe.Common.Abstractions/Steeltoe.Common.Abstractions.csproj deleted file mode 100644 index fd4d319a32..0000000000 --- a/src/Obsolete/Steeltoe.Common.Abstractions/Steeltoe.Common.Abstractions.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Abstractions commonly used across Steeltoe components. - Steeltoe - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Common.Expression/Build/Steeltoe.Common.Expression.targets b/src/Obsolete/Steeltoe.Common.Expression/Build/Steeltoe.Common.Expression.targets deleted file mode 100644 index 699afd69d7..0000000000 --- a/src/Obsolete/Steeltoe.Common.Expression/Build/Steeltoe.Common.Expression.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Common.Expression/PackageReadme.md b/src/Obsolete/Steeltoe.Common.Expression/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Common.Expression/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Common.Expression/Steeltoe.Common.Expression.csproj b/src/Obsolete/Steeltoe.Common.Expression/Steeltoe.Common.Expression.csproj deleted file mode 100644 index 205a0483a9..0000000000 --- a/src/Obsolete/Steeltoe.Common.Expression/Steeltoe.Common.Expression.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe common expression language library. - NET Core;Expression;SPEL - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Common.Kubernetes/Build/Steeltoe.Common.Kubernetes.targets b/src/Obsolete/Steeltoe.Common.Kubernetes/Build/Steeltoe.Common.Kubernetes.targets deleted file mode 100644 index 34f6dcd0fd..0000000000 --- a/src/Obsolete/Steeltoe.Common.Kubernetes/Build/Steeltoe.Common.Kubernetes.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Common.Kubernetes/PackageReadme.md b/src/Obsolete/Steeltoe.Common.Kubernetes/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Common.Kubernetes/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Common.Kubernetes/Steeltoe.Common.Kubernetes.csproj b/src/Obsolete/Steeltoe.Common.Kubernetes/Steeltoe.Common.Kubernetes.csproj deleted file mode 100644 index 271fcc6d8e..0000000000 --- a/src/Obsolete/Steeltoe.Common.Kubernetes/Steeltoe.Common.Kubernetes.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe common library for Kubernetes. - Kubernetes - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Common.Retry/Build/Steeltoe.Common.Retry.targets b/src/Obsolete/Steeltoe.Common.Retry/Build/Steeltoe.Common.Retry.targets deleted file mode 100644 index 5ec00bc904..0000000000 --- a/src/Obsolete/Steeltoe.Common.Retry/Build/Steeltoe.Common.Retry.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Common.Retry/PackageReadme.md b/src/Obsolete/Steeltoe.Common.Retry/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Common.Retry/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Common.Retry/Steeltoe.Common.Retry.csproj b/src/Obsolete/Steeltoe.Common.Retry/Steeltoe.Common.Retry.csproj deleted file mode 100644 index ab1ec15cca..0000000000 --- a/src/Obsolete/Steeltoe.Common.Retry/Steeltoe.Common.Retry.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Retries for circuit breaker. - retries;circuit breaker. - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Common.Security/Build/Steeltoe.Common.Security.targets b/src/Obsolete/Steeltoe.Common.Security/Build/Steeltoe.Common.Security.targets deleted file mode 100644 index 0cc80dad48..0000000000 --- a/src/Obsolete/Steeltoe.Common.Security/Build/Steeltoe.Common.Security.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Common.Security/PackageReadme.md b/src/Obsolete/Steeltoe.Common.Security/PackageReadme.md deleted file mode 100644 index 0c39078f7c..0000000000 --- a/src/Obsolete/Steeltoe.Common.Security/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Common.Certificates` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Common.Security/Steeltoe.Common.Security.csproj b/src/Obsolete/Steeltoe.Common.Security/Steeltoe.Common.Security.csproj deleted file mode 100644 index efbaed9ae9..0000000000 --- a/src/Obsolete/Steeltoe.Common.Security/Steeltoe.Common.Security.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe common library for security. - Steeltoe;security - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Common.Utils/Build/Steeltoe.Common.Utils.targets b/src/Obsolete/Steeltoe.Common.Utils/Build/Steeltoe.Common.Utils.targets deleted file mode 100644 index d8e9ca7c13..0000000000 --- a/src/Obsolete/Steeltoe.Common.Utils/Build/Steeltoe.Common.Utils.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Common.Utils/PackageReadme.md b/src/Obsolete/Steeltoe.Common.Utils/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Common.Utils/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Common.Utils/Steeltoe.Common.Utils.csproj b/src/Obsolete/Steeltoe.Common.Utils/Steeltoe.Common.Utils.csproj deleted file mode 100644 index b9fc95be98..0000000000 --- a/src/Obsolete/Steeltoe.Common.Utils/Steeltoe.Common.Utils.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe common utility libraries. - Steeltoe - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Connector.Abstractions/Build/Steeltoe.Connector.Abstractions.targets b/src/Obsolete/Steeltoe.Connector.Abstractions/Build/Steeltoe.Connector.Abstractions.targets deleted file mode 100644 index d57f3a8438..0000000000 --- a/src/Obsolete/Steeltoe.Connector.Abstractions/Build/Steeltoe.Connector.Abstractions.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Connector.Abstractions/PackageReadme.md b/src/Obsolete/Steeltoe.Connector.Abstractions/PackageReadme.md deleted file mode 100644 index 8598e8519c..0000000000 --- a/src/Obsolete/Steeltoe.Connector.Abstractions/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Connectors` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Connector.Abstractions/Steeltoe.Connector.Abstractions.csproj b/src/Obsolete/Steeltoe.Connector.Abstractions/Steeltoe.Connector.Abstractions.csproj deleted file mode 100644 index 2007b5bd63..0000000000 --- a/src/Obsolete/Steeltoe.Connector.Abstractions/Steeltoe.Connector.Abstractions.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Abstractions for working with backing services. - connectors;services - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Connector.CloudFoundry/Build/Steeltoe.Connector.CloudFoundry.targets b/src/Obsolete/Steeltoe.Connector.CloudFoundry/Build/Steeltoe.Connector.CloudFoundry.targets deleted file mode 100644 index 6ac082b5ad..0000000000 --- a/src/Obsolete/Steeltoe.Connector.CloudFoundry/Build/Steeltoe.Connector.CloudFoundry.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Connector.CloudFoundry/PackageReadme.md b/src/Obsolete/Steeltoe.Connector.CloudFoundry/PackageReadme.md deleted file mode 100644 index 8598e8519c..0000000000 --- a/src/Obsolete/Steeltoe.Connector.CloudFoundry/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Connectors` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Connector.CloudFoundry/Steeltoe.Connector.CloudFoundry.csproj b/src/Obsolete/Steeltoe.Connector.CloudFoundry/Steeltoe.Connector.CloudFoundry.csproj deleted file mode 100644 index 8d7d6f03f2..0000000000 --- a/src/Obsolete/Steeltoe.Connector.CloudFoundry/Steeltoe.Connector.CloudFoundry.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Package for enabling Steeltoe Connectors on Cloud Foundry. - CloudFoundry;vcap;connectors - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Connector.ConnectorBase/Build/Steeltoe.Connector.ConnectorBase.targets b/src/Obsolete/Steeltoe.Connector.ConnectorBase/Build/Steeltoe.Connector.ConnectorBase.targets deleted file mode 100644 index 285c31b244..0000000000 --- a/src/Obsolete/Steeltoe.Connector.ConnectorBase/Build/Steeltoe.Connector.ConnectorBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Connector.ConnectorBase/PackageReadme.md b/src/Obsolete/Steeltoe.Connector.ConnectorBase/PackageReadme.md deleted file mode 100644 index 8598e8519c..0000000000 --- a/src/Obsolete/Steeltoe.Connector.ConnectorBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Connectors` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Connector.ConnectorBase/Steeltoe.Connector.ConnectorBase.csproj b/src/Obsolete/Steeltoe.Connector.ConnectorBase/Steeltoe.Connector.ConnectorBase.csproj deleted file mode 100644 index 7a2cf9fa7c..0000000000 --- a/src/Obsolete/Steeltoe.Connector.ConnectorBase/Steeltoe.Connector.ConnectorBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Connectors for using service bindings in your application. - connectors;services - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Connector.ConnectorCore/Build/Steeltoe.Connector.ConnectorCore.targets b/src/Obsolete/Steeltoe.Connector.ConnectorCore/Build/Steeltoe.Connector.ConnectorCore.targets deleted file mode 100644 index 8c33be76bf..0000000000 --- a/src/Obsolete/Steeltoe.Connector.ConnectorCore/Build/Steeltoe.Connector.ConnectorCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Connector.ConnectorCore/PackageReadme.md b/src/Obsolete/Steeltoe.Connector.ConnectorCore/PackageReadme.md deleted file mode 100644 index 8598e8519c..0000000000 --- a/src/Obsolete/Steeltoe.Connector.ConnectorCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Connectors` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Connector.ConnectorCore/Steeltoe.Connector.ConnectorCore.csproj b/src/Obsolete/Steeltoe.Connector.ConnectorCore/Steeltoe.Connector.ConnectorCore.csproj deleted file mode 100644 index 73316835af..0000000000 --- a/src/Obsolete/Steeltoe.Connector.ConnectorCore/Steeltoe.Connector.ConnectorCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Package for using service connectors in your .NET Core application. - connectors;aspnetcore;services - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Connector.EF6Core/Build/Steeltoe.Connector.EF6Core.targets b/src/Obsolete/Steeltoe.Connector.EF6Core/Build/Steeltoe.Connector.EF6Core.targets deleted file mode 100644 index 1afad722c8..0000000000 --- a/src/Obsolete/Steeltoe.Connector.EF6Core/Build/Steeltoe.Connector.EF6Core.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Connector.EF6Core/PackageReadme.md b/src/Obsolete/Steeltoe.Connector.EF6Core/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Connector.EF6Core/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Connector.EF6Core/Steeltoe.Connector.EF6Core.csproj b/src/Obsolete/Steeltoe.Connector.EF6Core/Steeltoe.Connector.EF6Core.csproj deleted file mode 100644 index 5ce8299759..0000000000 --- a/src/Obsolete/Steeltoe.Connector.EF6Core/Steeltoe.Connector.EF6Core.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Connector Extensions for Entity Framework. - connectors;EntityFramework;aspnetcore;services - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Connector.EFCore/Build/Steeltoe.Connector.EFCore.targets b/src/Obsolete/Steeltoe.Connector.EFCore/Build/Steeltoe.Connector.EFCore.targets deleted file mode 100644 index 36c7910be1..0000000000 --- a/src/Obsolete/Steeltoe.Connector.EFCore/Build/Steeltoe.Connector.EFCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Connector.EFCore/PackageReadme.md b/src/Obsolete/Steeltoe.Connector.EFCore/PackageReadme.md deleted file mode 100644 index 0b94145db9..0000000000 --- a/src/Obsolete/Steeltoe.Connector.EFCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Connectors.EntityFrameworkCore` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Connector.EFCore/Steeltoe.Connector.EFCore.csproj b/src/Obsolete/Steeltoe.Connector.EFCore/Steeltoe.Connector.EFCore.csproj deleted file mode 100644 index 47c638ba60..0000000000 --- a/src/Obsolete/Steeltoe.Connector.EFCore/Steeltoe.Connector.EFCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Package for using Steeltoe Connectors with Entity Framework Core. - connectors;EFCore;EntityFrameworkCore;EF;Entity Framework Core;entity-framework-core;services - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Discovery.Abstractions/Build/Steeltoe.Discovery.Abstractions.targets b/src/Obsolete/Steeltoe.Discovery.Abstractions/Build/Steeltoe.Discovery.Abstractions.targets deleted file mode 100644 index de86a3c355..0000000000 --- a/src/Obsolete/Steeltoe.Discovery.Abstractions/Build/Steeltoe.Discovery.Abstractions.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Discovery.Abstractions/PackageReadme.md b/src/Obsolete/Steeltoe.Discovery.Abstractions/PackageReadme.md deleted file mode 100644 index a6390dae27..0000000000 --- a/src/Obsolete/Steeltoe.Discovery.Abstractions/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Common` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Discovery.Abstractions/Steeltoe.Discovery.Abstractions.csproj b/src/Obsolete/Steeltoe.Discovery.Abstractions/Steeltoe.Discovery.Abstractions.csproj deleted file mode 100644 index f4206bc0b0..0000000000 --- a/src/Obsolete/Steeltoe.Discovery.Abstractions/Steeltoe.Discovery.Abstractions.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Abstractions for service registration and discovery. - service discovery;service registry;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Discovery.ClientBase/Build/Steeltoe.Discovery.ClientBase.targets b/src/Obsolete/Steeltoe.Discovery.ClientBase/Build/Steeltoe.Discovery.ClientBase.targets deleted file mode 100644 index 58033869a2..0000000000 --- a/src/Obsolete/Steeltoe.Discovery.ClientBase/Build/Steeltoe.Discovery.ClientBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Discovery.ClientBase/PackageReadme.md b/src/Obsolete/Steeltoe.Discovery.ClientBase/PackageReadme.md deleted file mode 100644 index 0138850c57..0000000000 --- a/src/Obsolete/Steeltoe.Discovery.ClientBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Discovery.Configuration` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Discovery.ClientBase/Steeltoe.Discovery.ClientBase.csproj b/src/Obsolete/Steeltoe.Discovery.ClientBase/Steeltoe.Discovery.ClientBase.csproj deleted file mode 100644 index ae4596b9a7..0000000000 --- a/src/Obsolete/Steeltoe.Discovery.ClientBase/Steeltoe.Discovery.ClientBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Base package for using Steeltoe Service Discovery. - service discovery;service registry;Spring Cloud;eureka;consul;kubernetes - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Discovery.ClientCore/Build/Steeltoe.Discovery.ClientCore.targets b/src/Obsolete/Steeltoe.Discovery.ClientCore/Build/Steeltoe.Discovery.ClientCore.targets deleted file mode 100644 index 235e1127dd..0000000000 --- a/src/Obsolete/Steeltoe.Discovery.ClientCore/Build/Steeltoe.Discovery.ClientCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Discovery.ClientCore/PackageReadme.md b/src/Obsolete/Steeltoe.Discovery.ClientCore/PackageReadme.md deleted file mode 100644 index 0138850c57..0000000000 --- a/src/Obsolete/Steeltoe.Discovery.ClientCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Discovery.Configuration` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Discovery.ClientCore/Steeltoe.Discovery.ClientCore.csproj b/src/Obsolete/Steeltoe.Discovery.ClientCore/Steeltoe.Discovery.ClientCore.csproj deleted file mode 100644 index 7bb699582c..0000000000 --- a/src/Obsolete/Steeltoe.Discovery.ClientCore/Steeltoe.Discovery.ClientCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Package for using Steeltoe Service Discovery Client in ASP.NET Core applications. - aspnetcore;service discovery;service registry;Spring Cloud;eureka;consul;kubernetes - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Discovery.Kubernetes/Build/Steeltoe.Discovery.Kubernetes.targets b/src/Obsolete/Steeltoe.Discovery.Kubernetes/Build/Steeltoe.Discovery.Kubernetes.targets deleted file mode 100644 index 418fc9cf31..0000000000 --- a/src/Obsolete/Steeltoe.Discovery.Kubernetes/Build/Steeltoe.Discovery.Kubernetes.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Discovery.Kubernetes/PackageReadme.md b/src/Obsolete/Steeltoe.Discovery.Kubernetes/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Discovery.Kubernetes/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Discovery.Kubernetes/Steeltoe.Discovery.Kubernetes.csproj b/src/Obsolete/Steeltoe.Discovery.Kubernetes/Steeltoe.Discovery.Kubernetes.csproj deleted file mode 100644 index a5f829127a..0000000000 --- a/src/Obsolete/Steeltoe.Discovery.Kubernetes/Steeltoe.Discovery.Kubernetes.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Client for service discovery with Kubernetes native service discovery. - aspnetcore;Kubernetes;Spring;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.Abstractions/Build/Steeltoe.Extensions.Configuration.Abstractions.targets b/src/Obsolete/Steeltoe.Extensions.Configuration.Abstractions/Build/Steeltoe.Extensions.Configuration.Abstractions.targets deleted file mode 100644 index d84bc174ce..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.Abstractions/Build/Steeltoe.Extensions.Configuration.Abstractions.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.Abstractions/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Configuration.Abstractions/PackageReadme.md deleted file mode 100644 index 0d52dac0d5..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.Abstractions/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Configuration.Abstractions` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.Abstractions/Steeltoe.Extensions.Configuration.Abstractions.csproj b/src/Obsolete/Steeltoe.Extensions.Configuration.Abstractions/Steeltoe.Extensions.Configuration.Abstractions.csproj deleted file mode 100644 index 9f2c573e48..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.Abstractions/Steeltoe.Extensions.Configuration.Abstractions.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Abstractions used in Steeltoe Configuration libraries. - configuration;spring boot - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryBase/Build/Steeltoe.Extensions.Configuration.CloudFoundryBase.targets b/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryBase/Build/Steeltoe.Extensions.Configuration.CloudFoundryBase.targets deleted file mode 100644 index d1c4d2be2f..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryBase/Build/Steeltoe.Extensions.Configuration.CloudFoundryBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryBase/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryBase/PackageReadme.md deleted file mode 100644 index 0886311b76..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Configuration.CloudFoundry` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryBase/Steeltoe.Extensions.Configuration.CloudFoundryBase.csproj b/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryBase/Steeltoe.Extensions.Configuration.CloudFoundryBase.csproj deleted file mode 100644 index 5fbd4ba3c8..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryBase/Steeltoe.Extensions.Configuration.CloudFoundryBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Configuration Provider for reading Cloud Foundry Environment Variables. - configuration;CloudFoundry;vcap - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryCore/Build/Steeltoe.Extensions.Configuration.CloudFoundryCore.targets b/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryCore/Build/Steeltoe.Extensions.Configuration.CloudFoundryCore.targets deleted file mode 100644 index 35f400a323..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryCore/Build/Steeltoe.Extensions.Configuration.CloudFoundryCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryCore/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryCore/PackageReadme.md deleted file mode 100644 index 0886311b76..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Configuration.CloudFoundry` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryCore/Steeltoe.Extensions.Configuration.CloudFoundryCore.csproj b/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryCore/Steeltoe.Extensions.Configuration.CloudFoundryCore.csproj deleted file mode 100644 index 07fb9d28a1..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.CloudFoundryCore/Steeltoe.Extensions.Configuration.CloudFoundryCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Package for adding Cloud Foundry environment variable configuration provider to ASP.NET Core applications. - aspnetcore;CloudFoundry;Spring;Spring Cloud;vcap - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerBase/Build/Steeltoe.Extensions.Configuration.ConfigServerBase.targets b/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerBase/Build/Steeltoe.Extensions.Configuration.ConfigServerBase.targets deleted file mode 100644 index 682b2298e1..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerBase/Build/Steeltoe.Extensions.Configuration.ConfigServerBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerBase/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerBase/PackageReadme.md deleted file mode 100644 index ff0b8ed709..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Configuration.ConfigServer` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerBase/Steeltoe.Extensions.Configuration.ConfigServerBase.csproj b/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerBase/Steeltoe.Extensions.Configuration.ConfigServerBase.csproj deleted file mode 100644 index 0f657d42b7..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerBase/Steeltoe.Extensions.Configuration.ConfigServerBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Configuration provider for reading from Spring Cloud Config Server. - configuration;Spring Cloud;Spring Cloud Config Server - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerCore/Build/Steeltoe.Extensions.Configuration.ConfigServerCore.targets b/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerCore/Build/Steeltoe.Extensions.Configuration.ConfigServerCore.targets deleted file mode 100644 index 9fbbf06d4b..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerCore/Build/Steeltoe.Extensions.Configuration.ConfigServerCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerCore/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerCore/PackageReadme.md deleted file mode 100644 index ff0b8ed709..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Configuration.ConfigServer` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerCore/Steeltoe.Extensions.Configuration.ConfigServerCore.csproj b/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerCore/Steeltoe.Extensions.Configuration.ConfigServerCore.csproj deleted file mode 100644 index 8745390baf..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.ConfigServerCore/Steeltoe.Extensions.Configuration.ConfigServerCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Package for adding Spring Cloud Config Server configuration provider to ASP.NET Core applications. - aspnetcore;Spring Cloud;Spring Cloud Config Server - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding/Build/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding.targets b/src/Obsolete/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding/Build/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding.targets deleted file mode 100644 index c38b683dc3..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding/Build/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding/PackageReadme.md deleted file mode 100644 index 64cd1b85fe..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Configuration.Kubernetes.ServiceBindings` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding.csproj b/src/Obsolete/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding.csproj deleted file mode 100644 index 4017414592..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding/Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Configuration Provider for reading Kubernetes Service Bindings. - Configuration;Kubernetes;Services;Bindings - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesBase/Build/Steeltoe.Extensions.Configuration.KubernetesBase.targets b/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesBase/Build/Steeltoe.Extensions.Configuration.KubernetesBase.targets deleted file mode 100644 index 46e05db9b1..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesBase/Build/Steeltoe.Extensions.Configuration.KubernetesBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesBase/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesBase/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesBase/Steeltoe.Extensions.Configuration.KubernetesBase.csproj b/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesBase/Steeltoe.Extensions.Configuration.KubernetesBase.csproj deleted file mode 100644 index 21c169dbdf..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesBase/Steeltoe.Extensions.Configuration.KubernetesBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Configuration Provider for reading Cloud Foundry Environment Variables. - configuration;Kubernetes - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesCore/Build/Steeltoe.Extensions.Configuration.KubernetesCore.targets b/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesCore/Build/Steeltoe.Extensions.Configuration.KubernetesCore.targets deleted file mode 100644 index 9eddcafcb4..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesCore/Build/Steeltoe.Extensions.Configuration.KubernetesCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesCore/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesCore/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesCore/Steeltoe.Extensions.Configuration.KubernetesCore.csproj b/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesCore/Steeltoe.Extensions.Configuration.KubernetesCore.csproj deleted file mode 100644 index efdf80788e..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.KubernetesCore/Steeltoe.Extensions.Configuration.KubernetesCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Package for adding Kubernetes environment variables, ConfigMaps and Secrets to .NET applications. - Kubernetes;Spring;Spring Cloud;configuration;configmap - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderBase/Build/Steeltoe.Extensions.Configuration.PlaceholderBase.targets b/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderBase/Build/Steeltoe.Extensions.Configuration.PlaceholderBase.targets deleted file mode 100644 index f3e899d71d..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderBase/Build/Steeltoe.Extensions.Configuration.PlaceholderBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderBase/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderBase/PackageReadme.md deleted file mode 100644 index 7a2cb0dad7..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Configuration.Placeholder` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderBase/Steeltoe.Extensions.Configuration.PlaceholderBase.csproj b/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderBase/Steeltoe.Extensions.Configuration.PlaceholderBase.csproj deleted file mode 100644 index d4049b5eb2..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderBase/Steeltoe.Extensions.Configuration.PlaceholderBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Configuration provider for resolving property placeholders in configuration values. - configuration;placeholders;spring boot - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderCore/Build/Steeltoe.Extensions.Configuration.PlaceholderCore.targets b/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderCore/Build/Steeltoe.Extensions.Configuration.PlaceholderCore.targets deleted file mode 100644 index 1bf089a2fb..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderCore/Build/Steeltoe.Extensions.Configuration.PlaceholderCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderCore/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderCore/PackageReadme.md deleted file mode 100644 index 7a2cb0dad7..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Configuration.Placeholder` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderCore/Steeltoe.Extensions.Configuration.PlaceholderCore.csproj b/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderCore/Steeltoe.Extensions.Configuration.PlaceholderCore.csproj deleted file mode 100644 index 882d3186a2..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.PlaceholderCore/Steeltoe.Extensions.Configuration.PlaceholderCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Package for adding property placeholder resolving config provider to ASP.NET Core applications. - configuration;placeholders;aspnetcore;spring boot - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.RandomValueBase/Build/Steeltoe.Extensions.Configuration.RandomValueBase.targets b/src/Obsolete/Steeltoe.Extensions.Configuration.RandomValueBase/Build/Steeltoe.Extensions.Configuration.RandomValueBase.targets deleted file mode 100644 index 8d322d52b6..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.RandomValueBase/Build/Steeltoe.Extensions.Configuration.RandomValueBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.RandomValueBase/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Configuration.RandomValueBase/PackageReadme.md deleted file mode 100644 index 8e49bb4281..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.RandomValueBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Configuration.RandomValue` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.RandomValueBase/Steeltoe.Extensions.Configuration.RandomValueBase.csproj b/src/Obsolete/Steeltoe.Extensions.Configuration.RandomValueBase/Steeltoe.Extensions.Configuration.RandomValueBase.csproj deleted file mode 100644 index 7566376719..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.RandomValueBase/Steeltoe.Extensions.Configuration.RandomValueBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Configuration provider for generating random values. - configuration;random values - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootBase/Build/Steeltoe.Extensions.Configuration.SpringBootBase.targets b/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootBase/Build/Steeltoe.Extensions.Configuration.SpringBootBase.targets deleted file mode 100644 index d8aa14c328..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootBase/Build/Steeltoe.Extensions.Configuration.SpringBootBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootBase/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootBase/PackageReadme.md deleted file mode 100644 index a1409eec02..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Configuration.SpringBoot` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootBase/Steeltoe.Extensions.Configuration.SpringBootBase.csproj b/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootBase/Steeltoe.Extensions.Configuration.SpringBootBase.csproj deleted file mode 100644 index 56f603b646..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootBase/Steeltoe.Extensions.Configuration.SpringBootBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Configuration provider for reading Spring Boot style configuration. - configuration;springboot - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootCore/Build/Steeltoe.Extensions.Configuration.SpringBootCore.targets b/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootCore/Build/Steeltoe.Extensions.Configuration.SpringBootCore.targets deleted file mode 100644 index 6eab894027..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootCore/Build/Steeltoe.Extensions.Configuration.SpringBootCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootCore/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootCore/PackageReadme.md deleted file mode 100644 index a1409eec02..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Configuration.SpringBoot` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootCore/Steeltoe.Extensions.Configuration.SpringBootCore.csproj b/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootCore/Steeltoe.Extensions.Configuration.SpringBootCore.csproj deleted file mode 100644 index 56f603b646..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Configuration.SpringBootCore/Steeltoe.Extensions.Configuration.SpringBootCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Configuration provider for reading Spring Boot style configuration. - configuration;springboot - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Logging.Abstractions/Build/Steeltoe.Extensions.Logging.Abstractions.targets b/src/Obsolete/Steeltoe.Extensions.Logging.Abstractions/Build/Steeltoe.Extensions.Logging.Abstractions.targets deleted file mode 100644 index 36f4043565..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Logging.Abstractions/Build/Steeltoe.Extensions.Logging.Abstractions.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Logging.Abstractions/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Logging.Abstractions/PackageReadme.md deleted file mode 100644 index e6e1a26e91..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Logging.Abstractions/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Logging.Abstractions` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Logging.Abstractions/Steeltoe.Extensions.Logging.Abstractions.csproj b/src/Obsolete/Steeltoe.Extensions.Logging.Abstractions/Steeltoe.Extensions.Logging.Abstractions.csproj deleted file mode 100644 index ba13f09780..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Logging.Abstractions/Steeltoe.Extensions.Logging.Abstractions.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Abstractions for use with dynamic logging. - logging;dynamic logging;management;monitoring;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicLogger/Build/Steeltoe.Extensions.Logging.DynamicLogger.targets b/src/Obsolete/Steeltoe.Extensions.Logging.DynamicLogger/Build/Steeltoe.Extensions.Logging.DynamicLogger.targets deleted file mode 100644 index 1224f18289..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicLogger/Build/Steeltoe.Extensions.Logging.DynamicLogger.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicLogger/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Logging.DynamicLogger/PackageReadme.md deleted file mode 100644 index 6696ca487c..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicLogger/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Logging.DynamicConsole` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicLogger/Steeltoe.Extensions.Logging.DynamicLogger.csproj b/src/Obsolete/Steeltoe.Extensions.Logging.DynamicLogger/Steeltoe.Extensions.Logging.DynamicLogger.csproj deleted file mode 100644 index 4412937b49..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicLogger/Steeltoe.Extensions.Logging.DynamicLogger.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe Dynamic Console Logger. - logging;dynamic logging;console;management;monitoring;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogBase/Build/Steeltoe.Extensions.Logging.DynamicSerilogBase.targets b/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogBase/Build/Steeltoe.Extensions.Logging.DynamicSerilogBase.targets deleted file mode 100644 index a21adb7989..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogBase/Build/Steeltoe.Extensions.Logging.DynamicSerilogBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogBase/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogBase/PackageReadme.md deleted file mode 100644 index 2e596ac382..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Logging.DynamicSerilog` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogBase/Steeltoe.Extensions.Logging.DynamicSerilogBase.csproj b/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogBase/Steeltoe.Extensions.Logging.DynamicSerilogBase.csproj deleted file mode 100644 index d6e6fa7895..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogBase/Steeltoe.Extensions.Logging.DynamicSerilogBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe library for enabling dynamic management of Serilog. - logging;dynamic logging;serilog;management;monitoring;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogCore/Build/Steeltoe.Extensions.Logging.DynamicSerilogCore.targets b/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogCore/Build/Steeltoe.Extensions.Logging.DynamicSerilogCore.targets deleted file mode 100644 index 73ca107c27..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogCore/Build/Steeltoe.Extensions.Logging.DynamicSerilogCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogCore/PackageReadme.md b/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogCore/PackageReadme.md deleted file mode 100644 index 2e596ac382..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Logging.DynamicSerilog` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogCore/Steeltoe.Extensions.Logging.DynamicSerilogCore.csproj b/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogCore/Steeltoe.Extensions.Logging.DynamicSerilogCore.csproj deleted file mode 100644 index 49ce002d56..0000000000 --- a/src/Obsolete/Steeltoe.Extensions.Logging.DynamicSerilogCore/Steeltoe.Extensions.Logging.DynamicSerilogCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe library for enabling dynamic management of Serilog in ASP.NET Core applications. Includes Console sink. - logging;dynamic logging;serilog;aspnetcore;management;monitoring;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Integration.Abstractions/Build/Steeltoe.Integration.Abstractions.targets b/src/Obsolete/Steeltoe.Integration.Abstractions/Build/Steeltoe.Integration.Abstractions.targets deleted file mode 100644 index b26bcaca9d..0000000000 --- a/src/Obsolete/Steeltoe.Integration.Abstractions/Build/Steeltoe.Integration.Abstractions.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Integration.Abstractions/PackageReadme.md b/src/Obsolete/Steeltoe.Integration.Abstractions/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Integration.Abstractions/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Integration.Abstractions/Steeltoe.Integration.Abstractions.csproj b/src/Obsolete/Steeltoe.Integration.Abstractions/Steeltoe.Integration.Abstractions.csproj deleted file mode 100644 index 6fea06cf5a..0000000000 --- a/src/Obsolete/Steeltoe.Integration.Abstractions/Steeltoe.Integration.Abstractions.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Abstractions for use with Steeltoe Integration libraries. - Integration;NET Core;Spring;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Integration.IntegrationBase/Build/Steeltoe.Integration.IntegrationBase.targets b/src/Obsolete/Steeltoe.Integration.IntegrationBase/Build/Steeltoe.Integration.IntegrationBase.targets deleted file mode 100644 index 871662c291..0000000000 --- a/src/Obsolete/Steeltoe.Integration.IntegrationBase/Build/Steeltoe.Integration.IntegrationBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Integration.IntegrationBase/PackageReadme.md b/src/Obsolete/Steeltoe.Integration.IntegrationBase/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Integration.IntegrationBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Integration.IntegrationBase/Steeltoe.Integration.IntegrationBase.csproj b/src/Obsolete/Steeltoe.Integration.IntegrationBase/Steeltoe.Integration.IntegrationBase.csproj deleted file mode 100644 index 54338ac6a0..0000000000 --- a/src/Obsolete/Steeltoe.Integration.IntegrationBase/Steeltoe.Integration.IntegrationBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe Integration Base. - Integration;NET Core;Spring;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Integration.RabbitMQ/Build/Steeltoe.Integration.RabbitMQ.targets b/src/Obsolete/Steeltoe.Integration.RabbitMQ/Build/Steeltoe.Integration.RabbitMQ.targets deleted file mode 100644 index 97750f85e1..0000000000 --- a/src/Obsolete/Steeltoe.Integration.RabbitMQ/Build/Steeltoe.Integration.RabbitMQ.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Integration.RabbitMQ/PackageReadme.md b/src/Obsolete/Steeltoe.Integration.RabbitMQ/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Integration.RabbitMQ/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Integration.RabbitMQ/Steeltoe.Integration.RabbitMQ.csproj b/src/Obsolete/Steeltoe.Integration.RabbitMQ/Steeltoe.Integration.RabbitMQ.csproj deleted file mode 100644 index c60a3f2d36..0000000000 --- a/src/Obsolete/Steeltoe.Integration.RabbitMQ/Steeltoe.Integration.RabbitMQ.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe Integration RabbitMQ. - Integration;ASPNET Core;Spring;Spring Cloud;RabbitMQ - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.CloudFoundryCore/Build/Steeltoe.Management.CloudFoundryCore.targets b/src/Obsolete/Steeltoe.Management.CloudFoundryCore/Build/Steeltoe.Management.CloudFoundryCore.targets deleted file mode 100644 index b0d8d21205..0000000000 --- a/src/Obsolete/Steeltoe.Management.CloudFoundryCore/Build/Steeltoe.Management.CloudFoundryCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.CloudFoundryCore/PackageReadme.md b/src/Obsolete/Steeltoe.Management.CloudFoundryCore/PackageReadme.md deleted file mode 100644 index ccaa917bf0..0000000000 --- a/src/Obsolete/Steeltoe.Management.CloudFoundryCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Management.Endpoint` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Management.CloudFoundryCore/Steeltoe.Management.CloudFoundryCore.csproj b/src/Obsolete/Steeltoe.Management.CloudFoundryCore/Steeltoe.Management.CloudFoundryCore.csproj deleted file mode 100644 index fc75ecfea6..0000000000 --- a/src/Obsolete/Steeltoe.Management.CloudFoundryCore/Steeltoe.Management.CloudFoundryCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Package for using Steeltoe management endpoints with ASP.NET Core on Cloud Foundry. - actuator;management;monitoring;aspnetcore;CloudFoundry;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.Diagnostics/Build/Steeltoe.Management.Diagnostics.targets b/src/Obsolete/Steeltoe.Management.Diagnostics/Build/Steeltoe.Management.Diagnostics.targets deleted file mode 100644 index 3c99503b09..0000000000 --- a/src/Obsolete/Steeltoe.Management.Diagnostics/Build/Steeltoe.Management.Diagnostics.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.Diagnostics/PackageReadme.md b/src/Obsolete/Steeltoe.Management.Diagnostics/PackageReadme.md deleted file mode 100644 index ccaa917bf0..0000000000 --- a/src/Obsolete/Steeltoe.Management.Diagnostics/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Management.Endpoint` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Management.Diagnostics/Steeltoe.Management.Diagnostics.csproj b/src/Obsolete/Steeltoe.Management.Diagnostics/Steeltoe.Management.Diagnostics.csproj deleted file mode 100644 index fe9fe4476a..0000000000 --- a/src/Obsolete/Steeltoe.Management.Diagnostics/Steeltoe.Management.Diagnostics.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe Management Runtime Diagnostics. - actuator;management;monitoring;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.EndpointBase/Build/Steeltoe.Management.EndpointBase.targets b/src/Obsolete/Steeltoe.Management.EndpointBase/Build/Steeltoe.Management.EndpointBase.targets deleted file mode 100644 index f4d5a60157..0000000000 --- a/src/Obsolete/Steeltoe.Management.EndpointBase/Build/Steeltoe.Management.EndpointBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.EndpointBase/PackageReadme.md b/src/Obsolete/Steeltoe.Management.EndpointBase/PackageReadme.md deleted file mode 100644 index ccaa917bf0..0000000000 --- a/src/Obsolete/Steeltoe.Management.EndpointBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Management.Endpoint` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Management.EndpointBase/Steeltoe.Management.EndpointBase.csproj b/src/Obsolete/Steeltoe.Management.EndpointBase/Steeltoe.Management.EndpointBase.csproj deleted file mode 100644 index fb2eb15ceb..0000000000 --- a/src/Obsolete/Steeltoe.Management.EndpointBase/Steeltoe.Management.EndpointBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe management endpoints. - actuator;management;monitoring;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.EndpointCore/Build/Steeltoe.Management.EndpointCore.targets b/src/Obsolete/Steeltoe.Management.EndpointCore/Build/Steeltoe.Management.EndpointCore.targets deleted file mode 100644 index 74eebadb14..0000000000 --- a/src/Obsolete/Steeltoe.Management.EndpointCore/Build/Steeltoe.Management.EndpointCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.EndpointCore/PackageReadme.md b/src/Obsolete/Steeltoe.Management.EndpointCore/PackageReadme.md deleted file mode 100644 index ccaa917bf0..0000000000 --- a/src/Obsolete/Steeltoe.Management.EndpointCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Management.Endpoint` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Management.EndpointCore/Steeltoe.Management.EndpointCore.csproj b/src/Obsolete/Steeltoe.Management.EndpointCore/Steeltoe.Management.EndpointCore.csproj deleted file mode 100644 index 8509367149..0000000000 --- a/src/Obsolete/Steeltoe.Management.EndpointCore/Steeltoe.Management.EndpointCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Package for using Steeltoe management endpoints with ASP.NET Core. - Spring Cloud;Actuator;Management;Monitoring - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.KubernetesCore/Build/Steeltoe.Management.KubernetesCore.targets b/src/Obsolete/Steeltoe.Management.KubernetesCore/Build/Steeltoe.Management.KubernetesCore.targets deleted file mode 100644 index 4b0876b2d8..0000000000 --- a/src/Obsolete/Steeltoe.Management.KubernetesCore/Build/Steeltoe.Management.KubernetesCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.KubernetesCore/PackageReadme.md b/src/Obsolete/Steeltoe.Management.KubernetesCore/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Management.KubernetesCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Management.KubernetesCore/Steeltoe.Management.KubernetesCore.csproj b/src/Obsolete/Steeltoe.Management.KubernetesCore/Steeltoe.Management.KubernetesCore.csproj deleted file mode 100644 index b3a6d6d2c4..0000000000 --- a/src/Obsolete/Steeltoe.Management.KubernetesCore/Steeltoe.Management.KubernetesCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Package for using Steeltoe management endpoints with ASP.NET Core on Kubernetes. - actuator;management;monitoring;aspnetcore;Kubernetes;Spring Cloud;k8s - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.OpenTelemetryBase/Build/Steeltoe.Management.OpenTelemetryBase.targets b/src/Obsolete/Steeltoe.Management.OpenTelemetryBase/Build/Steeltoe.Management.OpenTelemetryBase.targets deleted file mode 100644 index c2600f24d7..0000000000 --- a/src/Obsolete/Steeltoe.Management.OpenTelemetryBase/Build/Steeltoe.Management.OpenTelemetryBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.OpenTelemetryBase/PackageReadme.md b/src/Obsolete/Steeltoe.Management.OpenTelemetryBase/PackageReadme.md deleted file mode 100644 index cc3a7d5d86..0000000000 --- a/src/Obsolete/Steeltoe.Management.OpenTelemetryBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Management.Prometheus` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Management.OpenTelemetryBase/Steeltoe.Management.OpenTelemetryBase.csproj b/src/Obsolete/Steeltoe.Management.OpenTelemetryBase/Steeltoe.Management.OpenTelemetryBase.csproj deleted file mode 100644 index 000fcbb965..0000000000 --- a/src/Obsolete/Steeltoe.Management.OpenTelemetryBase/Steeltoe.Management.OpenTelemetryBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe Management OpenTelemetry. - Tracing;OpenTelemetry;Management;Monitoring - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.TaskCore/Build/Steeltoe.Management.TaskCore.targets b/src/Obsolete/Steeltoe.Management.TaskCore/Build/Steeltoe.Management.TaskCore.targets deleted file mode 100644 index 3ae7b04a2a..0000000000 --- a/src/Obsolete/Steeltoe.Management.TaskCore/Build/Steeltoe.Management.TaskCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.TaskCore/PackageReadme.md b/src/Obsolete/Steeltoe.Management.TaskCore/PackageReadme.md deleted file mode 100644 index 3bf83d7451..0000000000 --- a/src/Obsolete/Steeltoe.Management.TaskCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Management.Tasks` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Management.TaskCore/Steeltoe.Management.TaskCore.csproj b/src/Obsolete/Steeltoe.Management.TaskCore/Steeltoe.Management.TaskCore.csproj deleted file mode 100644 index 7c42c957a0..0000000000 --- a/src/Obsolete/Steeltoe.Management.TaskCore/Steeltoe.Management.TaskCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Extensions for running tasks embedded in your ASP.NET Core application. Ideal for cf run-task in Cloud Foundry. - tasks;management;monitoring;aspnetcore;Spring Cloud;cf run-task - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.TracingBase/Build/Steeltoe.Management.TracingBase.targets b/src/Obsolete/Steeltoe.Management.TracingBase/Build/Steeltoe.Management.TracingBase.targets deleted file mode 100644 index 0a391d67fc..0000000000 --- a/src/Obsolete/Steeltoe.Management.TracingBase/Build/Steeltoe.Management.TracingBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.TracingBase/PackageReadme.md b/src/Obsolete/Steeltoe.Management.TracingBase/PackageReadme.md deleted file mode 100644 index 324240b977..0000000000 --- a/src/Obsolete/Steeltoe.Management.TracingBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Management.Tracing` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Management.TracingBase/Steeltoe.Management.TracingBase.csproj b/src/Obsolete/Steeltoe.Management.TracingBase/Steeltoe.Management.TracingBase.csproj deleted file mode 100644 index 8273df78c4..0000000000 --- a/src/Obsolete/Steeltoe.Management.TracingBase/Steeltoe.Management.TracingBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Base package for enabling request tracing in distributed systems. - management;monitoring;distributed trace - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.TracingCore/Build/Steeltoe.Management.TracingCore.targets b/src/Obsolete/Steeltoe.Management.TracingCore/Build/Steeltoe.Management.TracingCore.targets deleted file mode 100644 index 05b9df9337..0000000000 --- a/src/Obsolete/Steeltoe.Management.TracingCore/Build/Steeltoe.Management.TracingCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Management.TracingCore/PackageReadme.md b/src/Obsolete/Steeltoe.Management.TracingCore/PackageReadme.md deleted file mode 100644 index 324240b977..0000000000 --- a/src/Obsolete/Steeltoe.Management.TracingCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Management.Tracing` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Management.TracingCore/Steeltoe.Management.TracingCore.csproj b/src/Obsolete/Steeltoe.Management.TracingCore/Steeltoe.Management.TracingCore.csproj deleted file mode 100644 index 00d4ca2d3f..0000000000 --- a/src/Obsolete/Steeltoe.Management.TracingCore/Steeltoe.Management.TracingCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Add distributed tracing to ASP.NET Core applications. - aspnetcore;management;monitoring;metrics;Distributed Trace - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Messaging.Abstractions/Build/Steeltoe.Messaging.Abstractions.targets b/src/Obsolete/Steeltoe.Messaging.Abstractions/Build/Steeltoe.Messaging.Abstractions.targets deleted file mode 100644 index 37bafaa6c0..0000000000 --- a/src/Obsolete/Steeltoe.Messaging.Abstractions/Build/Steeltoe.Messaging.Abstractions.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Messaging.Abstractions/PackageReadme.md b/src/Obsolete/Steeltoe.Messaging.Abstractions/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Messaging.Abstractions/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Messaging.Abstractions/Steeltoe.Messaging.Abstractions.csproj b/src/Obsolete/Steeltoe.Messaging.Abstractions/Steeltoe.Messaging.Abstractions.csproj deleted file mode 100644 index c768e75a56..0000000000 --- a/src/Obsolete/Steeltoe.Messaging.Abstractions/Steeltoe.Messaging.Abstractions.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Abstractions for use with Steeltoe Messaging. - Messaging;NET Core;Spring;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Messaging.MessagingBase/Build/Steeltoe.Messaging.MessagingBase.targets b/src/Obsolete/Steeltoe.Messaging.MessagingBase/Build/Steeltoe.Messaging.MessagingBase.targets deleted file mode 100644 index a72ae0044f..0000000000 --- a/src/Obsolete/Steeltoe.Messaging.MessagingBase/Build/Steeltoe.Messaging.MessagingBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Messaging.MessagingBase/PackageReadme.md b/src/Obsolete/Steeltoe.Messaging.MessagingBase/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Messaging.MessagingBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Messaging.MessagingBase/Steeltoe.Messaging.MessagingBase.csproj b/src/Obsolete/Steeltoe.Messaging.MessagingBase/Steeltoe.Messaging.MessagingBase.csproj deleted file mode 100644 index ca4d4965ca..0000000000 --- a/src/Obsolete/Steeltoe.Messaging.MessagingBase/Steeltoe.Messaging.MessagingBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe Messaging Base. - Messaging;NET Core;Spring;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Messaging.RabbitMQ/Build/Steeltoe.Messaging.RabbitMQ.targets b/src/Obsolete/Steeltoe.Messaging.RabbitMQ/Build/Steeltoe.Messaging.RabbitMQ.targets deleted file mode 100644 index 2ba7ed0997..0000000000 --- a/src/Obsolete/Steeltoe.Messaging.RabbitMQ/Build/Steeltoe.Messaging.RabbitMQ.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Messaging.RabbitMQ/PackageReadme.md b/src/Obsolete/Steeltoe.Messaging.RabbitMQ/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Messaging.RabbitMQ/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Messaging.RabbitMQ/Steeltoe.Messaging.RabbitMQ.csproj b/src/Obsolete/Steeltoe.Messaging.RabbitMQ/Steeltoe.Messaging.RabbitMQ.csproj deleted file mode 100644 index 32584dfe81..0000000000 --- a/src/Obsolete/Steeltoe.Messaging.RabbitMQ/Steeltoe.Messaging.RabbitMQ.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe Messaging RabbitMQ. - Messaging;ASPNET Core;Spring;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryBase/Build/Steeltoe.Security.Authentication.CloudFoundryBase.targets b/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryBase/Build/Steeltoe.Security.Authentication.CloudFoundryBase.targets deleted file mode 100644 index 8ecc94edbc..0000000000 --- a/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryBase/Build/Steeltoe.Security.Authentication.CloudFoundryBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryBase/PackageReadme.md b/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryBase/PackageReadme.md deleted file mode 100644 index d3dc634352..0000000000 --- a/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Security.Authentication.JwtBearer` / `Steeltoe.Security.Authentication.OpenIdConnect` / `Steeltoe.Security.Authorization.Certificate` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryBase/Steeltoe.Security.Authentication.CloudFoundryBase.csproj b/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryBase/Steeltoe.Security.Authentication.CloudFoundryBase.csproj deleted file mode 100644 index ff75263b86..0000000000 --- a/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryBase/Steeltoe.Security.Authentication.CloudFoundryBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Base Security Provider for CloudFoundry. - CloudFoundry;security;oauth2;sso;openid - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryCore/Build/Steeltoe.Security.Authentication.CloudFoundryCore.targets b/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryCore/Build/Steeltoe.Security.Authentication.CloudFoundryCore.targets deleted file mode 100644 index 98248be8ff..0000000000 --- a/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryCore/Build/Steeltoe.Security.Authentication.CloudFoundryCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryCore/PackageReadme.md b/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryCore/PackageReadme.md deleted file mode 100644 index d3dc634352..0000000000 --- a/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Security.Authentication.JwtBearer` / `Steeltoe.Security.Authentication.OpenIdConnect` / `Steeltoe.Security.Authorization.Certificate` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryCore/Steeltoe.Security.Authentication.CloudFoundryCore.csproj b/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryCore/Steeltoe.Security.Authentication.CloudFoundryCore.csproj deleted file mode 100644 index 4166c11874..0000000000 --- a/src/Obsolete/Steeltoe.Security.Authentication.CloudFoundryCore/Steeltoe.Security.Authentication.CloudFoundryCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - ASP.NET Core External Security Provider for CloudFoundry. - CloudFoundry;ASPNET Core;Security;OAuth2;SSO;OpenIDConnect - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Security.Authentication.MtlsCore/Build/Steeltoe.Security.Authentication.MtlsCore.targets b/src/Obsolete/Steeltoe.Security.Authentication.MtlsCore/Build/Steeltoe.Security.Authentication.MtlsCore.targets deleted file mode 100644 index f60cbec70b..0000000000 --- a/src/Obsolete/Steeltoe.Security.Authentication.MtlsCore/Build/Steeltoe.Security.Authentication.MtlsCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Security.Authentication.MtlsCore/PackageReadme.md b/src/Obsolete/Steeltoe.Security.Authentication.MtlsCore/PackageReadme.md deleted file mode 100644 index c06f35e8bf..0000000000 --- a/src/Obsolete/Steeltoe.Security.Authentication.MtlsCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Security.Authorization.Certificate` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Security.Authentication.MtlsCore/Steeltoe.Security.Authentication.MtlsCore.csproj b/src/Obsolete/Steeltoe.Security.Authentication.MtlsCore/Steeltoe.Security.Authentication.MtlsCore.csproj deleted file mode 100644 index 0571350076..0000000000 --- a/src/Obsolete/Steeltoe.Security.Authentication.MtlsCore/Steeltoe.Security.Authentication.MtlsCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - ASP.NET Core middleware that enables an application to support certificate authentication. - aspnetcore;authentication;security;x509;certificate;mtls - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Security.DataProtection.CredHubBase/Build/Steeltoe.Security.DataProtection.CredHubBase.targets b/src/Obsolete/Steeltoe.Security.DataProtection.CredHubBase/Build/Steeltoe.Security.DataProtection.CredHubBase.targets deleted file mode 100644 index 9b31f9dbfb..0000000000 --- a/src/Obsolete/Steeltoe.Security.DataProtection.CredHubBase/Build/Steeltoe.Security.DataProtection.CredHubBase.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Security.DataProtection.CredHubBase/PackageReadme.md b/src/Obsolete/Steeltoe.Security.DataProtection.CredHubBase/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Security.DataProtection.CredHubBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Security.DataProtection.CredHubBase/Steeltoe.Security.DataProtection.CredHubBase.csproj b/src/Obsolete/Steeltoe.Security.DataProtection.CredHubBase/Steeltoe.Security.DataProtection.CredHubBase.csproj deleted file mode 100644 index 328a3d8393..0000000000 --- a/src/Obsolete/Steeltoe.Security.DataProtection.CredHubBase/Steeltoe.Security.DataProtection.CredHubBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - NET Client for CredHub - Base Package. - CloudFoundry;NET Core;Security;DataProtection;CredHub - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Security.DataProtection.CredHubCore/Build/Steeltoe.Security.DataProtection.CredHubCore.targets b/src/Obsolete/Steeltoe.Security.DataProtection.CredHubCore/Build/Steeltoe.Security.DataProtection.CredHubCore.targets deleted file mode 100644 index 96e580d73a..0000000000 --- a/src/Obsolete/Steeltoe.Security.DataProtection.CredHubCore/Build/Steeltoe.Security.DataProtection.CredHubCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Security.DataProtection.CredHubCore/PackageReadme.md b/src/Obsolete/Steeltoe.Security.DataProtection.CredHubCore/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Security.DataProtection.CredHubCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Security.DataProtection.CredHubCore/Steeltoe.Security.DataProtection.CredHubCore.csproj b/src/Obsolete/Steeltoe.Security.DataProtection.CredHubCore/Steeltoe.Security.DataProtection.CredHubCore.csproj deleted file mode 100644 index 6da6837b51..0000000000 --- a/src/Obsolete/Steeltoe.Security.DataProtection.CredHubCore/Steeltoe.Security.DataProtection.CredHubCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - ASP.NET Core Extensions for CredHub Client. - CloudFoundry;aspnetcore;Security;DataProtection;CredHub - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Security.DataProtection.RedisCore/Build/Steeltoe.Security.DataProtection.RedisCore.targets b/src/Obsolete/Steeltoe.Security.DataProtection.RedisCore/Build/Steeltoe.Security.DataProtection.RedisCore.targets deleted file mode 100644 index 8be8a421e6..0000000000 --- a/src/Obsolete/Steeltoe.Security.DataProtection.RedisCore/Build/Steeltoe.Security.DataProtection.RedisCore.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Security.DataProtection.RedisCore/PackageReadme.md b/src/Obsolete/Steeltoe.Security.DataProtection.RedisCore/PackageReadme.md deleted file mode 100644 index 5b8a6b20da..0000000000 --- a/src/Obsolete/Steeltoe.Security.DataProtection.RedisCore/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been superseded in Steeltoe v4. Reference `Steeltoe.Security.DataProtection.Redis` instead. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Security.DataProtection.RedisCore/Steeltoe.Security.DataProtection.RedisCore.csproj b/src/Obsolete/Steeltoe.Security.DataProtection.RedisCore/Steeltoe.Security.DataProtection.RedisCore.csproj deleted file mode 100644 index 254a8ba0ce..0000000000 --- a/src/Obsolete/Steeltoe.Security.DataProtection.RedisCore/Steeltoe.Security.DataProtection.RedisCore.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Support for storing data protection keys in Redis. - CloudFoundry;aspnetcore;security;dataprotection;redis - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Stream.Abstractions/Build/Steeltoe.Stream.Abstractions.targets b/src/Obsolete/Steeltoe.Stream.Abstractions/Build/Steeltoe.Stream.Abstractions.targets deleted file mode 100644 index 336ee776e1..0000000000 --- a/src/Obsolete/Steeltoe.Stream.Abstractions/Build/Steeltoe.Stream.Abstractions.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Stream.Abstractions/PackageReadme.md b/src/Obsolete/Steeltoe.Stream.Abstractions/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Stream.Abstractions/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Stream.Abstractions/Steeltoe.Stream.Abstractions.csproj b/src/Obsolete/Steeltoe.Stream.Abstractions/Steeltoe.Stream.Abstractions.csproj deleted file mode 100644 index 0c5e0bcac9..0000000000 --- a/src/Obsolete/Steeltoe.Stream.Abstractions/Steeltoe.Stream.Abstractions.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Abstractions for use with Steeltoe Stream. - Streams;NET Core;Spring;Spring Cloud - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Stream.Binder.RabbitMQ/Build/Steeltoe.Stream.Binder.RabbitMQ.targets b/src/Obsolete/Steeltoe.Stream.Binder.RabbitMQ/Build/Steeltoe.Stream.Binder.RabbitMQ.targets deleted file mode 100644 index 4de530b280..0000000000 --- a/src/Obsolete/Steeltoe.Stream.Binder.RabbitMQ/Build/Steeltoe.Stream.Binder.RabbitMQ.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Stream.Binder.RabbitMQ/PackageReadme.md b/src/Obsolete/Steeltoe.Stream.Binder.RabbitMQ/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Stream.Binder.RabbitMQ/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Stream.Binder.RabbitMQ/Steeltoe.Stream.Binder.RabbitMQ.csproj b/src/Obsolete/Steeltoe.Stream.Binder.RabbitMQ/Steeltoe.Stream.Binder.RabbitMQ.csproj deleted file mode 100644 index 5b5cb8e9d5..0000000000 --- a/src/Obsolete/Steeltoe.Stream.Binder.RabbitMQ/Steeltoe.Stream.Binder.RabbitMQ.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe Stream RabbitMQ Binder. - Streams;ASPNET Core;Spring;Spring Cloud;RabbitMQ;Binder - true - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Stream.StreamBase/Build/Steeltoe.Stream.StreamBase.targets b/src/Obsolete/Steeltoe.Stream.StreamBase/Build/Steeltoe.Stream.StreamBase.targets deleted file mode 100644 index 8c974c9287..0000000000 --- a/src/Obsolete/Steeltoe.Stream.StreamBase/Build/Steeltoe.Stream.StreamBase.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/Obsolete/Steeltoe.Stream.StreamBase/PackageReadme.md b/src/Obsolete/Steeltoe.Stream.StreamBase/PackageReadme.md deleted file mode 100644 index 4db75ab196..0000000000 --- a/src/Obsolete/Steeltoe.Stream.StreamBase/PackageReadme.md +++ /dev/null @@ -1,17 +0,0 @@ -# Steeltoe - -> [!IMPORTANT] -> This package has been removed from Steeltoe in v4. -> See for details. - -[Steeltoe](https://steeltoe.io/) provides building blocks for development of .NET applications that integrate with [Spring](https://spring.io/) and [Spring Boot](https://spring.io/projects/spring-boot) environments, as well as [Cloud Foundry](https://www.cloudfoundry.org/) and [Kubernetes](https://kubernetes.io/) with first-party support for [Tanzu](https://tanzu.vmware.com/tanzu). - -Key features include: - -- External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) -- Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. -- Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) -- Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) - -For more information and to get started, please visit [Steeltoe on GitHub](https://github.com/SteeltoeOSS/Steeltoe) or the [documentation](https://steeltoe.io/docs). diff --git a/src/Obsolete/Steeltoe.Stream.StreamBase/Steeltoe.Stream.StreamBase.csproj b/src/Obsolete/Steeltoe.Stream.StreamBase/Steeltoe.Stream.StreamBase.csproj deleted file mode 100644 index 0b83621d24..0000000000 --- a/src/Obsolete/Steeltoe.Stream.StreamBase/Steeltoe.Stream.StreamBase.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0 - Steeltoe Stream Base. - Streams;NET Core;Spring;Spring Cloud - true - - - - - - - - - diff --git a/src/Steeltoe.All.sln b/src/Steeltoe.All.sln index 39dd55ab9c..23230b91f9 100644 --- a/src/Steeltoe.All.sln +++ b/src/Steeltoe.All.sln @@ -204,134 +204,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Logging.Tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Management.Endpoint.RazorPagesTestWebApp", "Management\test\RazorPagesTestWebApp\Steeltoe.Management.Endpoint.RazorPagesTestWebApp.csproj", "{51DD3135-1EAA-4640-82F1-9FBECA421708}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Obsolete", "Obsolete", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Bootstrap.Autoconfig", "Obsolete\Steeltoe.Bootstrap.Autoconfig\Steeltoe.Bootstrap.Autoconfig.csproj", "{6607EBEE-8668-E1D3-C1CB-ED11A91DC100}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.CircuitBreaker.Abstractions", "Obsolete\Steeltoe.CircuitBreaker.Abstractions\Steeltoe.CircuitBreaker.Abstractions.csproj", "{5D16D6CC-2BBF-3E53-A932-7E6FCD64E123}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore", "Obsolete\Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore\Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore.csproj", "{77459A1A-63B4-F7CE-5B20-79091A4BC6C8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore", "Obsolete\Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore\Steeltoe.CircuitBreaker.Hystrix.MetricsStreamCore.csproj", "{7A06889E-2503-38D9-FE44-36527A9FD0C9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.CircuitBreaker.HystrixBase", "Obsolete\Steeltoe.CircuitBreaker.HystrixBase\Steeltoe.CircuitBreaker.HystrixBase.csproj", "{5159B5AC-6354-B394-93DD-2621DB302EDF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.CircuitBreaker.HystrixCore", "Obsolete\Steeltoe.CircuitBreaker.HystrixCore\Steeltoe.CircuitBreaker.HystrixCore.csproj", "{44D3B2AE-DA53-A134-182B-E4301BB856A3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Common.Abstractions", "Obsolete\Steeltoe.Common.Abstractions\Steeltoe.Common.Abstractions.csproj", "{55DDF80C-42C8-A046-883C-954049FCB811}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Common.Expression", "Obsolete\Steeltoe.Common.Expression\Steeltoe.Common.Expression.csproj", "{98E31A4F-30D0-0F7B-2E9D-D8F1AEB53DA5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Common.Kubernetes", "Obsolete\Steeltoe.Common.Kubernetes\Steeltoe.Common.Kubernetes.csproj", "{3F841B6E-2AEA-23B1-C141-CC67A8FB33E0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Common.Retry", "Obsolete\Steeltoe.Common.Retry\Steeltoe.Common.Retry.csproj", "{8057DA4A-FF84-72D3-54BE-A17B47760608}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Common.Security", "Obsolete\Steeltoe.Common.Security\Steeltoe.Common.Security.csproj", "{5A179D89-95C6-F103-DA7D-6FD33FDF151B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Common.Utils", "Obsolete\Steeltoe.Common.Utils\Steeltoe.Common.Utils.csproj", "{A4F19C30-3624-86C4-F533-D88C9DC2A972}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Connector.Abstractions", "Obsolete\Steeltoe.Connector.Abstractions\Steeltoe.Connector.Abstractions.csproj", "{3E41717A-789B-D213-A4A8-56BFED2E8D17}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Connector.CloudFoundry", "Obsolete\Steeltoe.Connector.CloudFoundry\Steeltoe.Connector.CloudFoundry.csproj", "{63CAD818-CAFA-41ED-E389-C7553AD813FD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Connector.ConnectorBase", "Obsolete\Steeltoe.Connector.ConnectorBase\Steeltoe.Connector.ConnectorBase.csproj", "{C6277231-D388-0D78-CC9F-973172F92585}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Connector.ConnectorCore", "Obsolete\Steeltoe.Connector.ConnectorCore\Steeltoe.Connector.ConnectorCore.csproj", "{981F5916-AB63-4E99-7762-1BC03CDD8D00}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Connector.EF6Core", "Obsolete\Steeltoe.Connector.EF6Core\Steeltoe.Connector.EF6Core.csproj", "{3105ADCB-D81E-25F2-8390-986E50E3A88B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Connector.EFCore", "Obsolete\Steeltoe.Connector.EFCore\Steeltoe.Connector.EFCore.csproj", "{2F785E38-6E2A-834A-0C9A-CBE3B307DE01}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Discovery.Abstractions", "Obsolete\Steeltoe.Discovery.Abstractions\Steeltoe.Discovery.Abstractions.csproj", "{4C3111B7-D8EC-0538-2C30-952EA4D9FC94}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Discovery.ClientBase", "Obsolete\Steeltoe.Discovery.ClientBase\Steeltoe.Discovery.ClientBase.csproj", "{2388CA32-2573-C097-0B04-B67C58BDB7CA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Discovery.ClientCore", "Obsolete\Steeltoe.Discovery.ClientCore\Steeltoe.Discovery.ClientCore.csproj", "{C5824CE4-D590-20E7-0565-6BDE1D809A19}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Discovery.Kubernetes", "Obsolete\Steeltoe.Discovery.Kubernetes\Steeltoe.Discovery.Kubernetes.csproj", "{14E1A30B-2E8A-794C-CF13-F47FDEF4ACD3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Configuration.Abstractions", "Obsolete\Steeltoe.Extensions.Configuration.Abstractions\Steeltoe.Extensions.Configuration.Abstractions.csproj", "{644C6F28-AEC7-AD30-1D65-F459E0F21DC6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Configuration.CloudFoundryBase", "Obsolete\Steeltoe.Extensions.Configuration.CloudFoundryBase\Steeltoe.Extensions.Configuration.CloudFoundryBase.csproj", "{18B7AC0F-16B9-B612-A6AA-E092EE68DA1D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Configuration.CloudFoundryCore", "Obsolete\Steeltoe.Extensions.Configuration.CloudFoundryCore\Steeltoe.Extensions.Configuration.CloudFoundryCore.csproj", "{5B7C359B-81DB-8BF9-D806-6DCEAC1A51D5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Configuration.ConfigServerBase", "Obsolete\Steeltoe.Extensions.Configuration.ConfigServerBase\Steeltoe.Extensions.Configuration.ConfigServerBase.csproj", "{EEDC48F4-14C6-4017-0CB4-1A7E2315C685}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Configuration.ConfigServerCore", "Obsolete\Steeltoe.Extensions.Configuration.ConfigServerCore\Steeltoe.Extensions.Configuration.ConfigServerCore.csproj", "{B1965821-5021-E09B-0107-9BF12D674AB1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding", "Obsolete\Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding\Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding.csproj", "{C11055B8-9185-2BF4-35E7-65C977DD5DEC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Configuration.KubernetesBase", "Obsolete\Steeltoe.Extensions.Configuration.KubernetesBase\Steeltoe.Extensions.Configuration.KubernetesBase.csproj", "{F44A6978-521A-1E7B-8841-C1BF15F80CCB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Configuration.KubernetesCore", "Obsolete\Steeltoe.Extensions.Configuration.KubernetesCore\Steeltoe.Extensions.Configuration.KubernetesCore.csproj", "{CD01D081-2759-DBD2-A98F-BE5ECE04A403}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Configuration.PlaceholderBase", "Obsolete\Steeltoe.Extensions.Configuration.PlaceholderBase\Steeltoe.Extensions.Configuration.PlaceholderBase.csproj", "{F7A34F42-9092-1D8D-BEA7-146968B3EE3A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Configuration.PlaceholderCore", "Obsolete\Steeltoe.Extensions.Configuration.PlaceholderCore\Steeltoe.Extensions.Configuration.PlaceholderCore.csproj", "{73DB7209-1957-2295-FB91-3FBC82442B01}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Configuration.RandomValueBase", "Obsolete\Steeltoe.Extensions.Configuration.RandomValueBase\Steeltoe.Extensions.Configuration.RandomValueBase.csproj", "{135C853D-B3A2-8F7C-4926-B52E9392002F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Configuration.SpringBootBase", "Obsolete\Steeltoe.Extensions.Configuration.SpringBootBase\Steeltoe.Extensions.Configuration.SpringBootBase.csproj", "{E87C0BA6-E772-68FA-34C6-64227DD9F3EE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Configuration.SpringBootCore", "Obsolete\Steeltoe.Extensions.Configuration.SpringBootCore\Steeltoe.Extensions.Configuration.SpringBootCore.csproj", "{5FFC2953-2430-8AC9-0096-3FDBC1CA5140}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Logging.Abstractions", "Obsolete\Steeltoe.Extensions.Logging.Abstractions\Steeltoe.Extensions.Logging.Abstractions.csproj", "{192D3A1C-8554-3D05-DA6E-A57030ECA124}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Logging.DynamicLogger", "Obsolete\Steeltoe.Extensions.Logging.DynamicLogger\Steeltoe.Extensions.Logging.DynamicLogger.csproj", "{31EC11DD-755D-FECE-4678-D88E17BFDBB6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Logging.DynamicSerilogBase", "Obsolete\Steeltoe.Extensions.Logging.DynamicSerilogBase\Steeltoe.Extensions.Logging.DynamicSerilogBase.csproj", "{CB988CB6-A9A2-0365-354E-1464906386FF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Extensions.Logging.DynamicSerilogCore", "Obsolete\Steeltoe.Extensions.Logging.DynamicSerilogCore\Steeltoe.Extensions.Logging.DynamicSerilogCore.csproj", "{5064A781-B3B0-585F-B856-E294556180F1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Integration.Abstractions", "Obsolete\Steeltoe.Integration.Abstractions\Steeltoe.Integration.Abstractions.csproj", "{97D530C8-97AC-CEBE-9657-CE02557D6ED9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Integration.IntegrationBase", "Obsolete\Steeltoe.Integration.IntegrationBase\Steeltoe.Integration.IntegrationBase.csproj", "{942F9B69-02CD-469B-C00C-3454E93BA2AE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Integration.RabbitMQ", "Obsolete\Steeltoe.Integration.RabbitMQ\Steeltoe.Integration.RabbitMQ.csproj", "{48145FD7-B653-CFE3-B30A-7EF2E0120B21}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Management.CloudFoundryCore", "Obsolete\Steeltoe.Management.CloudFoundryCore\Steeltoe.Management.CloudFoundryCore.csproj", "{05B5D7F9-FE77-1519-2023-61CD1FAD90B1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Management.Diagnostics", "Obsolete\Steeltoe.Management.Diagnostics\Steeltoe.Management.Diagnostics.csproj", "{1BB203B2-65E0-3834-3EB4-0BEC2F75FEBC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Management.EndpointBase", "Obsolete\Steeltoe.Management.EndpointBase\Steeltoe.Management.EndpointBase.csproj", "{E974FA60-F02F-FA1F-BA84-C25972F8DD4F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Management.EndpointCore", "Obsolete\Steeltoe.Management.EndpointCore\Steeltoe.Management.EndpointCore.csproj", "{A857F14B-45A2-8244-554B-C3205FC38F08}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Management.KubernetesCore", "Obsolete\Steeltoe.Management.KubernetesCore\Steeltoe.Management.KubernetesCore.csproj", "{A12F0AE9-DCB4-9D54-E586-FBBF10CA93F6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Management.OpenTelemetryBase", "Obsolete\Steeltoe.Management.OpenTelemetryBase\Steeltoe.Management.OpenTelemetryBase.csproj", "{56589499-4FA9-EC26-7F7E-D187D035D4DA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Management.TaskCore", "Obsolete\Steeltoe.Management.TaskCore\Steeltoe.Management.TaskCore.csproj", "{0F613620-3071-DF98-3506-F37F016C07E1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Management.TracingBase", "Obsolete\Steeltoe.Management.TracingBase\Steeltoe.Management.TracingBase.csproj", "{C45ED108-A68D-B447-00D1-BCB45EEC0D15}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Management.TracingCore", "Obsolete\Steeltoe.Management.TracingCore\Steeltoe.Management.TracingCore.csproj", "{1AFEBB7F-BFB8-2C11-28BF-68AA6376479B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Messaging.Abstractions", "Obsolete\Steeltoe.Messaging.Abstractions\Steeltoe.Messaging.Abstractions.csproj", "{63AA9B32-84B1-BE5F-4507-8E7DF73880F2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Messaging.MessagingBase", "Obsolete\Steeltoe.Messaging.MessagingBase\Steeltoe.Messaging.MessagingBase.csproj", "{83653A8C-7946-338C-F42C-AB714F49991D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Messaging.RabbitMQ", "Obsolete\Steeltoe.Messaging.RabbitMQ\Steeltoe.Messaging.RabbitMQ.csproj", "{32481813-3CE4-F4C8-086F-15FED097835F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Security.Authentication.CloudFoundryBase", "Obsolete\Steeltoe.Security.Authentication.CloudFoundryBase\Steeltoe.Security.Authentication.CloudFoundryBase.csproj", "{4F6C59C7-0311-8ADF-017D-E64F46510A70}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Security.Authentication.CloudFoundryCore", "Obsolete\Steeltoe.Security.Authentication.CloudFoundryCore\Steeltoe.Security.Authentication.CloudFoundryCore.csproj", "{FF8B81BD-63BA-BEE6-2466-B1A3EE553494}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Security.Authentication.MtlsCore", "Obsolete\Steeltoe.Security.Authentication.MtlsCore\Steeltoe.Security.Authentication.MtlsCore.csproj", "{9D7D20F4-F986-4194-9D18-4F28654EDE7D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Security.DataProtection.CredHubBase", "Obsolete\Steeltoe.Security.DataProtection.CredHubBase\Steeltoe.Security.DataProtection.CredHubBase.csproj", "{A90ADACF-3BC4-3C34-F6D6-D02D99C799D5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Security.DataProtection.CredHubCore", "Obsolete\Steeltoe.Security.DataProtection.CredHubCore\Steeltoe.Security.DataProtection.CredHubCore.csproj", "{75087953-E456-EA40-857F-B38A2BF4DD1D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Security.DataProtection.RedisCore", "Obsolete\Steeltoe.Security.DataProtection.RedisCore\Steeltoe.Security.DataProtection.RedisCore.csproj", "{CF879664-186D-AE54-434E-F0FC3B5D27DC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Stream.Abstractions", "Obsolete\Steeltoe.Stream.Abstractions\Steeltoe.Stream.Abstractions.csproj", "{D377B3DB-B229-DC7A-D651-320B5904BC29}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Stream.Binder.RabbitMQ", "Obsolete\Steeltoe.Stream.Binder.RabbitMQ\Steeltoe.Stream.Binder.RabbitMQ.csproj", "{F42A9BC5-DA3E-A1CE-B48E-7EBF59D8D161}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Stream.StreamBase", "Obsolete\Steeltoe.Stream.StreamBase\Steeltoe.Stream.StreamBase.csproj", "{51FBF981-91B2-407E-7A10-D28E5FB717F4}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -618,195 +490,6 @@ Global {51DD3135-1EAA-4640-82F1-9FBECA421708}.Debug|Any CPU.Build.0 = Debug|Any CPU {51DD3135-1EAA-4640-82F1-9FBECA421708}.Release|Any CPU.ActiveCfg = Release|Any CPU {51DD3135-1EAA-4640-82F1-9FBECA421708}.Release|Any CPU.Build.0 = Release|Any CPU - {6607EBEE-8668-E1D3-C1CB-ED11A91DC100}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6607EBEE-8668-E1D3-C1CB-ED11A91DC100}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6607EBEE-8668-E1D3-C1CB-ED11A91DC100}.Release|Any CPU.Build.0 = Release|Any CPU - {5D16D6CC-2BBF-3E53-A932-7E6FCD64E123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5D16D6CC-2BBF-3E53-A932-7E6FCD64E123}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5D16D6CC-2BBF-3E53-A932-7E6FCD64E123}.Release|Any CPU.Build.0 = Release|Any CPU - {77459A1A-63B4-F7CE-5B20-79091A4BC6C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77459A1A-63B4-F7CE-5B20-79091A4BC6C8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77459A1A-63B4-F7CE-5B20-79091A4BC6C8}.Release|Any CPU.Build.0 = Release|Any CPU - {7A06889E-2503-38D9-FE44-36527A9FD0C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7A06889E-2503-38D9-FE44-36527A9FD0C9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7A06889E-2503-38D9-FE44-36527A9FD0C9}.Release|Any CPU.Build.0 = Release|Any CPU - {5159B5AC-6354-B394-93DD-2621DB302EDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5159B5AC-6354-B394-93DD-2621DB302EDF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5159B5AC-6354-B394-93DD-2621DB302EDF}.Release|Any CPU.Build.0 = Release|Any CPU - {44D3B2AE-DA53-A134-182B-E4301BB856A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {44D3B2AE-DA53-A134-182B-E4301BB856A3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {44D3B2AE-DA53-A134-182B-E4301BB856A3}.Release|Any CPU.Build.0 = Release|Any CPU - {55DDF80C-42C8-A046-883C-954049FCB811}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {55DDF80C-42C8-A046-883C-954049FCB811}.Release|Any CPU.ActiveCfg = Release|Any CPU - {55DDF80C-42C8-A046-883C-954049FCB811}.Release|Any CPU.Build.0 = Release|Any CPU - {98E31A4F-30D0-0F7B-2E9D-D8F1AEB53DA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {98E31A4F-30D0-0F7B-2E9D-D8F1AEB53DA5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {98E31A4F-30D0-0F7B-2E9D-D8F1AEB53DA5}.Release|Any CPU.Build.0 = Release|Any CPU - {3F841B6E-2AEA-23B1-C141-CC67A8FB33E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3F841B6E-2AEA-23B1-C141-CC67A8FB33E0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3F841B6E-2AEA-23B1-C141-CC67A8FB33E0}.Release|Any CPU.Build.0 = Release|Any CPU - {8057DA4A-FF84-72D3-54BE-A17B47760608}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8057DA4A-FF84-72D3-54BE-A17B47760608}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8057DA4A-FF84-72D3-54BE-A17B47760608}.Release|Any CPU.Build.0 = Release|Any CPU - {5A179D89-95C6-F103-DA7D-6FD33FDF151B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5A179D89-95C6-F103-DA7D-6FD33FDF151B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5A179D89-95C6-F103-DA7D-6FD33FDF151B}.Release|Any CPU.Build.0 = Release|Any CPU - {A4F19C30-3624-86C4-F533-D88C9DC2A972}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4F19C30-3624-86C4-F533-D88C9DC2A972}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A4F19C30-3624-86C4-F533-D88C9DC2A972}.Release|Any CPU.Build.0 = Release|Any CPU - {3E41717A-789B-D213-A4A8-56BFED2E8D17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3E41717A-789B-D213-A4A8-56BFED2E8D17}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3E41717A-789B-D213-A4A8-56BFED2E8D17}.Release|Any CPU.Build.0 = Release|Any CPU - {63CAD818-CAFA-41ED-E389-C7553AD813FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63CAD818-CAFA-41ED-E389-C7553AD813FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63CAD818-CAFA-41ED-E389-C7553AD813FD}.Release|Any CPU.Build.0 = Release|Any CPU - {C6277231-D388-0D78-CC9F-973172F92585}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C6277231-D388-0D78-CC9F-973172F92585}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C6277231-D388-0D78-CC9F-973172F92585}.Release|Any CPU.Build.0 = Release|Any CPU - {981F5916-AB63-4E99-7762-1BC03CDD8D00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {981F5916-AB63-4E99-7762-1BC03CDD8D00}.Release|Any CPU.ActiveCfg = Release|Any CPU - {981F5916-AB63-4E99-7762-1BC03CDD8D00}.Release|Any CPU.Build.0 = Release|Any CPU - {3105ADCB-D81E-25F2-8390-986E50E3A88B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3105ADCB-D81E-25F2-8390-986E50E3A88B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3105ADCB-D81E-25F2-8390-986E50E3A88B}.Release|Any CPU.Build.0 = Release|Any CPU - {2F785E38-6E2A-834A-0C9A-CBE3B307DE01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F785E38-6E2A-834A-0C9A-CBE3B307DE01}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F785E38-6E2A-834A-0C9A-CBE3B307DE01}.Release|Any CPU.Build.0 = Release|Any CPU - {4C3111B7-D8EC-0538-2C30-952EA4D9FC94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C3111B7-D8EC-0538-2C30-952EA4D9FC94}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C3111B7-D8EC-0538-2C30-952EA4D9FC94}.Release|Any CPU.Build.0 = Release|Any CPU - {2388CA32-2573-C097-0B04-B67C58BDB7CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2388CA32-2573-C097-0B04-B67C58BDB7CA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2388CA32-2573-C097-0B04-B67C58BDB7CA}.Release|Any CPU.Build.0 = Release|Any CPU - {C5824CE4-D590-20E7-0565-6BDE1D809A19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C5824CE4-D590-20E7-0565-6BDE1D809A19}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C5824CE4-D590-20E7-0565-6BDE1D809A19}.Release|Any CPU.Build.0 = Release|Any CPU - {14E1A30B-2E8A-794C-CF13-F47FDEF4ACD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14E1A30B-2E8A-794C-CF13-F47FDEF4ACD3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14E1A30B-2E8A-794C-CF13-F47FDEF4ACD3}.Release|Any CPU.Build.0 = Release|Any CPU - {644C6F28-AEC7-AD30-1D65-F459E0F21DC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {644C6F28-AEC7-AD30-1D65-F459E0F21DC6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {644C6F28-AEC7-AD30-1D65-F459E0F21DC6}.Release|Any CPU.Build.0 = Release|Any CPU - {18B7AC0F-16B9-B612-A6AA-E092EE68DA1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {18B7AC0F-16B9-B612-A6AA-E092EE68DA1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {18B7AC0F-16B9-B612-A6AA-E092EE68DA1D}.Release|Any CPU.Build.0 = Release|Any CPU - {5B7C359B-81DB-8BF9-D806-6DCEAC1A51D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B7C359B-81DB-8BF9-D806-6DCEAC1A51D5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B7C359B-81DB-8BF9-D806-6DCEAC1A51D5}.Release|Any CPU.Build.0 = Release|Any CPU - {EEDC48F4-14C6-4017-0CB4-1A7E2315C685}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EEDC48F4-14C6-4017-0CB4-1A7E2315C685}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EEDC48F4-14C6-4017-0CB4-1A7E2315C685}.Release|Any CPU.Build.0 = Release|Any CPU - {B1965821-5021-E09B-0107-9BF12D674AB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B1965821-5021-E09B-0107-9BF12D674AB1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B1965821-5021-E09B-0107-9BF12D674AB1}.Release|Any CPU.Build.0 = Release|Any CPU - {C11055B8-9185-2BF4-35E7-65C977DD5DEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C11055B8-9185-2BF4-35E7-65C977DD5DEC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C11055B8-9185-2BF4-35E7-65C977DD5DEC}.Release|Any CPU.Build.0 = Release|Any CPU - {F44A6978-521A-1E7B-8841-C1BF15F80CCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F44A6978-521A-1E7B-8841-C1BF15F80CCB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F44A6978-521A-1E7B-8841-C1BF15F80CCB}.Release|Any CPU.Build.0 = Release|Any CPU - {CD01D081-2759-DBD2-A98F-BE5ECE04A403}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD01D081-2759-DBD2-A98F-BE5ECE04A403}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD01D081-2759-DBD2-A98F-BE5ECE04A403}.Release|Any CPU.Build.0 = Release|Any CPU - {F7A34F42-9092-1D8D-BEA7-146968B3EE3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F7A34F42-9092-1D8D-BEA7-146968B3EE3A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F7A34F42-9092-1D8D-BEA7-146968B3EE3A}.Release|Any CPU.Build.0 = Release|Any CPU - {73DB7209-1957-2295-FB91-3FBC82442B01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {73DB7209-1957-2295-FB91-3FBC82442B01}.Release|Any CPU.ActiveCfg = Release|Any CPU - {73DB7209-1957-2295-FB91-3FBC82442B01}.Release|Any CPU.Build.0 = Release|Any CPU - {135C853D-B3A2-8F7C-4926-B52E9392002F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {135C853D-B3A2-8F7C-4926-B52E9392002F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {135C853D-B3A2-8F7C-4926-B52E9392002F}.Release|Any CPU.Build.0 = Release|Any CPU - {E87C0BA6-E772-68FA-34C6-64227DD9F3EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E87C0BA6-E772-68FA-34C6-64227DD9F3EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E87C0BA6-E772-68FA-34C6-64227DD9F3EE}.Release|Any CPU.Build.0 = Release|Any CPU - {5FFC2953-2430-8AC9-0096-3FDBC1CA5140}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5FFC2953-2430-8AC9-0096-3FDBC1CA5140}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5FFC2953-2430-8AC9-0096-3FDBC1CA5140}.Release|Any CPU.Build.0 = Release|Any CPU - {192D3A1C-8554-3D05-DA6E-A57030ECA124}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {192D3A1C-8554-3D05-DA6E-A57030ECA124}.Release|Any CPU.ActiveCfg = Release|Any CPU - {192D3A1C-8554-3D05-DA6E-A57030ECA124}.Release|Any CPU.Build.0 = Release|Any CPU - {31EC11DD-755D-FECE-4678-D88E17BFDBB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {31EC11DD-755D-FECE-4678-D88E17BFDBB6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {31EC11DD-755D-FECE-4678-D88E17BFDBB6}.Release|Any CPU.Build.0 = Release|Any CPU - {CB988CB6-A9A2-0365-354E-1464906386FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CB988CB6-A9A2-0365-354E-1464906386FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CB988CB6-A9A2-0365-354E-1464906386FF}.Release|Any CPU.Build.0 = Release|Any CPU - {5064A781-B3B0-585F-B856-E294556180F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5064A781-B3B0-585F-B856-E294556180F1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5064A781-B3B0-585F-B856-E294556180F1}.Release|Any CPU.Build.0 = Release|Any CPU - {97D530C8-97AC-CEBE-9657-CE02557D6ED9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {97D530C8-97AC-CEBE-9657-CE02557D6ED9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {97D530C8-97AC-CEBE-9657-CE02557D6ED9}.Release|Any CPU.Build.0 = Release|Any CPU - {942F9B69-02CD-469B-C00C-3454E93BA2AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {942F9B69-02CD-469B-C00C-3454E93BA2AE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {942F9B69-02CD-469B-C00C-3454E93BA2AE}.Release|Any CPU.Build.0 = Release|Any CPU - {48145FD7-B653-CFE3-B30A-7EF2E0120B21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {48145FD7-B653-CFE3-B30A-7EF2E0120B21}.Release|Any CPU.ActiveCfg = Release|Any CPU - {48145FD7-B653-CFE3-B30A-7EF2E0120B21}.Release|Any CPU.Build.0 = Release|Any CPU - {05B5D7F9-FE77-1519-2023-61CD1FAD90B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {05B5D7F9-FE77-1519-2023-61CD1FAD90B1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {05B5D7F9-FE77-1519-2023-61CD1FAD90B1}.Release|Any CPU.Build.0 = Release|Any CPU - {1BB203B2-65E0-3834-3EB4-0BEC2F75FEBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1BB203B2-65E0-3834-3EB4-0BEC2F75FEBC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1BB203B2-65E0-3834-3EB4-0BEC2F75FEBC}.Release|Any CPU.Build.0 = Release|Any CPU - {E974FA60-F02F-FA1F-BA84-C25972F8DD4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E974FA60-F02F-FA1F-BA84-C25972F8DD4F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E974FA60-F02F-FA1F-BA84-C25972F8DD4F}.Release|Any CPU.Build.0 = Release|Any CPU - {A857F14B-45A2-8244-554B-C3205FC38F08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A857F14B-45A2-8244-554B-C3205FC38F08}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A857F14B-45A2-8244-554B-C3205FC38F08}.Release|Any CPU.Build.0 = Release|Any CPU - {A12F0AE9-DCB4-9D54-E586-FBBF10CA93F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A12F0AE9-DCB4-9D54-E586-FBBF10CA93F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A12F0AE9-DCB4-9D54-E586-FBBF10CA93F6}.Release|Any CPU.Build.0 = Release|Any CPU - {56589499-4FA9-EC26-7F7E-D187D035D4DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {56589499-4FA9-EC26-7F7E-D187D035D4DA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {56589499-4FA9-EC26-7F7E-D187D035D4DA}.Release|Any CPU.Build.0 = Release|Any CPU - {0F613620-3071-DF98-3506-F37F016C07E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0F613620-3071-DF98-3506-F37F016C07E1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0F613620-3071-DF98-3506-F37F016C07E1}.Release|Any CPU.Build.0 = Release|Any CPU - {C45ED108-A68D-B447-00D1-BCB45EEC0D15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C45ED108-A68D-B447-00D1-BCB45EEC0D15}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C45ED108-A68D-B447-00D1-BCB45EEC0D15}.Release|Any CPU.Build.0 = Release|Any CPU - {1AFEBB7F-BFB8-2C11-28BF-68AA6376479B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1AFEBB7F-BFB8-2C11-28BF-68AA6376479B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1AFEBB7F-BFB8-2C11-28BF-68AA6376479B}.Release|Any CPU.Build.0 = Release|Any CPU - {63AA9B32-84B1-BE5F-4507-8E7DF73880F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63AA9B32-84B1-BE5F-4507-8E7DF73880F2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63AA9B32-84B1-BE5F-4507-8E7DF73880F2}.Release|Any CPU.Build.0 = Release|Any CPU - {83653A8C-7946-338C-F42C-AB714F49991D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {83653A8C-7946-338C-F42C-AB714F49991D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {83653A8C-7946-338C-F42C-AB714F49991D}.Release|Any CPU.Build.0 = Release|Any CPU - {32481813-3CE4-F4C8-086F-15FED097835F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {32481813-3CE4-F4C8-086F-15FED097835F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {32481813-3CE4-F4C8-086F-15FED097835F}.Release|Any CPU.Build.0 = Release|Any CPU - {4F6C59C7-0311-8ADF-017D-E64F46510A70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4F6C59C7-0311-8ADF-017D-E64F46510A70}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4F6C59C7-0311-8ADF-017D-E64F46510A70}.Release|Any CPU.Build.0 = Release|Any CPU - {FF8B81BD-63BA-BEE6-2466-B1A3EE553494}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FF8B81BD-63BA-BEE6-2466-B1A3EE553494}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FF8B81BD-63BA-BEE6-2466-B1A3EE553494}.Release|Any CPU.Build.0 = Release|Any CPU - {9D7D20F4-F986-4194-9D18-4F28654EDE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9D7D20F4-F986-4194-9D18-4F28654EDE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9D7D20F4-F986-4194-9D18-4F28654EDE7D}.Release|Any CPU.Build.0 = Release|Any CPU - {A90ADACF-3BC4-3C34-F6D6-D02D99C799D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A90ADACF-3BC4-3C34-F6D6-D02D99C799D5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A90ADACF-3BC4-3C34-F6D6-D02D99C799D5}.Release|Any CPU.Build.0 = Release|Any CPU - {75087953-E456-EA40-857F-B38A2BF4DD1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {75087953-E456-EA40-857F-B38A2BF4DD1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {75087953-E456-EA40-857F-B38A2BF4DD1D}.Release|Any CPU.Build.0 = Release|Any CPU - {CF879664-186D-AE54-434E-F0FC3B5D27DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CF879664-186D-AE54-434E-F0FC3B5D27DC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CF879664-186D-AE54-434E-F0FC3B5D27DC}.Release|Any CPU.Build.0 = Release|Any CPU - {D377B3DB-B229-DC7A-D651-320B5904BC29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D377B3DB-B229-DC7A-D651-320B5904BC29}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D377B3DB-B229-DC7A-D651-320B5904BC29}.Release|Any CPU.Build.0 = Release|Any CPU - {F42A9BC5-DA3E-A1CE-B48E-7EBF59D8D161}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F42A9BC5-DA3E-A1CE-B48E-7EBF59D8D161}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F42A9BC5-DA3E-A1CE-B48E-7EBF59D8D161}.Release|Any CPU.Build.0 = Release|Any CPU - {51FBF981-91B2-407E-7A10-D28E5FB717F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {51FBF981-91B2-407E-7A10-D28E5FB717F4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {51FBF981-91B2-407E-7A10-D28E5FB717F4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -882,69 +565,6 @@ Global {738AFF97-B4C4-4EAC-B9C5-C405D481C92B} = {59874241-E276-4035-B31D-14924889A1C9} {A9CCC214-212A-4296-98F5-65ADDB2BB8B4} = {59874241-E276-4035-B31D-14924889A1C9} {51DD3135-1EAA-4640-82F1-9FBECA421708} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} - {6607EBEE-8668-E1D3-C1CB-ED11A91DC100} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {5D16D6CC-2BBF-3E53-A932-7E6FCD64E123} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {77459A1A-63B4-F7CE-5B20-79091A4BC6C8} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {7A06889E-2503-38D9-FE44-36527A9FD0C9} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {5159B5AC-6354-B394-93DD-2621DB302EDF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {44D3B2AE-DA53-A134-182B-E4301BB856A3} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {55DDF80C-42C8-A046-883C-954049FCB811} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {98E31A4F-30D0-0F7B-2E9D-D8F1AEB53DA5} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {3F841B6E-2AEA-23B1-C141-CC67A8FB33E0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {8057DA4A-FF84-72D3-54BE-A17B47760608} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {5A179D89-95C6-F103-DA7D-6FD33FDF151B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {A4F19C30-3624-86C4-F533-D88C9DC2A972} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {3E41717A-789B-D213-A4A8-56BFED2E8D17} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {63CAD818-CAFA-41ED-E389-C7553AD813FD} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {C6277231-D388-0D78-CC9F-973172F92585} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {981F5916-AB63-4E99-7762-1BC03CDD8D00} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {3105ADCB-D81E-25F2-8390-986E50E3A88B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {2F785E38-6E2A-834A-0C9A-CBE3B307DE01} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {4C3111B7-D8EC-0538-2C30-952EA4D9FC94} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {2388CA32-2573-C097-0B04-B67C58BDB7CA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {C5824CE4-D590-20E7-0565-6BDE1D809A19} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {14E1A30B-2E8A-794C-CF13-F47FDEF4ACD3} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {644C6F28-AEC7-AD30-1D65-F459E0F21DC6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {18B7AC0F-16B9-B612-A6AA-E092EE68DA1D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {5B7C359B-81DB-8BF9-D806-6DCEAC1A51D5} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {EEDC48F4-14C6-4017-0CB4-1A7E2315C685} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {B1965821-5021-E09B-0107-9BF12D674AB1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {C11055B8-9185-2BF4-35E7-65C977DD5DEC} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {F44A6978-521A-1E7B-8841-C1BF15F80CCB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {CD01D081-2759-DBD2-A98F-BE5ECE04A403} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {F7A34F42-9092-1D8D-BEA7-146968B3EE3A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {73DB7209-1957-2295-FB91-3FBC82442B01} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {135C853D-B3A2-8F7C-4926-B52E9392002F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {E87C0BA6-E772-68FA-34C6-64227DD9F3EE} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {5FFC2953-2430-8AC9-0096-3FDBC1CA5140} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {192D3A1C-8554-3D05-DA6E-A57030ECA124} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {31EC11DD-755D-FECE-4678-D88E17BFDBB6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {CB988CB6-A9A2-0365-354E-1464906386FF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {5064A781-B3B0-585F-B856-E294556180F1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {97D530C8-97AC-CEBE-9657-CE02557D6ED9} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {942F9B69-02CD-469B-C00C-3454E93BA2AE} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {48145FD7-B653-CFE3-B30A-7EF2E0120B21} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {05B5D7F9-FE77-1519-2023-61CD1FAD90B1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {1BB203B2-65E0-3834-3EB4-0BEC2F75FEBC} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {E974FA60-F02F-FA1F-BA84-C25972F8DD4F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {A857F14B-45A2-8244-554B-C3205FC38F08} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {A12F0AE9-DCB4-9D54-E586-FBBF10CA93F6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {56589499-4FA9-EC26-7F7E-D187D035D4DA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {0F613620-3071-DF98-3506-F37F016C07E1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {C45ED108-A68D-B447-00D1-BCB45EEC0D15} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {1AFEBB7F-BFB8-2C11-28BF-68AA6376479B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {63AA9B32-84B1-BE5F-4507-8E7DF73880F2} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {83653A8C-7946-338C-F42C-AB714F49991D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {32481813-3CE4-F4C8-086F-15FED097835F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {4F6C59C7-0311-8ADF-017D-E64F46510A70} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {FF8B81BD-63BA-BEE6-2466-B1A3EE553494} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {9D7D20F4-F986-4194-9D18-4F28654EDE7D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {A90ADACF-3BC4-3C34-F6D6-D02D99C799D5} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {75087953-E456-EA40-857F-B38A2BF4DD1D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {CF879664-186D-AE54-434E-F0FC3B5D27DC} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {D377B3DB-B229-DC7A-D651-320B5904BC29} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {F42A9BC5-DA3E-A1CE-B48E-7EBF59D8D161} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {51FBF981-91B2-407E-7A10-D28E5FB717F4} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AB0245B9-2464-47F8-BE15-D80A7A2FA965} From c78a34280f103ee3ee7722697b76e8296b5464e7 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 5 Sep 2025 08:37:20 -0500 Subject: [PATCH 03/81] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 86d1f97917..051a22ffab 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ In addition to the [feature documentation](https://steeltoe.io/api), we have bui | --- | --- | | 4.x | .NET 8 - 9 | | 3.x | .NET Core 3.1 - .NET 8 | -| 3.x (Steeltoe.Stream) | .NET Core 3.1 - .NET 6 | +| 3.x (Integration, Messaging and Stream) | .NET Core 3.1 - .NET 6 | | 2.x | .NET Framework 4.6.1+ | For more details, see [Supported Versions on the Wiki](https://github.com/SteeltoeOSS/Steeltoe/wiki/Steeltoe-Support-Versions). From a8c3a98cb3ef8c6513a121c11fe62383e1244411 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 24 Sep 2025 06:09:23 +0200 Subject: [PATCH 04/81] Fixed: Without any configuration, an app listening on both https and http would be registered as non-secure (but with the https port number) in Consul (#1596) --- .../Configuration/ConsulDiscoveryOptions.cs | 5 +++-- .../src/Consul/ConfigurationSchema.json | 2 +- .../PostConfigureConsulDiscoveryOptions.cs | 18 +++++++++++------- .../src/Consul/Registry/ConsulRegistration.cs | 8 ++++---- .../PostConfigureConsulDiscoveryOptionsTest.cs | 3 ++- .../Registry/ConsulRegistrationTest.cs | 2 +- 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs b/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs index 430a479aa8..b0758008e8 100644 --- a/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs +++ b/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs @@ -15,6 +15,7 @@ public sealed class ConsulDiscoveryOptions internal bool IsHeartbeatEnabled => Heartbeat is { Enabled: true }; internal bool IsRetryEnabled => Retry is { Enabled: true }; + internal string EffectiveScheme => Scheme ?? "http"; /// /// Gets or sets a value indicating whether to enable the Consul client. Default value: true. @@ -58,9 +59,9 @@ public sealed class ConsulDiscoveryOptions public bool QueryPassing { get; set; } = true; /// - /// Gets or sets the scheme to register the running app with ("http" or "https"). Default value: http. + /// Gets or sets the scheme to register the running app with ("http" or "https"). /// - public string? Scheme { get; set; } = "http"; + public string? Scheme { get; set; } /// /// Gets or sets a value indicating whether to enable periodic health checking for the running app. Default value: true. diff --git a/src/Discovery/src/Consul/ConfigurationSchema.json b/src/Discovery/src/Consul/ConfigurationSchema.json index 74e9bf9afe..3111a531f0 100644 --- a/src/Discovery/src/Consul/ConfigurationSchema.json +++ b/src/Discovery/src/Consul/ConfigurationSchema.json @@ -173,7 +173,7 @@ }, "Scheme": { "type": "string", - "description": "Gets or sets the scheme to register the running app with (\"http\" or \"https\"). Default value: http." + "description": "Gets or sets the scheme to register the running app with (\"http\" or \"https\")." }, "ServiceName": { "type": "string", diff --git a/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs b/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs index 72c3a55856..6ad1371335 100644 --- a/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs +++ b/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs @@ -65,7 +65,7 @@ public void PostConfigure(string? name, ConsulDiscoveryOptions options) if (options.Port == 0) { ICollection addresses = _configuration.GetListenAddresses(); - SetPortsFromListenAddresses(options, addresses); + SetSchemeWithPortFromListenAddresses(options, addresses); } options.InstanceId = GetInstanceId(options); @@ -77,11 +77,11 @@ private string GetServiceName(ConsulDiscoveryOptions options) return NormalizeForConsul(serviceName, nameof(ConsulDiscoveryOptions.ServiceName)); } - private void SetPortsFromListenAddresses(ConsulDiscoveryOptions options, IEnumerable listenOnAddresses) + private void SetSchemeWithPortFromListenAddresses(ConsulDiscoveryOptions options, IEnumerable listenOnAddresses) { // Try to pull some values out of server configuration to override defaults, but only if not using NetUtils. // If NetUtils are configured, the user probably wants to define their own behavior. - if (options is { UseAspNetCoreUrls: true, Port: 0 }) + if (options is { UseAspNetCoreUrls: true, Port: 0, Scheme: null }) { int? listenHttpPort = null; int? listenHttpsPort = null; @@ -100,11 +100,15 @@ private void SetPortsFromListenAddresses(ConsulDiscoveryOptions options, IEnumer } } - int? listenPort = listenHttpsPort ?? listenHttpPort; - - if (listenPort != null) + if (listenHttpsPort != null) + { + options.Port = listenHttpsPort.Value; + options.Scheme = "https"; + } + else if (listenHttpPort != null) { - options.Port = listenPort.Value; + options.Port = listenHttpPort.Value; + options.Scheme = "http"; } } } diff --git a/src/Discovery/src/Consul/Registry/ConsulRegistration.cs b/src/Discovery/src/Consul/Registry/ConsulRegistration.cs index d51902e6d7..74c0dcf67d 100644 --- a/src/Discovery/src/Consul/Registry/ConsulRegistration.cs +++ b/src/Discovery/src/Consul/Registry/ConsulRegistration.cs @@ -34,10 +34,10 @@ internal sealed class ConsulRegistration : IServiceInstance public int Port { get; } /// - public bool IsSecure => _optionsMonitor.CurrentValue.Scheme == "https"; + public bool IsSecure => _optionsMonitor.CurrentValue.EffectiveScheme == "https"; /// - public Uri Uri => new($"{_optionsMonitor.CurrentValue.Scheme}://{Host}:{Port}"); + public Uri Uri => new($"{_optionsMonitor.CurrentValue.EffectiveScheme}://{Host}:{Port}"); public IReadOnlyList Tags { get; } @@ -114,7 +114,7 @@ private static Dictionary CreateMetadata(ConsulDiscoveryOptions } // store the secure flag in the metadata so that clients will be able to figure out whether to use http or https automatically - metadata.TryAdd("secure", options.Scheme == "https" ? "true" : "false"); + metadata.TryAdd("secure", options.EffectiveScheme == "https" ? "true" : "false"); return metadata; } @@ -145,7 +145,7 @@ public static AgentServiceCheck CreateCheck(int port, ConsulDiscoveryOptions opt } else { - var uri = new Uri($"{options.Scheme}://{options.HostName}:{port}{options.HealthCheckPath}"); + var uri = new Uri($"{options.EffectiveScheme}://{options.HostName}:{port}{options.HealthCheckPath}"); check.HTTP = uri.ToString(); } diff --git a/src/Discovery/test/Consul.Test/Discovery/PostConfigureConsulDiscoveryOptionsTest.cs b/src/Discovery/test/Consul.Test/Discovery/PostConfigureConsulDiscoveryOptionsTest.cs index aaf7cf0f20..a09e3c2f1e 100644 --- a/src/Discovery/test/Consul.Test/Discovery/PostConfigureConsulDiscoveryOptionsTest.cs +++ b/src/Discovery/test/Consul.Test/Discovery/PostConfigureConsulDiscoveryOptionsTest.cs @@ -41,7 +41,8 @@ public void Constructor_InitializesDefaults() options.InstanceZone.Should().BeNull(); options.PreferIPAddress.Should().BeFalse(); options.QueryPassing.Should().BeTrue(); - options.Scheme.Should().Be("http"); + options.Scheme.Should().BeNull(); + options.EffectiveScheme.Should().Be("http"); options.ServiceName.Should().BeNull(); options.Tags.Should().BeEmpty(); options.Metadata.Should().BeEmpty(); diff --git a/src/Discovery/test/Consul.Test/Registry/ConsulRegistrationTest.cs b/src/Discovery/test/Consul.Test/Registry/ConsulRegistrationTest.cs index ef8854a771..c3c1d90880 100644 --- a/src/Discovery/test/Consul.Test/Registry/ConsulRegistrationTest.cs +++ b/src/Discovery/test/Consul.Test/Registry/ConsulRegistrationTest.cs @@ -161,7 +161,7 @@ public void CreateCheck_ReturnsExpected() options.Heartbeat = null; const int port = 1234; result = ConsulRegistration.CreateCheck(port, options); - var uri = new Uri($"{options.Scheme}://{options.HostName}:{port}{options.HealthCheckPath}"); + var uri = new Uri($"{options.EffectiveScheme}://{options.HostName}:{port}{options.HealthCheckPath}"); result.HTTP.Should().Be(uri.ToString()); result.Interval.Should().Be(DateTimeConversions.ToTimeSpan(options.HealthCheckInterval!)); From 6f7dfa20a394a14bd605e4bb292925570c646e5a Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 24 Sep 2025 16:12:51 +0200 Subject: [PATCH 05/81] Service Discovery: Expose Instance ID in IServiceInstance (#1597) --- src/Common/src/Common/Discovery/IServiceInstance.cs | 5 +++++ src/Common/src/Common/PublicAPI.Unshipped.txt | 1 + .../ConfigServerConfigurationProviderTest.cs | 7 ++++--- .../src/Configuration/ConfigurationServiceInstance.cs | 3 +++ src/Discovery/src/Configuration/PublicAPI.Unshipped.txt | 1 + src/Discovery/src/Consul/ConsulServiceInstance.cs | 4 ++++ src/Discovery/src/Consul/Registry/ConsulRegistration.cs | 4 +--- src/Discovery/src/Consul/ThisServiceInstance.cs | 4 ++++ src/Discovery/src/Eureka/EurekaServiceInstance.cs | 2 ++ .../HttpClients/LoadBalancers/ServiceInstancesResolver.cs | 2 ++ .../Consul.Test/Discovery/ConsulDiscoveryClientTest.cs | 8 ++++++++ .../Consul.Test/Discovery/ConsulServiceInstanceTest.cs | 2 ++ .../test/Consul.Test/Discovery/ThisServiceInstanceTest.cs | 1 + .../test/Eureka.Test/EurekaDiscoveryClientTest.cs | 1 + .../test/Eureka.Test/EurekaServiceInstanceTest.cs | 1 + .../LoadBalancers/RoundRobinLoadBalancerTest.cs | 1 + 16 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/Common/src/Common/Discovery/IServiceInstance.cs b/src/Common/src/Common/Discovery/IServiceInstance.cs index 8f23b8ef3d..8a1c5cac8c 100644 --- a/src/Common/src/Common/Discovery/IServiceInstance.cs +++ b/src/Common/src/Common/Discovery/IServiceInstance.cs @@ -11,6 +11,11 @@ public interface IServiceInstance /// string ServiceId { get; } + /// + /// Gets the instance ID as registered by the discovery client. + /// + string InstanceId { get; } + /// /// Gets the hostname of the registered service instance. /// diff --git a/src/Common/src/Common/PublicAPI.Unshipped.txt b/src/Common/src/Common/PublicAPI.Unshipped.txt index 7dc5c58110..0c34fd435f 100644 --- a/src/Common/src/Common/PublicAPI.Unshipped.txt +++ b/src/Common/src/Common/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +Steeltoe.Common.Discovery.IServiceInstance.InstanceId.get -> string! diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs index 6638ca8c82..7bbfb0f21b 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs @@ -373,8 +373,8 @@ public void UpdateSettingsFromDiscovery_UpdatesSettingsCorrectly() List instances = [ - new TestServiceInstance("i1", new Uri("https://foo.bar:8888/"), metadata1), - new TestServiceInstance("i2", new Uri("https://foo.bar.baz:9999/"), metadata2) + new TestServiceInstance("s", "i1", new Uri("https://foo.bar:8888/"), metadata1), + new TestServiceInstance("s", "i2", new Uri("https://foo.bar.baz:9999/"), metadata2) ]; provider.UpdateSettingsFromDiscovery(instances, options); @@ -416,9 +416,10 @@ private static string GetEncodedUserPassword(string user, string password) return Convert.ToBase64String(Encoding.ASCII.GetBytes($"{user}:{password}")); } - private sealed class TestServiceInstance(string serviceId, Uri uri, IReadOnlyDictionary metadata) : IServiceInstance + private sealed class TestServiceInstance(string serviceId, string instanceId, Uri uri, IReadOnlyDictionary metadata) : IServiceInstance { public string ServiceId { get; } = serviceId; + public string InstanceId { get; } = instanceId; public string Host { get; } = uri.Host; public int Port { get; } = uri.Port; public bool IsSecure { get; } = uri.Scheme == Uri.UriSchemeHttps; diff --git a/src/Discovery/src/Configuration/ConfigurationServiceInstance.cs b/src/Discovery/src/Configuration/ConfigurationServiceInstance.cs index 4b96049165..f5ac148f17 100644 --- a/src/Discovery/src/Configuration/ConfigurationServiceInstance.cs +++ b/src/Discovery/src/Configuration/ConfigurationServiceInstance.cs @@ -22,6 +22,9 @@ public sealed class ConfigurationServiceInstance : IServiceInstance [Required] public string? ServiceId { get; set; } + /// + public string InstanceId => string.Empty; + /// [Required] public string? Host { get; set; } diff --git a/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt b/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt index 7dc5c58110..8a7cac8051 100644 --- a/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt +++ b/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.InstanceId.get -> string! diff --git a/src/Discovery/src/Consul/ConsulServiceInstance.cs b/src/Discovery/src/Consul/ConsulServiceInstance.cs index 5c31174d2f..bc38ec621f 100644 --- a/src/Discovery/src/Consul/ConsulServiceInstance.cs +++ b/src/Discovery/src/Consul/ConsulServiceInstance.cs @@ -16,6 +16,9 @@ internal sealed class ConsulServiceInstance : IServiceInstance /// public string ServiceId { get; } + /// + public string InstanceId { get; } + /// public string Host { get; } @@ -48,6 +51,7 @@ internal ConsulServiceInstance(ServiceEntry serviceEntry) Metadata = serviceEntry.Service.Meta.AsReadOnly(); IsSecure = serviceEntry.Service.Meta != null && serviceEntry.Service.Meta.TryGetValue("secure", out string? secureString) && bool.Parse(secureString); ServiceId = serviceEntry.Service.Service; + InstanceId = serviceEntry.Service.ID; Port = serviceEntry.Service.Port; Uri = new Uri($"{(IsSecure ? "https" : "http")}://{Host}:{Port}"); } diff --git a/src/Discovery/src/Consul/Registry/ConsulRegistration.cs b/src/Discovery/src/Consul/Registry/ConsulRegistration.cs index 74c0dcf67d..3366692fdf 100644 --- a/src/Discovery/src/Consul/Registry/ConsulRegistration.cs +++ b/src/Discovery/src/Consul/Registry/ConsulRegistration.cs @@ -22,9 +22,7 @@ internal sealed class ConsulRegistration : IServiceInstance /// public string ServiceId { get; } - /// - /// Gets the instance ID as registered by the Consul server. - /// + /// public string InstanceId { get; } /// diff --git a/src/Discovery/src/Consul/ThisServiceInstance.cs b/src/Discovery/src/Consul/ThisServiceInstance.cs index 6453bd6909..20717a7687 100644 --- a/src/Discovery/src/Consul/ThisServiceInstance.cs +++ b/src/Discovery/src/Consul/ThisServiceInstance.cs @@ -15,6 +15,9 @@ internal sealed class ThisServiceInstance : IServiceInstance /// public string ServiceId { get; } + /// + public string InstanceId { get; } + /// public string Host { get; } @@ -35,6 +38,7 @@ public ThisServiceInstance(ConsulRegistration registration) ArgumentNullException.ThrowIfNull(registration); ServiceId = registration.ServiceId; + InstanceId = registration.InstanceId; Host = registration.Host; IsSecure = registration.IsSecure; Port = registration.Port; diff --git a/src/Discovery/src/Eureka/EurekaServiceInstance.cs b/src/Discovery/src/Eureka/EurekaServiceInstance.cs index e6ac9e1928..1a4e843073 100644 --- a/src/Discovery/src/Eureka/EurekaServiceInstance.cs +++ b/src/Discovery/src/Eureka/EurekaServiceInstance.cs @@ -13,6 +13,7 @@ namespace Steeltoe.Discovery.Eureka; internal sealed class EurekaServiceInstance : IServiceInstance { public string ServiceId { get; } + public string InstanceId { get; } public string Host { get; } public int Port { get; } public bool IsSecure { get; } @@ -24,6 +25,7 @@ public EurekaServiceInstance(InstanceInfo instance) ArgumentNullException.ThrowIfNull(instance); ServiceId = instance.AppName; + InstanceId = instance.InstanceId; Host = instance.HostName; Port = GetPort(instance); IsSecure = instance.IsSecurePortEnabled; diff --git a/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs b/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs index 8f354d8da2..2ea2c03c93 100644 --- a/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs +++ b/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs @@ -138,6 +138,7 @@ private sealed class JsonSerializableServiceInstance : IServiceInstance // Trust that deserialized instances meet the IServiceInstance contract, so suppress nullability warnings. public string ServiceId { get; set; } = null!; + public string InstanceId { get; set; } = null!; public string Host { get; set; } = null!; public int Port { get; set; } public bool IsSecure { get; set; } @@ -151,6 +152,7 @@ public static JsonSerializableServiceInstance CopyFrom(IServiceInstance instance return new JsonSerializableServiceInstance { ServiceId = instance.ServiceId, + InstanceId = instance.InstanceId, Host = instance.Host, Port = instance.Port, IsSecure = instance.IsSecure, diff --git a/src/Discovery/test/Consul.Test/Discovery/ConsulDiscoveryClientTest.cs b/src/Discovery/test/Consul.Test/Discovery/ConsulDiscoveryClientTest.cs index 88caae557d..7d1e0cb3ff 100644 --- a/src/Discovery/test/Consul.Test/Discovery/ConsulDiscoveryClientTest.cs +++ b/src/Discovery/test/Consul.Test/Discovery/ConsulDiscoveryClientTest.cs @@ -32,6 +32,7 @@ public async Task AddInstancesToListAsync_AddsExpected() Service = new AgentService { Service = "ServiceId", + ID = "Instance1", Address = "foo.bar.com", Port = 1234, Meta = new Dictionary @@ -46,6 +47,7 @@ public async Task AddInstancesToListAsync_AddsExpected() Service = new AgentService { Service = "ServiceId", + ID = "Instance2", Address = "foo1.bar1.com", Port = 5678, Meta = new Dictionary @@ -79,6 +81,7 @@ await discoveryClient.AddInstancesToListAsync(serviceInstances, "ServiceId", Que serviceInstances[0].Host.Should().Be("foo.bar.com"); serviceInstances[0].ServiceId.Should().Be("ServiceId"); + serviceInstances[0].InstanceId.Should().Be("Instance1"); serviceInstances[0].IsSecure.Should().BeTrue(); serviceInstances[0].Port.Should().Be(1234); serviceInstances[0].Metadata.Should().HaveCount(2); @@ -88,6 +91,7 @@ await discoveryClient.AddInstancesToListAsync(serviceInstances, "ServiceId", Que serviceInstances[1].Host.Should().Be("foo1.bar1.com"); serviceInstances[1].ServiceId.Should().Be("ServiceId"); + serviceInstances[1].InstanceId.Should().Be("Instance2"); serviceInstances[1].IsSecure.Should().BeFalse(); serviceInstances[1].Port.Should().Be(5678); serviceInstances[1].Metadata.Should().HaveCount(2); @@ -169,6 +173,7 @@ public async Task GetAllInstances_ReturnsExpected() Service = new AgentService { Service = "ServiceId", + ID = "Instance1", Address = "foo.bar.com", Port = 1234, Meta = new Dictionary @@ -183,6 +188,7 @@ public async Task GetAllInstances_ReturnsExpected() Service = new AgentService { Service = "ServiceId", + ID = "Instance2", Address = "foo1.bar1.com", Port = 5678, Meta = new Dictionary @@ -215,6 +221,7 @@ public async Task GetAllInstances_ReturnsExpected() serviceInstances[0].Host.Should().Be("foo.bar.com"); serviceInstances[0].ServiceId.Should().Be("ServiceId"); + serviceInstances[0].InstanceId.Should().Be("Instance1"); serviceInstances[0].IsSecure.Should().BeTrue(); serviceInstances[0].Port.Should().Be(1234); serviceInstances[0].Metadata.Should().HaveCount(2); @@ -224,6 +231,7 @@ public async Task GetAllInstances_ReturnsExpected() serviceInstances[1].Host.Should().Be("foo1.bar1.com"); serviceInstances[1].ServiceId.Should().Be("ServiceId"); + serviceInstances[1].InstanceId.Should().Be("Instance2"); serviceInstances[1].IsSecure.Should().BeFalse(); serviceInstances[1].Port.Should().Be(5678); serviceInstances[1].Metadata.Should().HaveCount(2); diff --git a/src/Discovery/test/Consul.Test/Discovery/ConsulServiceInstanceTest.cs b/src/Discovery/test/Consul.Test/Discovery/ConsulServiceInstanceTest.cs index a9426cd7a2..9f23b3a233 100644 --- a/src/Discovery/test/Consul.Test/Discovery/ConsulServiceInstanceTest.cs +++ b/src/Discovery/test/Consul.Test/Discovery/ConsulServiceInstanceTest.cs @@ -16,6 +16,7 @@ public void Constructor_Initializes() Service = new AgentService { Service = "ServiceId", + ID = "Instance1", Address = "foo.bar.com", Port = 1234, Tags = @@ -35,6 +36,7 @@ public void Constructor_Initializes() serviceInstance.Host.Should().Be("foo.bar.com"); serviceInstance.ServiceId.Should().Be("ServiceId"); + serviceInstance.InstanceId.Should().Be("Instance1"); serviceInstance.IsSecure.Should().BeTrue(); serviceInstance.Port.Should().Be(1234); serviceInstance.Tags.Should().HaveCount(2); diff --git a/src/Discovery/test/Consul.Test/Discovery/ThisServiceInstanceTest.cs b/src/Discovery/test/Consul.Test/Discovery/ThisServiceInstanceTest.cs index 1b4fa260cd..e509ee6919 100644 --- a/src/Discovery/test/Consul.Test/Discovery/ThisServiceInstanceTest.cs +++ b/src/Discovery/test/Consul.Test/Discovery/ThisServiceInstanceTest.cs @@ -32,6 +32,7 @@ public void Constructor_Initializes() instance.Host.Should().Be("test.foo.bar"); instance.ServiceId.Should().Be("foobar"); + instance.InstanceId.Should().Be("ID"); instance.IsSecure.Should().BeFalse(); instance.Port.Should().Be(1234); instance.Metadata.Should().ContainSingle(); diff --git a/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs b/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs index fc45d1dce7..68e0e762f3 100644 --- a/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs +++ b/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs @@ -167,6 +167,7 @@ public async Task Constructor_Initializes_Correctly() thisService.Metadata.Should().BeEmpty(); thisService.Port.Should().Be(5000); thisService.ServiceId.Should().Be("DEMO"); + thisService.InstanceId.Should().Be($"{instanceOptions.HostName}:demo:5000"); thisService.Uri.Should().Be(new Uri($"http://{instanceOptions.HostName}:5000")); } diff --git a/src/Discovery/test/Eureka.Test/EurekaServiceInstanceTest.cs b/src/Discovery/test/Eureka.Test/EurekaServiceInstanceTest.cs index ce1bb62cd0..2a5403b94f 100644 --- a/src/Discovery/test/Eureka.Test/EurekaServiceInstanceTest.cs +++ b/src/Discovery/test/Eureka.Test/EurekaServiceInstanceTest.cs @@ -27,6 +27,7 @@ public void InstanceWithBothPorts() var serviceInstance = new EurekaServiceInstance(instance); serviceInstance.ServiceId.Should().Be(instance.AppName); + serviceInstance.InstanceId.Should().Be("id"); serviceInstance.Host.Should().Be(instance.HostName); serviceInstance.Port.Should().Be(instance.SecurePort); serviceInstance.IsSecure.Should().BeTrue(); diff --git a/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs b/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs index 0d7a33fbf7..fb9d69fa7b 100644 --- a/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs +++ b/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs @@ -241,6 +241,7 @@ private static IDistributedCache GetCache() private sealed class TestServiceInstance(Uri uri) : IServiceInstance { public string ServiceId => throw new NotImplementedException(); + public string InstanceId => throw new NotImplementedException(); public string Host => throw new NotImplementedException(); public int Port => throw new NotImplementedException(); public bool IsSecure => throw new NotImplementedException(); From 68f4fae56baf5f1fe04fa9c848c4841eef8935ea Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 24 Sep 2025 17:06:39 +0200 Subject: [PATCH 06/81] Exclude Aspire service discovery resolver from internal HttpClient used by Eureka (#1598) --- .../EurekaServiceCollectionExtensions.cs | 15 ++++++--- ...veryWebApplicationBuilderExtensionsTest.cs | 33 +++++++++++++++++++ .../ResolvingHttpDelegatingHandler.cs | 16 +++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 src/Discovery/test/HttpClients.Test/ResolvingHttpDelegatingHandler.cs diff --git a/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs b/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs index a40b5757cf..f41e70d962 100644 --- a/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs +++ b/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs @@ -20,6 +20,7 @@ namespace Steeltoe.Discovery.Eureka; public static class EurekaServiceCollectionExtensions { private const string SpringDiscoveryEnabled = "spring:cloud:discovery:enabled"; + private const string ResolvingHttpDelegatingHandlerName = "Microsoft.Extensions.ServiceDiscovery.Http.ResolvingHttpDelegatingHandler"; /// /// Configures to use for service discovery. @@ -108,7 +109,7 @@ private static void AddEurekaClient(IServiceCollection services) services.ConfigureCertificateOptions("Eureka"); IHttpClientBuilder eurekaHttpClientBuilder = services.AddHttpClient("Eureka"); - eurekaHttpClientBuilder.ConfigureAdditionalHttpMessageHandlers((defaultHandlers, _) => RemoveDiscoveryHttpDelegatingHandler(defaultHandlers)); + eurekaHttpClientBuilder.ConfigureAdditionalHttpMessageHandlers((defaultHandlers, _) => RemoveDiscoveryHttpHandlers(defaultHandlers)); eurekaHttpClientBuilder.ConfigurePrimaryHttpMessageHandler(serviceProvider => { @@ -122,7 +123,7 @@ private static void AddEurekaClient(IServiceCollection services) }); IHttpClientBuilder eurekaTokenHttpClientBuilder = services.AddHttpClient("AccessTokenForEureka"); - eurekaTokenHttpClientBuilder.ConfigureAdditionalHttpMessageHandlers((defaultHandlers, _) => RemoveDiscoveryHttpDelegatingHandler(defaultHandlers)); + eurekaTokenHttpClientBuilder.ConfigureAdditionalHttpMessageHandlers((defaultHandlers, _) => RemoveDiscoveryHttpHandlers(defaultHandlers)); eurekaTokenHttpClientBuilder.ConfigurePrimaryHttpMessageHandler(serviceProvider => { @@ -139,12 +140,19 @@ private static void AddEurekaClient(IServiceCollection services) services.AddSingleton(); } - private static void RemoveDiscoveryHttpDelegatingHandler(ICollection defaultHandlers) + private static void RemoveDiscoveryHttpHandlers(ICollection defaultHandlers) { + // Prevent infinite recursion: The inner HttClient used by EurekaDiscoveryClient must not use service discovery. + DelegatingHandler[] discoveryHandlers = defaultHandlers.Where(handler => { Type handlerType = handler.GetType(); + if (handlerType.FullName == ResolvingHttpDelegatingHandlerName) + { + return true; + } + if (handlerType.IsConstructedGenericType) { Type handlerOpenType = handlerType.GetGenericTypeDefinition(); @@ -160,7 +168,6 @@ private static void RemoveDiscoveryHttpDelegatingHandler(ICollection + { + ["Eureka:Client:ShouldFetchRegistry"] = "false", + ["Eureka:Client:ShouldRegisterWithEureka"] = "false" + }); + + builder.Services.AddEurekaDiscoveryClient(); + builder.Services.AddTransient(); + builder.Services.ConfigureHttpClientDefaults(action => action.AddHttpMessageHandler()); + + var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.Expect(HttpMethod.Get, "http://localhost:8761/eureka/apps").Respond("application/json", "{}"); + + await using WebApplication host = builder.Build(); + + host.Services.GetRequiredService().Using(handler); + + var discoveryClient = host.Services.GetRequiredService(); + Func action = async () => await discoveryClient.FetchRegistryAsync(true, TestContext.Current.CancellationToken); + + await action.Should().NotThrowAsync(); + + handler.Mock.VerifyNoOutstandingExpectation(); + } } diff --git a/src/Discovery/test/HttpClients.Test/ResolvingHttpDelegatingHandler.cs b/src/Discovery/test/HttpClients.Test/ResolvingHttpDelegatingHandler.cs new file mode 100644 index 0000000000..98516a5842 --- /dev/null +++ b/src/Discovery/test/HttpClients.Test/ResolvingHttpDelegatingHandler.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +#pragma warning disable IDE0130 // Namespace does not match folder structure +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.ServiceDiscovery.Http; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +internal sealed class ResolvingHttpDelegatingHandler : DelegatingHandler +{ + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + throw new InvalidOperationException("This handler should have been removed from the HttpClient pipeline."); + } +} From 176e7da5ccf6781cac580e9b59ab917be3f54db3 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Wed, 24 Sep 2025 15:30:18 -0500 Subject: [PATCH 07/81] Set templated=false for all management endpoints --- .../Endpoint/Actuators/Hypermedia/HypermediaService.cs | 3 +-- .../Actuators/CloudFoundry/CloudFoundryActuatorTest.cs | 8 ++++---- .../Actuators/Hypermedia/HypermediaActuatorTest.cs | 6 +++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Management/src/Endpoint/Actuators/Hypermedia/HypermediaService.cs b/src/Management/src/Endpoint/Actuators/Hypermedia/HypermediaService.cs index 1ee759de23..4e667ea69e 100644 --- a/src/Management/src/Endpoint/Actuators/Hypermedia/HypermediaService.cs +++ b/src/Management/src/Endpoint/Actuators/Hypermedia/HypermediaService.cs @@ -114,7 +114,6 @@ private static Link CreateLink(Uri baseUrl, string? basePath, EndpointOptions en }; string href = builder.Uri.ToString(); - bool isTemplated = !endpointOptions.RequiresExactMatch(); - return new Link(href, isTemplated); + return new Link(href, false); } } diff --git a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs index 9faf688a6d..ec3379712f 100644 --- a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs @@ -176,7 +176,7 @@ public async Task Endpoint_returns_expected_data_with_all_actuators_registered(H }, "health": { "href": "http://localhost/cloudfoundryapplication/health", - "templated": true + "templated": false }, "heapdump": { "href": "http://localhost/cloudfoundryapplication/heapdump", @@ -192,7 +192,7 @@ public async Task Endpoint_returns_expected_data_with_all_actuators_registered(H }, "loggers": { "href": "http://localhost/cloudfoundryapplication/loggers", - "templated": true + "templated": false }, "mappings": { "href": "http://localhost/cloudfoundryapplication/mappings", @@ -410,7 +410,7 @@ public async Task Hides_disabled_actuators_and_ignores_exposure() "_links": { "loggers": { "href": "http://localhost/cloudfoundryapplication/loggers", - "templated": true + "templated": false }, "self": { "href": "http://localhost/cloudfoundryapplication", @@ -507,7 +507,7 @@ public async Task Can_change_configuration_at_runtime() "_links": { "health": { "href": "http://localhost/cloudfoundryapplication/health", - "templated": true + "templated": false }, "self": { "href": "http://localhost/cloudfoundryapplication", diff --git a/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs index 91d1a37f33..1b9e98e5e5 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs @@ -111,7 +111,7 @@ public async Task Endpoint_returns_expected_data_with_all_actuators_registered(H "_links": { "health": { "href": "http://localhost/actuator/health", - "templated": true + "templated": false }, "info": { "href": "http://localhost/actuator/info", @@ -196,7 +196,7 @@ public async Task Can_use_alternate_IDs_and_paths() }, "loggers": { "href": "http://localhost/alt-actuator/alt-loggers-path", - "templated": true + "templated": false }, "self": { "href": "http://localhost/alt-actuator/hypermedia", @@ -448,7 +448,7 @@ public async Task Can_change_configuration_at_runtime() "_links": { "health": { "href": "http://localhost/actuator/health", - "templated": true + "templated": false }, "self": { "href": "http://localhost/actuator", From 2f994977e66f1b1b3677801741ac674469d0cab9 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 2 Oct 2025 17:42:43 +0200 Subject: [PATCH 08/81] Expose secure/non-secure URIs on service instance, optimize Eureka (#1604) * Expose secure/non-secure uris on service instance * Optimize returned instances from Eureka --- .../src/Common/Discovery/IServiceInstance.cs | 10 ++ src/Common/src/Common/PublicAPI.Unshipped.txt | 2 + .../ConfigServerConfigurationProviderTest.cs | 2 + .../ConfigurationServiceInstance.cs | 6 + .../src/Configuration/PublicAPI.Unshipped.txt | 2 + .../src/Consul/ConsulServiceInstance.cs | 8 ++ .../src/Consul/Registry/ConsulRegistration.cs | 6 + .../src/Consul/ThisServiceInstance.cs | 8 ++ .../AppInfo/ApplicationInfoCollection.cs | 119 ++++++----------- .../src/Eureka/EurekaDiscoveryClient.cs | 40 ++++-- .../src/Eureka/EurekaServiceInstance.cs | 40 ++++-- .../LoadBalancers/ServiceInstancesResolver.cs | 4 + .../Discovery/ConsulDiscoveryClientTest.cs | 8 ++ .../Discovery/ConsulServiceInstanceTest.cs | 2 + .../Discovery/ThisServiceInstanceTest.cs | 2 + .../Registry/ConsulRegistrationTest.cs | 11 +- .../AppInfo/ApplicationInfoCollectionTest.cs | 122 +++++++----------- .../Eureka.Test/EurekaDiscoveryClientTest.cs | 18 +-- .../Eureka.Test/EurekaServiceInstanceTest.cs | 8 ++ .../RoundRobinLoadBalancerTest.cs | 2 + 20 files changed, 235 insertions(+), 185 deletions(-) diff --git a/src/Common/src/Common/Discovery/IServiceInstance.cs b/src/Common/src/Common/Discovery/IServiceInstance.cs index 8a1c5cac8c..25a5823fcc 100644 --- a/src/Common/src/Common/Discovery/IServiceInstance.cs +++ b/src/Common/src/Common/Discovery/IServiceInstance.cs @@ -36,6 +36,16 @@ public interface IServiceInstance /// Uri Uri { get; } + /// + /// Gets the HTTP-based resolved address of the registered service instance, if available. + /// + Uri? NonSecureUri { get; } + + /// + /// Gets the HTTPS-based resolved address of the registered service instance, if available. + /// + Uri? SecureUri { get; } + /// /// Gets the key/value metadata associated with this service instance. /// diff --git a/src/Common/src/Common/PublicAPI.Unshipped.txt b/src/Common/src/Common/PublicAPI.Unshipped.txt index 0c34fd435f..4d8338db45 100644 --- a/src/Common/src/Common/PublicAPI.Unshipped.txt +++ b/src/Common/src/Common/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ #nullable enable Steeltoe.Common.Discovery.IServiceInstance.InstanceId.get -> string! +Steeltoe.Common.Discovery.IServiceInstance.NonSecureUri.get -> System.Uri? +Steeltoe.Common.Discovery.IServiceInstance.SecureUri.get -> System.Uri? diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs index 7bbfb0f21b..1f13886305 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs @@ -424,6 +424,8 @@ private sealed class TestServiceInstance(string serviceId, string instanceId, Ur public int Port { get; } = uri.Port; public bool IsSecure { get; } = uri.Scheme == Uri.UriSchemeHttps; public Uri Uri { get; } = uri; + public Uri? NonSecureUri => IsSecure ? null : Uri; + public Uri? SecureUri => IsSecure ? Uri : null; public IReadOnlyDictionary Metadata { get; } = metadata; } } diff --git a/src/Discovery/src/Configuration/ConfigurationServiceInstance.cs b/src/Discovery/src/Configuration/ConfigurationServiceInstance.cs index f5ac148f17..a5dd35b917 100644 --- a/src/Discovery/src/Configuration/ConfigurationServiceInstance.cs +++ b/src/Discovery/src/Configuration/ConfigurationServiceInstance.cs @@ -38,6 +38,12 @@ public sealed class ConfigurationServiceInstance : IServiceInstance /// public Uri Uri => new($"{(IsSecure ? Uri.UriSchemeHttps : Uri.UriSchemeHttp)}{Uri.SchemeDelimiter}{Host}:{Port}"); + /// + public Uri? NonSecureUri => IsSecure ? null : Uri; + + /// + public Uri? SecureUri => IsSecure ? Uri : null; + /// public IDictionary Metadata { get; } = new Dictionary(); } diff --git a/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt b/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt index 8a7cac8051..d7fc263414 100644 --- a/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt +++ b/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ #nullable enable Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.InstanceId.get -> string! +Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.NonSecureUri.get -> System.Uri? +Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.SecureUri.get -> System.Uri? diff --git a/src/Discovery/src/Consul/ConsulServiceInstance.cs b/src/Discovery/src/Consul/ConsulServiceInstance.cs index bc38ec621f..6c6894d3c5 100644 --- a/src/Discovery/src/Consul/ConsulServiceInstance.cs +++ b/src/Discovery/src/Consul/ConsulServiceInstance.cs @@ -31,6 +31,12 @@ internal sealed class ConsulServiceInstance : IServiceInstance /// public Uri Uri { get; } + /// + public Uri? NonSecureUri { get; } + + /// + public Uri? SecureUri { get; } + public IReadOnlyList Tags { get; } /// @@ -54,5 +60,7 @@ internal ConsulServiceInstance(ServiceEntry serviceEntry) InstanceId = serviceEntry.Service.ID; Port = serviceEntry.Service.Port; Uri = new Uri($"{(IsSecure ? "https" : "http")}://{Host}:{Port}"); + NonSecureUri = IsSecure ? null : Uri; + SecureUri = IsSecure ? Uri : null; } } diff --git a/src/Discovery/src/Consul/Registry/ConsulRegistration.cs b/src/Discovery/src/Consul/Registry/ConsulRegistration.cs index 3366692fdf..c8861fd4d4 100644 --- a/src/Discovery/src/Consul/Registry/ConsulRegistration.cs +++ b/src/Discovery/src/Consul/Registry/ConsulRegistration.cs @@ -37,6 +37,12 @@ internal sealed class ConsulRegistration : IServiceInstance /// public Uri Uri => new($"{_optionsMonitor.CurrentValue.EffectiveScheme}://{Host}:{Port}"); + /// + public Uri? NonSecureUri => IsSecure ? null : Uri; + + /// + public Uri? SecureUri => IsSecure ? Uri : null; + public IReadOnlyList Tags { get; } /// diff --git a/src/Discovery/src/Consul/ThisServiceInstance.cs b/src/Discovery/src/Consul/ThisServiceInstance.cs index 20717a7687..73a15ce002 100644 --- a/src/Discovery/src/Consul/ThisServiceInstance.cs +++ b/src/Discovery/src/Consul/ThisServiceInstance.cs @@ -30,6 +30,12 @@ internal sealed class ThisServiceInstance : IServiceInstance /// public Uri Uri { get; } + /// + public Uri? NonSecureUri { get; } + + /// + public Uri? SecureUri { get; } + /// public IReadOnlyDictionary Metadata { get; } @@ -44,5 +50,7 @@ public ThisServiceInstance(ConsulRegistration registration) Port = registration.Port; Metadata = registration.Metadata; Uri = registration.Uri; + NonSecureUri = registration.NonSecureUri; + SecureUri = registration.SecureUri; } } diff --git a/src/Discovery/src/Eureka/AppInfo/ApplicationInfoCollection.cs b/src/Discovery/src/Eureka/AppInfo/ApplicationInfoCollection.cs index d77eb1f76e..28e0fdbae6 100644 --- a/src/Discovery/src/Eureka/AppInfo/ApplicationInfoCollection.cs +++ b/src/Discovery/src/Eureka/AppInfo/ApplicationInfoCollection.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Concurrent; +using System.Collections.ObjectModel; using System.Text; using System.Text.Json; using Steeltoe.Common; @@ -17,11 +18,8 @@ namespace Steeltoe.Discovery.Eureka.AppInfo; /// public sealed class ApplicationInfoCollection : IReadOnlyCollection { - private readonly object _addRemoveInstanceLock = new(); - internal ConcurrentDictionary ApplicationMap { get; } = new(); internal ConcurrentDictionary> VipInstanceMap { get; } = new(); - internal ConcurrentDictionary> SecureVipInstanceMap { get; } = new(); public string? AppsHashCode { get; internal set; } public long? Version { get; private set; } @@ -51,18 +49,25 @@ internal ApplicationInfoCollection(IList apps) return ApplicationMap.GetValueOrDefault(appName.ToUpperInvariant()); } - internal List GetInstancesBySecureVipAddress(string secureVipAddress) + internal ReadOnlyCollection GetInstancesByVipAddress(string vipAddress) { - ArgumentException.ThrowIfNullOrWhiteSpace(secureVipAddress); + ArgumentException.ThrowIfNullOrWhiteSpace(vipAddress); - return GetByVipAddress(secureVipAddress, SecureVipInstanceMap); - } + List result = []; + string addressUpper = vipAddress.ToUpperInvariant(); - internal List GetInstancesByVipAddress(string vipAddress) - { - ArgumentException.ThrowIfNullOrWhiteSpace(vipAddress); + if (VipInstanceMap.TryGetValue(addressUpper, out ConcurrentDictionary? instancesById)) + { + foreach (InstanceInfo instance in instancesById.Values) + { + if ((ReturnUpInstancesOnly && instance.EffectiveStatus == InstanceStatus.Up) || !ReturnUpInstancesOnly) + { + result.Add(instance); + } + } + } - return GetByVipAddress(vipAddress, VipInstanceMap); + return result.AsReadOnly(); } /// @@ -92,79 +97,51 @@ internal void Add(ApplicationInfo app) foreach (InstanceInfo instance in app.Instances) { - AddInstanceToVip(instance); + AddToVipInstanceMap(instance); } } - private void AddInstanceToVip(InstanceInfo instance) + private void AddToVipInstanceMap(InstanceInfo instance) { - foreach (string vipAddress in ExpandVipAddresses(instance.VipAddress)) + foreach (string vipAddress in ExpandVipAddresses(instance)) { - AddInstanceToVip(instance, vipAddress, VipInstanceMap); - } + string addressUpper = vipAddress.ToUpperInvariant(); - foreach (string secureVipAddress in ExpandVipAddresses(instance.SecureVipAddress)) - { - AddInstanceToVip(instance, secureVipAddress, SecureVipInstanceMap); + ConcurrentDictionary instancesById = VipInstanceMap.GetOrAdd(addressUpper, new ConcurrentDictionary()); + instancesById.AddOrUpdate(instance.InstanceId, _ => instance, (_, _) => instance); } } - private void AddInstanceToVip(InstanceInfo instance, string address, ConcurrentDictionary> dictionary) + private static HashSet ExpandVipAddresses(InstanceInfo instance) { - lock (_addRemoveInstanceLock) - { - string addressUpper = address.ToUpperInvariant(); - - if (!dictionary.TryGetValue(addressUpper, out ConcurrentDictionary? instances)) - { - instances = new ConcurrentDictionary(); - dictionary[addressUpper] = instances; - } + HashSet addresses = []; - instances[instance.InstanceId] = instance; + if (instance.SecureVipAddress != null) + { + string[] secureAddresses = instance.SecureVipAddress.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + addresses.UnionWith(secureAddresses); } - } - private static string[] ExpandVipAddresses(string? addresses) - { - if (string.IsNullOrWhiteSpace(addresses)) + if (instance.VipAddress != null) { - return []; + string[] vipAddresses = instance.VipAddress.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + addresses.UnionWith(vipAddresses); } - return addresses.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + return addresses; } - internal void RemoveInstanceFromVip(InstanceInfo instance) + internal void RemoveFromVipInstanceMap(InstanceInfo instance) { ArgumentNullException.ThrowIfNull(instance); - foreach (string vipAddress in ExpandVipAddresses(instance.VipAddress)) - { - RemoveInstanceFromVip(instance, vipAddress, VipInstanceMap); - } - - foreach (string secureVipAddress in ExpandVipAddresses(instance.SecureVipAddress)) - { - RemoveInstanceFromVip(instance, secureVipAddress, SecureVipInstanceMap); - } - } - - private void RemoveInstanceFromVip(InstanceInfo instance, string address, - ConcurrentDictionary> dictionary) - { - lock (_addRemoveInstanceLock) + foreach (string vipAddress in ExpandVipAddresses(instance)) { - string addressUpper = address.ToUpperInvariant(); + string addressUpper = vipAddress.ToUpperInvariant(); - if (dictionary.TryGetValue(addressUpper, out ConcurrentDictionary? instances)) + if (VipInstanceMap.TryGetValue(addressUpper, out ConcurrentDictionary? instancesById)) { - _ = instances.TryRemove(instance.InstanceId, out _); - - if (instances.IsEmpty) - { - _ = dictionary.TryRemove(addressUpper, out _); - } + instancesById.TryRemove(instance.InstanceId, out _); } } } @@ -190,11 +167,11 @@ internal void UpdateFromDelta(ApplicationInfoCollection delta) case ActionType.Added: case ActionType.Modified: existingApp.Add(instance); - AddInstanceToVip(instance); + AddToVipInstanceMap(instance); break; case ActionType.Deleted: existingApp.Remove(instance); - RemoveInstanceFromVip(instance); + RemoveFromVipInstanceMap(instance); break; } } @@ -261,22 +238,4 @@ internal static ApplicationInfoCollection FromJson(JsonApplications? jsonApplica return apps; } - - private List GetByVipAddress(string name, ConcurrentDictionary> dictionary) - { - List result = []; - - if (dictionary.TryGetValue(name.ToUpperInvariant(), out ConcurrentDictionary? instances)) - { - foreach (InstanceInfo instance in instances.Values.ToArray()) - { - if ((ReturnUpInstancesOnly && instance.EffectiveStatus == InstanceStatus.Up) || !ReturnUpInstancesOnly) - { - result.Add(instance); - } - } - } - - return result; - } } diff --git a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs index 4d9a9d2357..dbb50b9480 100644 --- a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs +++ b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Text; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Steeltoe.Common.Discovery; @@ -156,12 +157,11 @@ public EurekaDiscoveryClient(EurekaApplicationInfoManager appInfoManager, Eureka return Applications.GetRegisteredApplication(appName); } - internal IReadOnlyList GetInstancesByVipAddress(string vipAddress, bool secure) + internal IReadOnlyList GetInstancesByVipAddress(string vipAddress) { ArgumentException.ThrowIfNullOrWhiteSpace(vipAddress); - List instances = secure ? Applications.GetInstancesBySecureVipAddress(vipAddress) : Applications.GetInstancesByVipAddress(vipAddress); - return instances.AsReadOnly(); + return Applications.GetInstancesByVipAddress(vipAddress); } /// @@ -533,18 +533,40 @@ public Task> GetInstancesAsync(string serviceId, Cancell { ArgumentException.ThrowIfNullOrWhiteSpace(serviceId); - IReadOnlyList nonSecureInstances = GetInstancesByVipAddress(serviceId, false); - IReadOnlyList secureInstances = GetInstancesByVipAddress(serviceId, true); - - IEnumerable instances = secureInstances.Concat(nonSecureInstances).DistinctBy(instance => instance.InstanceId); + IReadOnlyList instances = GetInstancesByVipAddress(serviceId); IServiceInstance[] serviceInstances = instances.Select(instance => new EurekaServiceInstance(instance)).Cast().ToArray(); - _logger.LogDebug("Returning {Count} service instances: {ServiceInstances}", serviceInstances.Length, - string.Join(", ", serviceInstances.Select(instance => $"{instance.ServiceId}={instance.Uri}"))); + if (_logger.IsEnabled(LogLevel.Debug)) + { + string instanceNames = string.Join(", ", serviceInstances.Select(FormatServiceInstance)); + _logger.LogDebug("Returning {Count} service instances for '{ServiceId}': {ServiceInstances}", serviceInstances.Length, serviceId, instanceNames); + } return Task.FromResult>(serviceInstances); } + private static string FormatServiceInstance(IServiceInstance instance) + { + var builder = new StringBuilder(); + + if (instance.SecureUri != null) + { + builder.Append(instance.SecureUri); + } + + if (instance.NonSecureUri != null) + { + if (builder.Length > 0) + { + builder.Append(';'); + } + + builder.Append(instance.NonSecureUri); + } + + return $"{instance.InstanceId}={builder}"; + } + /// public IServiceInstance GetLocalServiceInstance() { diff --git a/src/Discovery/src/Eureka/EurekaServiceInstance.cs b/src/Discovery/src/Eureka/EurekaServiceInstance.cs index 1a4e843073..851edb04f3 100644 --- a/src/Discovery/src/Eureka/EurekaServiceInstance.cs +++ b/src/Discovery/src/Eureka/EurekaServiceInstance.cs @@ -12,12 +12,31 @@ namespace Steeltoe.Discovery.Eureka; /// internal sealed class EurekaServiceInstance : IServiceInstance { + /// public string ServiceId { get; } + + /// public string InstanceId { get; } + + /// public string Host { get; } + + /// public int Port { get; } + + /// public bool IsSecure { get; } + + /// public Uri Uri { get; } + + /// + public Uri? NonSecureUri { get; } + + /// + public Uri? SecureUri { get; } + + /// public IReadOnlyDictionary Metadata { get; } public EurekaServiceInstance(InstanceInfo instance) @@ -27,24 +46,23 @@ public EurekaServiceInstance(InstanceInfo instance) ServiceId = instance.AppName; InstanceId = instance.InstanceId; Host = instance.HostName; - Port = GetPort(instance); - IsSecure = instance.IsSecurePortEnabled; - Uri = new Uri($"{(IsSecure ? "https" : "http")}://{Host}:{Port}"); Metadata = instance.Metadata; - } - private static int GetPort(InstanceInfo instance) - { - if (instance.IsSecurePortEnabled) + if (instance is { IsNonSecurePortEnabled: true, NonSecurePort: > 0 }) { - return instance.SecurePort; +#pragma warning disable S5332 // Using clear-text protocols is security-sensitive + NonSecureUri = new Uri($"http://{Host}:{instance.NonSecurePort}"); +#pragma warning restore S5332 // Using clear-text protocols is security-sensitive + Port = instance.NonSecurePort; } - if (instance.IsNonSecurePortEnabled) + if (instance is { IsSecurePortEnabled: true, SecurePort: > 0 }) { - return instance.NonSecurePort; + SecureUri = new Uri($"https://{Host}:{instance.SecurePort}"); + Port = instance.SecurePort; } - return 0; + IsSecure = instance.IsSecurePortEnabled; + Uri = new Uri($"{(IsSecure ? "https" : "http")}://{Host}:{Port}"); } } diff --git a/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs b/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs index 2ea2c03c93..c6e6be70c6 100644 --- a/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs +++ b/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs @@ -143,6 +143,8 @@ private sealed class JsonSerializableServiceInstance : IServiceInstance public int Port { get; set; } public bool IsSecure { get; set; } public Uri Uri { get; set; } = null!; + public Uri? NonSecureUri { get; set; } + public Uri? SecureUri { get; set; } public IReadOnlyDictionary Metadata { get; set; } = null!; public static JsonSerializableServiceInstance CopyFrom(IServiceInstance instance) @@ -157,6 +159,8 @@ public static JsonSerializableServiceInstance CopyFrom(IServiceInstance instance Port = instance.Port, IsSecure = instance.IsSecure, Uri = instance.Uri, + NonSecureUri = instance.NonSecureUri, + SecureUri = instance.SecureUri, Metadata = instance.Metadata }; } diff --git a/src/Discovery/test/Consul.Test/Discovery/ConsulDiscoveryClientTest.cs b/src/Discovery/test/Consul.Test/Discovery/ConsulDiscoveryClientTest.cs index 7d1e0cb3ff..6cc5cb66e2 100644 --- a/src/Discovery/test/Consul.Test/Discovery/ConsulDiscoveryClientTest.cs +++ b/src/Discovery/test/Consul.Test/Discovery/ConsulDiscoveryClientTest.cs @@ -88,6 +88,8 @@ await discoveryClient.AddInstancesToListAsync(serviceInstances, "ServiceId", Que serviceInstances[0].Metadata.Should().ContainKey("foo").WhoseValue.Should().Be("bar"); serviceInstances[0].Metadata.Should().ContainKey("secure").WhoseValue.Should().Be("true"); serviceInstances[0].Uri.Should().Be(new Uri("https://foo.bar.com:1234")); + serviceInstances[0].NonSecureUri.Should().BeNull(); + serviceInstances[0].SecureUri.Should().Be(serviceInstances[0].Uri); serviceInstances[1].Host.Should().Be("foo1.bar1.com"); serviceInstances[1].ServiceId.Should().Be("ServiceId"); @@ -98,6 +100,8 @@ await discoveryClient.AddInstancesToListAsync(serviceInstances, "ServiceId", Que serviceInstances[1].Metadata.Should().ContainKey("bar").WhoseValue.Should().Be("foo"); serviceInstances[1].Metadata.Should().ContainKey("secure").WhoseValue.Should().Be("false"); serviceInstances[1].Uri.Should().Be(new Uri("http://foo1.bar1.com:5678")); + serviceInstances[1].NonSecureUri.Should().Be(serviceInstances[1].Uri); + serviceInstances[1].SecureUri.Should().BeNull(); } [Fact] @@ -228,6 +232,8 @@ public async Task GetAllInstances_ReturnsExpected() serviceInstances[0].Metadata.Should().ContainKey("foo").WhoseValue.Should().Be("bar"); serviceInstances[0].Metadata.Should().ContainKey("secure").WhoseValue.Should().Be("true"); serviceInstances[0].Uri.Should().Be(new Uri("https://foo.bar.com:1234")); + serviceInstances[0].NonSecureUri.Should().BeNull(); + serviceInstances[0].SecureUri.Should().Be(serviceInstances[0].Uri); serviceInstances[1].Host.Should().Be("foo1.bar1.com"); serviceInstances[1].ServiceId.Should().Be("ServiceId"); @@ -238,5 +244,7 @@ public async Task GetAllInstances_ReturnsExpected() serviceInstances[1].Metadata.Should().ContainKey("bar").WhoseValue.Should().Be("foo"); serviceInstances[1].Metadata.Should().ContainKey("secure").WhoseValue.Should().Be("false"); serviceInstances[1].Uri.Should().Be(new Uri("http://foo1.bar1.com:5678")); + serviceInstances[1].NonSecureUri.Should().Be(serviceInstances[1].Uri); + serviceInstances[1].SecureUri.Should().BeNull(); } } diff --git a/src/Discovery/test/Consul.Test/Discovery/ConsulServiceInstanceTest.cs b/src/Discovery/test/Consul.Test/Discovery/ConsulServiceInstanceTest.cs index 9f23b3a233..ab4b65505b 100644 --- a/src/Discovery/test/Consul.Test/Discovery/ConsulServiceInstanceTest.cs +++ b/src/Discovery/test/Consul.Test/Discovery/ConsulServiceInstanceTest.cs @@ -46,5 +46,7 @@ public void Constructor_Initializes() serviceInstance.Metadata.Should().ContainKey("foo").WhoseValue.Should().Be("bar"); serviceInstance.Metadata.Should().ContainKey("secure").WhoseValue.Should().Be("true"); serviceInstance.Uri.Should().Be(new Uri("https://foo.bar.com:1234")); + serviceInstance.NonSecureUri.Should().BeNull(); + serviceInstance.SecureUri.Should().Be(serviceInstance.Uri); } } diff --git a/src/Discovery/test/Consul.Test/Discovery/ThisServiceInstanceTest.cs b/src/Discovery/test/Consul.Test/Discovery/ThisServiceInstanceTest.cs index e509ee6919..1ae187bd1e 100644 --- a/src/Discovery/test/Consul.Test/Discovery/ThisServiceInstanceTest.cs +++ b/src/Discovery/test/Consul.Test/Discovery/ThisServiceInstanceTest.cs @@ -38,5 +38,7 @@ public void Constructor_Initializes() instance.Metadata.Should().ContainSingle(); instance.Metadata.Should().ContainKey("foo").WhoseValue.Should().Be("bar"); instance.Uri.Should().Be(new Uri("http://test.foo.bar:1234")); + instance.NonSecureUri.Should().Be(instance.Uri); + instance.SecureUri.Should().BeNull(); } } diff --git a/src/Discovery/test/Consul.Test/Registry/ConsulRegistrationTest.cs b/src/Discovery/test/Consul.Test/Registry/ConsulRegistrationTest.cs index c3c1d90880..fcc2a27912 100644 --- a/src/Discovery/test/Consul.Test/Registry/ConsulRegistrationTest.cs +++ b/src/Discovery/test/Consul.Test/Registry/ConsulRegistrationTest.cs @@ -46,6 +46,8 @@ public void Constructor_SetsProperties() registration.Metadata.Should().ContainKey("foo").WhoseValue.Should().Be("bar"); registration.IsSecure.Should().BeFalse(); registration.Uri.Should().Be(new Uri("http://address:1234")); + registration.NonSecureUri.Should().Be(registration.Uri); + registration.SecureUri.Should().BeNull(); } [Fact] @@ -177,17 +179,20 @@ public void CreateRegistration_ReturnsExpected() { ["spring:application:name"] = "foobar", ["consul:discovery:hostName"] = "some-host", - ["consul:discovery:port"] = "1100" + ["consul:discovery:port"] = "1100", + ["consul:discovery:scheme"] = "https" }; ConsulRegistration registration = TestRegistrationFactory.Create(appSettings); registration.InstanceId.Should().StartWith("foobar-"); - registration.IsSecure.Should().BeFalse(); + registration.IsSecure.Should().BeTrue(); registration.ServiceId.Should().Be("foobar"); registration.Host.Should().Be("some-host"); registration.Port.Should().Be(1100); - registration.Uri.Should().Be(new Uri("http://some-host:1100")); + registration.Uri.Should().Be(new Uri("https://some-host:1100")); + registration.SecureUri.Should().Be(registration.Uri); + registration.NonSecureUri.Should().BeNull(); registration.InnerRegistration.Should().NotBeNull(); registration.InnerRegistration.Address.Should().Be("some-host"); diff --git a/src/Discovery/test/Eureka.Test/AppInfo/ApplicationInfoCollectionTest.cs b/src/Discovery/test/Eureka.Test/AppInfo/ApplicationInfoCollectionTest.cs index 78a3ee441d..6648a50ce8 100644 --- a/src/Discovery/test/Eureka.Test/AppInfo/ApplicationInfoCollectionTest.cs +++ b/src/Discovery/test/Eureka.Test/AppInfo/ApplicationInfoCollectionTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Collections.ObjectModel; using Steeltoe.Discovery.Eureka.AppInfo; using Steeltoe.Discovery.Eureka.Transport; @@ -64,8 +65,8 @@ public void Add_ExpandsTo_ApplicationMap() apps.GetInstancesByVipAddress("vip1a").Should().ContainSingle(); apps.GetInstancesByVipAddress("vip1b").Should().ContainSingle(); - apps.GetInstancesBySecureVipAddress("svip2a").Should().ContainSingle(); - apps.GetInstancesBySecureVipAddress("svip2b").Should().ContainSingle(); + apps.GetInstancesByVipAddress("svip2a").Should().ContainSingle(); + apps.GetInstancesByVipAddress("svip2b").Should().ContainSingle(); } [Fact] @@ -117,13 +118,11 @@ public void Add_AddsTo_VirtualHostInstanceMaps() app2 ]; - apps.VipInstanceMap.Should().HaveCount(2); + apps.VipInstanceMap.Should().HaveCount(4); apps.VipInstanceMap.Should().ContainKey("vapp1".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); apps.VipInstanceMap.Should().ContainKey("vapp2".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); - - apps.SecureVipInstanceMap.Should().HaveCount(2); - apps.SecureVipInstanceMap.Should().ContainKey("svapp1".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); - apps.SecureVipInstanceMap.Should().ContainKey("svapp2".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); + apps.VipInstanceMap.Should().ContainKey("svapp1".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); + apps.VipInstanceMap.Should().ContainKey("svapp2".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); } [Fact] @@ -159,26 +158,20 @@ public void RemoveInstanceFromVip_UpdatesApp_RemovesFromVirtualHostInstanceMaps( ]) ]); - apps.VipInstanceMap.Should().HaveCount(2); + apps.VipInstanceMap.Should().HaveCount(4); apps.VipInstanceMap.Should().ContainKey("vapp1".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); apps.VipInstanceMap.Should().ContainKey("vapp2".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); + apps.VipInstanceMap.Should().ContainKey("svapp1".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); + apps.VipInstanceMap.Should().ContainKey("svapp2".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); - apps.SecureVipInstanceMap.Should().HaveCount(2); - apps.SecureVipInstanceMap.Should().ContainKey("svapp1".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); - apps.SecureVipInstanceMap.Should().ContainKey("svapp2".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); - - apps.RemoveInstanceFromVip(new InstanceInfoBuilder().WithId("id2").WithVipAddress("vapp1").WithSecureVipAddress("svapp1").Build()); - apps.RemoveInstanceFromVip(new InstanceInfoBuilder().WithId("id1").WithVipAddress("vapp1").WithSecureVipAddress("svapp1").Build()); + apps.RemoveFromVipInstanceMap(new InstanceInfoBuilder().WithId("id2").WithVipAddress("vapp1").WithSecureVipAddress("svapp1").Build()); + apps.RemoveFromVipInstanceMap(new InstanceInfoBuilder().WithId("id1").WithVipAddress("vapp1").WithSecureVipAddress("svapp1").Build()); - apps.VipInstanceMap.Should().ContainSingle(); - apps.VipInstanceMap.Should().NotContainKey("vapp1".ToUpperInvariant()); - apps.VipInstanceMap.TryGetValue("vapp1".ToUpperInvariant(), out _).Should().BeFalse(); + apps.VipInstanceMap.Should().HaveCount(4); + apps.VipInstanceMap.Should().ContainKey("vapp1".ToUpperInvariant()).WhoseValue.Should().BeEmpty(); apps.VipInstanceMap.Should().ContainKey("vapp2".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); - - apps.SecureVipInstanceMap.Should().ContainSingle(); - apps.SecureVipInstanceMap.Should().NotContainKey("svapp1".ToUpperInvariant()); - apps.SecureVipInstanceMap.TryGetValue("svapp1".ToUpperInvariant(), out _).Should().BeFalse(); - apps.SecureVipInstanceMap.Should().ContainKey("svapp2".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); + apps.VipInstanceMap.Should().ContainKey("svapp1".ToUpperInvariant()).WhoseValue.Should().BeEmpty(); + apps.VipInstanceMap.Should().ContainKey("svapp2".ToUpperInvariant()).WhoseValue.Should().HaveCount(2); } [Fact] @@ -206,16 +199,22 @@ public void GetRegisteredApplication_ReturnsExpected() } [Fact] - public void GetInstancesBySecureVipAddress_ReturnsExpected() + public void GetInstancesByVipAddress_ReturnsExpected() { + InstanceInfo instance11 = new InstanceInfoBuilder().WithId("id1").WithVipAddress("vapp1").WithSecureVipAddress("svapp1").Build(); + InstanceInfo instance12 = new InstanceInfoBuilder().WithId("id2").WithVipAddress("vapp1").WithSecureVipAddress("svapp1").Build(); + var app1 = new ApplicationInfo("app1", [ - new InstanceInfoBuilder().WithId("id1").WithVipAddress("vapp1").WithSecureVipAddress("svapp1").Build(), - new InstanceInfoBuilder().WithId("id2").WithVipAddress("vapp1").WithSecureVipAddress("svapp1").Build() + instance11, + instance12 ]); + InstanceInfo instance21 = new InstanceInfoBuilder().WithId("id1").WithVipAddress("vapp2").WithSecureVipAddress("svapp2").Build(); + InstanceInfo instance22 = new InstanceInfoBuilder().WithId("id2").WithVipAddress("vapp2").WithSecureVipAddress("svapp2").Build(); + var app2 = new ApplicationInfo("app2", [ - new InstanceInfoBuilder().WithId("id1").WithVipAddress("vapp2").WithSecureVipAddress("svapp2").Build(), - new InstanceInfoBuilder().WithId("id2").WithVipAddress("vapp2").WithSecureVipAddress("svapp2").Build() + instance21, + instance22 ]); var apps = new ApplicationInfoCollection([ @@ -223,56 +222,31 @@ public void GetInstancesBySecureVipAddress_ReturnsExpected() app2 ]); - List result = apps.GetInstancesBySecureVipAddress("svapp1"); - - result.Should().HaveCount(2); - result.Should().Contain(app1.GetInstance("id1")!); - result.Should().Contain(app1.GetInstance("id2")!); - - result = apps.GetInstancesBySecureVipAddress("svapp2"); - - result.Should().HaveCount(2); - result.Should().Contain(app2.GetInstance("id1")!); - result.Should().Contain(app2.GetInstance("id2")!); - - result = apps.GetInstancesBySecureVipAddress("foobar"); - - result.Should().BeEmpty(); - } - - [Fact] - public void GetInstancesByVipAddress_ReturnsExpected() - { - var app1 = new ApplicationInfo("app1", [ - new InstanceInfoBuilder().WithId("id1").WithVipAddress("vapp1").WithSecureVipAddress("svapp1").Build(), - new InstanceInfoBuilder().WithId("id2").WithVipAddress("vapp1").WithSecureVipAddress("svapp1").Build() - ]); + ReadOnlyCollection secureInstances1 = apps.GetInstancesByVipAddress("svapp1"); - var app2 = new ApplicationInfo("app2", [ - new InstanceInfoBuilder().WithId("id1").WithVipAddress("vapp2").WithSecureVipAddress("svapp2").Build(), - new InstanceInfoBuilder().WithId("id2").WithVipAddress("vapp2").WithSecureVipAddress("svapp2").Build() - ]); + secureInstances1.Should().HaveCount(2); + secureInstances1.Should().Contain(instance11); + secureInstances1.Should().Contain(instance12); - var apps = new ApplicationInfoCollection([ - app1, - app2 - ]); + ReadOnlyCollection secureInstances2 = apps.GetInstancesByVipAddress("svapp2"); - List result = apps.GetInstancesByVipAddress("vapp1"); + secureInstances2.Should().HaveCount(2); + secureInstances2.Should().Contain(instance21); + secureInstances2.Should().Contain(instance22); - result.Should().HaveCount(2); - result.Should().Contain(app1.GetInstance("id1")!); - result.Should().Contain(app1.GetInstance("id2")!); + ReadOnlyCollection nonSecureInstances1 = apps.GetInstancesByVipAddress("vapp1"); - result = apps.GetInstancesByVipAddress("vapp2"); + nonSecureInstances1.Should().HaveCount(2); + nonSecureInstances1.Should().Contain(instance11); + nonSecureInstances1.Should().Contain(instance12); - result.Should().HaveCount(2); - result.Should().Contain(app2.GetInstance("id1")!); - result.Should().Contain(app2.GetInstance("id2")!); + ReadOnlyCollection nonSecureInstances2 = apps.GetInstancesByVipAddress("vapp2"); - result = apps.GetInstancesByVipAddress("foobar"); + nonSecureInstances2.Should().HaveCount(2); + nonSecureInstances2.Should().Contain(instance21); + nonSecureInstances2.Should().Contain(instance22); - result.Should().BeEmpty(); + apps.GetInstancesByVipAddress("foobar").Should().BeEmpty(); } [Fact] @@ -310,7 +284,7 @@ public void UpdateFromDelta_EmptyDelta_NoChange() registered.Name.Should().Be("app2"); registered.Instances.Should().HaveCount(2); - List result = apps.GetInstancesByVipAddress("vapp1"); + ReadOnlyCollection result = apps.GetInstancesByVipAddress("vapp1"); result.Should().HaveCount(2); result.Should().Contain(app1.GetInstance("id1")!); @@ -375,7 +349,7 @@ public void UpdateFromDelta_AddNewAppNewInstance_UpdatesCorrectly() registered.Name.Should().Be("app3"); registered.Instances.Should().ContainSingle(); - List result = apps.GetInstancesByVipAddress("vapp1"); + ReadOnlyCollection result = apps.GetInstancesByVipAddress("vapp1"); result.Should().HaveCount(2); result.Should().Contain(app1.GetInstance("id1")!); @@ -438,7 +412,7 @@ public void UpdateFromDelta_ExistingAppWithAddNewInstance_UpdatesCorrectly() registered.Name.Should().Be("app2"); registered.Instances.Should().HaveCount(3); - List result = apps.GetInstancesByVipAddress("vapp1"); + ReadOnlyCollection result = apps.GetInstancesByVipAddress("vapp1"); result.Should().HaveCount(2); result.Should().Contain(app1.GetInstance("id1")!); @@ -501,7 +475,7 @@ public void UpdateFromDelta_ExistingAppWithModifyInstance_UpdatesCorrectly() registered.Instances.Should().HaveCount(2); registered.Instances.Should().AllSatisfy(instance => instance.Status.Should().Be(InstanceStatus.Up)); - List result = apps.GetInstancesByVipAddress("vapp1"); + ReadOnlyCollection result = apps.GetInstancesByVipAddress("vapp1"); result.Should().HaveCount(2); result.Should().Contain(app1.GetInstance("id1")!); @@ -562,7 +536,7 @@ public void UpdateFromDelta_ExistingAppWithRemovedInstance_UpdatesCorrectly() registered.Name.Should().Be("app2"); registered.Instances.Should().ContainSingle().Which.Status.Should().Be(InstanceStatus.Up); - List result = apps.GetInstancesByVipAddress("vapp1"); + ReadOnlyCollection result = apps.GetInstancesByVipAddress("vapp1"); result.Should().HaveCount(2); result.Should().Contain(app1.GetInstance("id1")!); diff --git a/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs b/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs index 68e0e762f3..84f39e1df6 100644 --- a/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs +++ b/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs @@ -169,6 +169,8 @@ public async Task Constructor_Initializes_Correctly() thisService.ServiceId.Should().Be("DEMO"); thisService.InstanceId.Should().Be($"{instanceOptions.HostName}:demo:5000"); thisService.Uri.Should().Be(new Uri($"http://{instanceOptions.HostName}:5000")); + thisService.NonSecureUri.Should().Be(thisService.Uri); + thisService.SecureUri.Should().BeNull(); } [Fact] @@ -415,7 +417,7 @@ public async Task UnRegisterAsync_Succeeds_WhenOKStatusReturned() } [Fact] - public async Task GetInstancesByVipAddress_ReturnsExpected() + public async Task GetInstancesAsync_ReturnsExpected() { var appSettings = new Dictionary { @@ -433,13 +435,13 @@ public async Task GetInstancesByVipAddress_ReturnsExpected() discoveryClient.Applications = new ApplicationInfoCollection([ new ApplicationInfo("app1", [ - new InstanceInfo("id1", "app1", "localhost", "192.168.56.1", new DataCenterInfo(), TimeProvider.System) + new InstanceInfo("id11", "app1", "localhost", "192.168.56.1", new DataCenterInfo(), TimeProvider.System) { VipAddress = "vapp1", SecureVipAddress = "svapp1", Status = InstanceStatus.Down }, - new InstanceInfo("id2", "app1", "localhost", "192.168.56.1", new DataCenterInfo(), TimeProvider.System) + new InstanceInfo("id12", "app1", "localhost", "192.168.56.1", new DataCenterInfo(), TimeProvider.System) { VipAddress = "vapp1", SecureVipAddress = "svapp1", @@ -462,18 +464,18 @@ public async Task GetInstancesByVipAddress_ReturnsExpected() ]) ]); - IReadOnlyList result = discoveryClient.GetInstancesByVipAddress("vapp1", false); + IList result = await discoveryClient.GetInstancesAsync("vapp1", TestContext.Current.CancellationToken); result.Should().HaveCount(2); - result.Should().ContainSingle(info => info.InstanceId == "id1"); - result.Should().ContainSingle(info => info.InstanceId == "id2"); + result.Should().ContainSingle(info => info.InstanceId == "id11"); + result.Should().ContainSingle(info => info.InstanceId == "id12"); - result = discoveryClient.GetInstancesByVipAddress("boohoo", false); + result = await discoveryClient.GetInstancesAsync("boohoo", TestContext.Current.CancellationToken); result.Should().BeEmpty(); discoveryClient.Applications.ReturnUpInstancesOnly = true; - result = discoveryClient.GetInstancesByVipAddress("vapp1", false); + result = await discoveryClient.GetInstancesAsync("vapp1", TestContext.Current.CancellationToken); result.Should().BeEmpty(); } diff --git a/src/Discovery/test/Eureka.Test/EurekaServiceInstanceTest.cs b/src/Discovery/test/Eureka.Test/EurekaServiceInstanceTest.cs index 2a5403b94f..78d30feba8 100644 --- a/src/Discovery/test/Eureka.Test/EurekaServiceInstanceTest.cs +++ b/src/Discovery/test/Eureka.Test/EurekaServiceInstanceTest.cs @@ -33,6 +33,8 @@ public void InstanceWithBothPorts() serviceInstance.IsSecure.Should().BeTrue(); serviceInstance.Metadata.Should().ContainKey("foo").WhoseValue.Should().Be("bar"); serviceInstance.Uri.Should().Be("https://host:9999/"); + serviceInstance.NonSecureUri.Should().Be("http://host:8888/"); + serviceInstance.SecureUri.Should().Be("https://host:9999/"); } [Fact] @@ -51,6 +53,8 @@ public void InstanceWithSecurePort() serviceInstance.Port.Should().Be(instance.SecurePort); serviceInstance.IsSecure.Should().BeTrue(); serviceInstance.Uri.Should().Be("https://host:9999/"); + serviceInstance.NonSecureUri.Should().BeNull(); + serviceInstance.SecureUri.Should().Be("https://host:9999/"); } [Fact] @@ -69,6 +73,8 @@ public void InstanceWithNonSecurePort() serviceInstance.Port.Should().Be(instance.NonSecurePort); serviceInstance.IsSecure.Should().BeFalse(); serviceInstance.Uri.Should().Be("http://host:8888/"); + serviceInstance.NonSecureUri.Should().Be("http://host:8888/"); + serviceInstance.SecureUri.Should().BeNull(); } [Fact] @@ -87,5 +93,7 @@ public void InstanceWithoutPort() serviceInstance.Port.Should().Be(0); serviceInstance.IsSecure.Should().BeFalse(); serviceInstance.Uri.Should().Be("http://host:0/"); + serviceInstance.NonSecureUri.Should().BeNull(); + serviceInstance.SecureUri.Should().BeNull(); } } diff --git a/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs b/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs index fb9d69fa7b..08f9e920a5 100644 --- a/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs +++ b/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs @@ -246,6 +246,8 @@ private sealed class TestServiceInstance(Uri uri) : IServiceInstance public int Port => throw new NotImplementedException(); public bool IsSecure => throw new NotImplementedException(); public Uri Uri { get; } = uri; + public Uri? NonSecureUri => throw new NotImplementedException(); + public Uri? SecureUri => throw new NotImplementedException(); public IReadOnlyDictionary Metadata => throw new NotImplementedException(); } From 883f3268bc3781a1f1b7663746bba0bb9bf9c62c Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 16 Oct 2025 13:59:51 +0200 Subject: [PATCH 09/81] Optimize NuGet package restore (#1605) * Optimize NuGet package restore Don't query for all packages in all feeds * Trigger component builds on nuget.config change --- .github/workflows/component-common.yml | 1 + .github/workflows/component-configuration.yml | 1 + .github/workflows/component-connectors.yml | 1 + .github/workflows/component-discovery.yml | 1 + .github/workflows/component-logging.yml | 1 + .github/workflows/component-management.yml | 1 + .github/workflows/component-security.yml | 1 + nuget.config | 15 ++++++++++++--- 8 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/component-common.yml b/.github/workflows/component-common.yml index 80f6ea8e7b..4909d6736a 100644 --- a/.github/workflows/component-common.yml +++ b/.github/workflows/component-common.yml @@ -8,6 +8,7 @@ on: - stylecop.json - '*.props' - '*.ruleset' + - nuget.config - .config/dotnet-tools.json - .github/workflows/component-shared-workflow.yml - .github/workflows/component-common.yml diff --git a/.github/workflows/component-configuration.yml b/.github/workflows/component-configuration.yml index f0cae5d49c..3f6cc30272 100644 --- a/.github/workflows/component-configuration.yml +++ b/.github/workflows/component-configuration.yml @@ -8,6 +8,7 @@ on: - stylecop.json - '*.props' - '*.ruleset' + - nuget.config - .config/dotnet-tools.json - .github/workflows/component-shared-workflow.yml - .github/workflows/component-configuration.yml diff --git a/.github/workflows/component-connectors.yml b/.github/workflows/component-connectors.yml index 375c7677f9..43f173b6f4 100644 --- a/.github/workflows/component-connectors.yml +++ b/.github/workflows/component-connectors.yml @@ -8,6 +8,7 @@ on: - stylecop.json - '*.props' - '*.ruleset' + - nuget.config - .config/dotnet-tools.json - .github/workflows/component-shared-workflow.yml - .github/workflows/component-connectors.yml diff --git a/.github/workflows/component-discovery.yml b/.github/workflows/component-discovery.yml index 12bf4088a7..7e991eb1db 100644 --- a/.github/workflows/component-discovery.yml +++ b/.github/workflows/component-discovery.yml @@ -8,6 +8,7 @@ on: - stylecop.json - '*.props' - '*.ruleset' + - nuget.config - .config/dotnet-tools.json - .github/workflows/component-shared-workflow.yml - .github/workflows/component-discovery.yml diff --git a/.github/workflows/component-logging.yml b/.github/workflows/component-logging.yml index 1a453d26a8..1ad817653a 100644 --- a/.github/workflows/component-logging.yml +++ b/.github/workflows/component-logging.yml @@ -8,6 +8,7 @@ on: - stylecop.json - '*.props' - '*.ruleset' + - nuget.config - .config/dotnet-tools.json - .github/workflows/component-shared-workflow.yml - .github/workflows/component-logging.yml diff --git a/.github/workflows/component-management.yml b/.github/workflows/component-management.yml index e3a01e34f8..da124fbeea 100644 --- a/.github/workflows/component-management.yml +++ b/.github/workflows/component-management.yml @@ -8,6 +8,7 @@ on: - stylecop.json - '*.props' - '*.ruleset' + - nuget.config - .config/dotnet-tools.json - .github/workflows/component-shared-workflow.yml - .github/workflows/component-management.yml diff --git a/.github/workflows/component-security.yml b/.github/workflows/component-security.yml index cfe3a805f5..25658195ec 100644 --- a/.github/workflows/component-security.yml +++ b/.github/workflows/component-security.yml @@ -8,6 +8,7 @@ on: - stylecop.json - '*.props' - '*.ruleset' + - nuget.config - .config/dotnet-tools.json - .github/workflows/component-shared-workflow.yml - .github/workflows/component-security.yml diff --git a/nuget.config b/nuget.config index f5253c498c..d363dbb2bd 100644 --- a/nuget.config +++ b/nuget.config @@ -1,8 +1,17 @@ - + - + + - + + + + + + + + + From cfc15ca9dafb1dd20b1282fd85511b803fb299d5 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:15:57 +0200 Subject: [PATCH 10/81] Lower the level of recurring logging to reduce noise in apps (#1608) * Reduce the level of recurring logging to reduce noise in apps * Remove redundant ? * Log at Info level only the first time --- .../ConfigServerConfigurationProvider.cs | 4 +--- .../RoundRobinLoadBalancerTest.cs | 4 ++-- .../SpringBootAdminPeriodicRefresh.cs | 5 ++++- .../SpringBootAdminRefreshRunner.cs | 18 +++++++++++++----- .../SpringBootAdminRefreshRunnerTest.cs | 14 +++++++------- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs b/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs index cbb723915d..7963604378 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs @@ -725,9 +725,7 @@ internal async Task RefreshVaultTokenAsync(CancellationToken cancellationToken) Uri uri = GetVaultRenewUri(); HttpRequestMessage message = await GetVaultRenewRequestMessageAsync(uri, cancellationToken); - _logger.LogInformation("Renewing Vault token {Token} for {Ttl} milliseconds at Uri {Uri}", obscuredToken, ClientOptions.TokenTtl, - uri.ToMaskedString()); - + _logger.LogDebug("Renewing Vault token {Token} for {Ttl} milliseconds at Uri {Uri}", obscuredToken, ClientOptions.TokenTtl, uri.ToMaskedString()); using HttpResponseMessage response = await httpClient.SendAsync(message, cancellationToken); if (response.StatusCode != HttpStatusCode.OK) diff --git a/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs b/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs index 08f9e920a5..0f41aab316 100644 --- a/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs +++ b/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs @@ -246,8 +246,8 @@ private sealed class TestServiceInstance(Uri uri) : IServiceInstance public int Port => throw new NotImplementedException(); public bool IsSecure => throw new NotImplementedException(); public Uri Uri { get; } = uri; - public Uri? NonSecureUri => throw new NotImplementedException(); - public Uri? SecureUri => throw new NotImplementedException(); + public Uri NonSecureUri => throw new NotImplementedException(); + public Uri SecureUri => throw new NotImplementedException(); public IReadOnlyDictionary Metadata => throw new NotImplementedException(); } diff --git a/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminPeriodicRefresh.cs b/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminPeriodicRefresh.cs index f2cf82a106..40139c7c9b 100644 --- a/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminPeriodicRefresh.cs +++ b/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminPeriodicRefresh.cs @@ -40,6 +40,7 @@ private async Task TimerLoopAsync(TimeSpan interval) try { _logger.LogDebug("Starting periodic refresh loop with interval {Interval}.", interval); + bool isFirstTime = true; do { @@ -47,12 +48,14 @@ private async Task TimerLoopAsync(TimeSpan interval) try { - await _runner.RunAsync(_timerTokenSource.Token); + await _runner.RunAsync(isFirstTime, _timerTokenSource.Token); } catch (Exception exception) when (!exception.IsCancellation()) { _logger.LogWarning(exception, "Refresh cycle failed."); } + + isFirstTime = false; } while (await _periodicTimer.WaitForNextTickAsync(_timerTokenSource.Token)); } diff --git a/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminRefreshRunner.cs b/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminRefreshRunner.cs index 4f1ce42ff5..eaf7784216 100644 --- a/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminRefreshRunner.cs +++ b/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminRefreshRunner.cs @@ -54,7 +54,7 @@ public SpringBootAdminRefreshRunner(AppUrlCalculator appUrlCalculator, SpringBoo _logger = logger; } - public async Task RunAsync(CancellationToken cancellationToken) + public async Task RunAsync(bool isFirstTime, CancellationToken cancellationToken) { _logger.LogDebug("Validating options."); SpringBootAdminClientOptions clientOptions = _clientOptionsMonitor.CurrentValue; @@ -66,7 +66,7 @@ public async Task RunAsync(CancellationToken cancellationToken) await SafeUnregisterAsync(_lastGoodOptions, cancellationToken); } - await RegisterAsync(clientOptions, cancellationToken); + await RegisterAsync(clientOptions, isFirstTime, cancellationToken); } private void ValidateAndSetOptions(SpringBootAdminClientOptions options) @@ -124,11 +124,19 @@ private void ValidateAndSetOptions(SpringBootAdminClientOptions options) } } - private async Task RegisterAsync(SpringBootAdminClientOptions clientOptions, CancellationToken cancellationToken) + private async Task RegisterAsync(SpringBootAdminClientOptions clientOptions, bool isFirstTime, CancellationToken cancellationToken) { Application app = CreateApplication(new Uri(clientOptions.BaseUrl!), clientOptions); - _logger.LogInformation("Registering with Spring Boot Admin Server at {Url}.", clientOptions.Url); + if (isFirstTime) + { + _logger.LogInformation("Registering with Spring Boot Admin Server at {Url}.", clientOptions.Url); + } + else + { + _logger.LogDebug("Registering with Spring Boot Admin Server at {Url}.", clientOptions.Url); + } + _lastRegistrationId = await _springBootAdminApiClient.RegisterAsync(app, clientOptions, cancellationToken); _lastGoodOptions = clientOptions; } @@ -175,7 +183,7 @@ private async Task SafeUnregisterAsync(SpringBootAdminClientOptions clientOption { try { - _logger.LogInformation("Unregistering from Spring Boot Admin Server at {Url}.", clientOptions.Url); + _logger.LogDebug("Unregistering from Spring Boot Admin Server at {Url}.", clientOptions.Url); await _springBootAdminApiClient.UnregisterAsync(_lastRegistrationId, clientOptions, cancellationToken); _lastRegistrationId = null; } diff --git a/src/Management/test/Endpoint.Test/SpringBootAdminClient/SpringBootAdminRefreshRunnerTest.cs b/src/Management/test/Endpoint.Test/SpringBootAdminClient/SpringBootAdminRefreshRunnerTest.cs index 9761d19fe4..c1349cdbd7 100644 --- a/src/Management/test/Endpoint.Test/SpringBootAdminClient/SpringBootAdminRefreshRunnerTest.cs +++ b/src/Management/test/Endpoint.Test/SpringBootAdminClient/SpringBootAdminRefreshRunnerTest.cs @@ -83,7 +83,7 @@ public async Task BindsConfiguration() app.Services.GetRequiredService().Using(handler); var runner = app.Services.GetRequiredService(); - await runner.RunAsync(TestContext.Current.CancellationToken); + await runner.RunAsync(true, TestContext.Current.CancellationToken); SpringBootAdminClientOptions? options = runner.LastGoodOptions; options.Should().NotBeNull(); @@ -116,7 +116,7 @@ public async Task FailsOnMissingConfiguration() await using WebApplication app = builder.Build(); var runner = app.Services.GetRequiredService(); - Func action = async () => await runner.RunAsync(TestContext.Current.CancellationToken); + Func action = async () => await runner.RunAsync(true, TestContext.Current.CancellationToken); string[] errorsExpected = [ @@ -145,7 +145,7 @@ public async Task FailsOnInvalidConfiguration() await using WebApplication app = builder.Build(); var runner = app.Services.GetRequiredService(); - Func action = async () => await runner.RunAsync(TestContext.Current.CancellationToken); + Func action = async () => await runner.RunAsync(true, TestContext.Current.CancellationToken); string[] errorsExpected = [ @@ -175,7 +175,7 @@ public async Task FailsWhenConfigurationForBasePathIsUrl() await using WebApplication app = builder.Build(); var runner = app.Services.GetRequiredService(); - Func action = async () => await runner.RunAsync(TestContext.Current.CancellationToken); + Func action = async () => await runner.RunAsync(true, TestContext.Current.CancellationToken); await action.Should().ThrowExactlyAsync() .WithMessage("Use BaseUrl instead of BasePath to configure the absolute URL to register with"); @@ -202,7 +202,7 @@ public async Task BindsApplicationNameFromSpringConfiguration() app.Services.GetRequiredService().Using(handler); var runner = app.Services.GetRequiredService(); - await runner.RunAsync(TestContext.Current.CancellationToken); + await runner.RunAsync(true, TestContext.Current.CancellationToken); SpringBootAdminClientOptions? options = runner.LastGoodOptions; options.Should().NotBeNull(); @@ -249,7 +249,7 @@ public async Task SendsRegisterRequestForDefaultConfiguration() app.Services.GetRequiredService().Using(handler); var runner = app.Services.GetRequiredService(); - await runner.RunAsync(TestContext.Current.CancellationToken); + await runner.RunAsync(true, TestContext.Current.CancellationToken); runner.LastRegistrationId.Should().Be("1234567"); runner.LastGoodOptions.Should().NotBeNull(); @@ -301,7 +301,7 @@ public async Task SendsRegisterRequestForCustomConfiguration() app.Services.GetRequiredService().Using(handler); var runner = app.Services.GetRequiredService(); - await runner.RunAsync(TestContext.Current.CancellationToken); + await runner.RunAsync(true, TestContext.Current.CancellationToken); runner.LastRegistrationId.Should().Be("1234567"); runner.LastGoodOptions.Should().NotBeNull(); From 0f0672525b6551ebaba1f063219c5f4d0feab3db Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:41:53 +0200 Subject: [PATCH 11/81] Update places where BindConfiguration is called, which registers anonymous delegates, and is thus not safe to be called multiple times. (#1609) With a pre-check in place, several Try* methods behind it can be replaced for efficiency, but only if: - The implementation is an internal type and does not implement a public interface - The implementation type is not shared between Steeltoe components --- ...CloudFoundryServiceCollectionExtensions.cs | 24 ++++++++++++------- .../CloudFoundryHostBuilderExtensionsTest.cs | 14 +++++++++++ ...onfigurationServiceCollectionExtensions.cs | 15 +++++++++--- .../ConsulServiceCollectionExtensions.cs | 15 +++++++++--- .../EurekaServiceCollectionExtensions.cs | 17 ++++++++----- .../ConfigurationDiscoveryClientTest.cs | 12 ++++++++++ .../RegisterMultipleDiscoveryClientsTest.cs | 16 +++++++++++++ .../SerilogLoggingBuilderExtensions.cs | 2 +- 8 files changed, 94 insertions(+), 21 deletions(-) diff --git a/src/Configuration/src/CloudFoundry/CloudFoundryServiceCollectionExtensions.cs b/src/Configuration/src/CloudFoundry/CloudFoundryServiceCollectionExtensions.cs index 4c7147867f..a53699ed6f 100644 --- a/src/Configuration/src/CloudFoundry/CloudFoundryServiceCollectionExtensions.cs +++ b/src/Configuration/src/CloudFoundry/CloudFoundryServiceCollectionExtensions.cs @@ -28,17 +28,25 @@ public static IServiceCollection AddCloudFoundryOptions(this IServiceCollection { ArgumentNullException.ThrowIfNull(services); - services.AddOptions().BindConfiguration("vcap"); + if (!IsRegistered(services)) + { + services.AddOptions().BindConfiguration("vcap"); - services.TryAddEnumerable(ServiceDescriptor.Singleton, ConfigureApplicationInstanceInfo>()); - services.TryAddEnumerable(ServiceDescriptor.Singleton, ConfigureCloudFoundryApplicationOptions>()); + services.TryAddEnumerable(ServiceDescriptor.Singleton, ConfigureApplicationInstanceInfo>()); + services.AddSingleton, ConfigureCloudFoundryApplicationOptions>(); - services.Replace(ServiceDescriptor.Singleton(serviceProvider => - { - var optionsMonitor = serviceProvider.GetRequiredService>(); - return optionsMonitor.CurrentValue; - })); + services.Replace(ServiceDescriptor.Singleton(serviceProvider => + { + var optionsMonitor = serviceProvider.GetRequiredService>(); + return optionsMonitor.CurrentValue; + })); + } return services; } + + private static bool IsRegistered(IServiceCollection services) + { + return services.Any(descriptor => descriptor.SafeGetImplementationType() == typeof(ConfigureCloudFoundryApplicationOptions)); + } } diff --git a/src/Configuration/test/CloudFoundry.Test/CloudFoundryHostBuilderExtensionsTest.cs b/src/Configuration/test/CloudFoundry.Test/CloudFoundryHostBuilderExtensionsTest.cs index fdf8871894..c4527bcb23 100644 --- a/src/Configuration/test/CloudFoundry.Test/CloudFoundryHostBuilderExtensionsTest.cs +++ b/src/Configuration/test/CloudFoundry.Test/CloudFoundryHostBuilderExtensionsTest.cs @@ -63,4 +63,18 @@ public void HostApplicationAddCloudFoundryConfiguration_Adds() configurationRoot.EnumerateProviders().Should().ContainSingle(); } + + [Fact] + public void Does_not_register_multiple_times() + { + WebApplicationBuilder hostBuilder = TestWebApplicationBuilderFactory.Create(); + hostBuilder.AddCloudFoundryConfiguration(); + int beforeSourceCount = hostBuilder.Configuration.EnumerateSources().Count(); + int beforeServiceCount = hostBuilder.Services.Count; + + hostBuilder.AddCloudFoundryConfiguration(); + + hostBuilder.Configuration.EnumerateSources().Count().Should().Be(beforeSourceCount); + hostBuilder.Services.Count.Should().Be(beforeServiceCount); + } } diff --git a/src/Discovery/src/Configuration/ConfigurationServiceCollectionExtensions.cs b/src/Discovery/src/Configuration/ConfigurationServiceCollectionExtensions.cs index 652c368278..03ad09ee9d 100644 --- a/src/Discovery/src/Configuration/ConfigurationServiceCollectionExtensions.cs +++ b/src/Discovery/src/Configuration/ConfigurationServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Steeltoe.Common; using Steeltoe.Common.Discovery; namespace Steeltoe.Discovery.Configuration; @@ -49,11 +50,19 @@ public static IServiceCollection AddConfigurationDiscoveryClient(this IServiceCo { ArgumentNullException.ThrowIfNull(services); - services.AddOptions().BindConfiguration(ConfigurationDiscoveryOptions.ConfigurationPrefix); + if (!IsRegistered(services)) + { + services.AddOptions().BindConfiguration(ConfigurationDiscoveryOptions.ConfigurationPrefix); - services.TryAddEnumerable(ServiceDescriptor.Singleton()); - services.AddHostedService(); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.AddHostedService(); + } return services; } + + private static bool IsRegistered(IServiceCollection services) + { + return services.Any(descriptor => descriptor.SafeGetImplementationType() == typeof(ConfigurationDiscoveryClient)); + } } diff --git a/src/Discovery/src/Consul/ConsulServiceCollectionExtensions.cs b/src/Discovery/src/Consul/ConsulServiceCollectionExtensions.cs index 93fc2f924d..bb1fdc71c2 100644 --- a/src/Discovery/src/Consul/ConsulServiceCollectionExtensions.cs +++ b/src/Discovery/src/Consul/ConsulServiceCollectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using Steeltoe.Common; using Steeltoe.Common.Discovery; using Steeltoe.Common.Extensions; using Steeltoe.Common.HealthChecks; @@ -32,12 +33,20 @@ public static IServiceCollection AddConsulDiscoveryClient(this IServiceCollectio { ArgumentNullException.ThrowIfNull(services); - ConfigureConsulServices(services); - AddConsulServices(services); + if (!IsRegistered(services)) + { + ConfigureConsulServices(services); + AddConsulServices(services); + } return services; } + private static bool IsRegistered(IServiceCollection services) + { + return services.Any(descriptor => descriptor.SafeGetImplementationType() == typeof(PostConfigureConsulDiscoveryOptions)); + } + private static void ConfigureConsulServices(IServiceCollection services) { services.AddApplicationInstanceInfo(); @@ -96,6 +105,6 @@ private static void AddConsulServices(IServiceCollection services) services.AddSingleton(); services.TryAddEnumerable(ServiceDescriptor.Singleton()); services.AddHostedService(); - services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.AddSingleton(); } } diff --git a/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs b/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs index f41e70d962..3348501e08 100644 --- a/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs +++ b/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs @@ -35,7 +35,7 @@ public static IServiceCollection AddEurekaDiscoveryClient(this IServiceCollectio { ArgumentNullException.ThrowIfNull(services); - if (services.All(descriptor => descriptor.SafeGetImplementationType() != typeof(EurekaDiscoveryClient))) + if (!IsRegistered(services)) { ConfigureEurekaServices(services); AddEurekaServices(services); @@ -44,6 +44,11 @@ public static IServiceCollection AddEurekaDiscoveryClient(this IServiceCollectio return services; } + private static bool IsRegistered(IServiceCollection services) + { + return services.Any(descriptor => descriptor.SafeGetImplementationType() == typeof(EurekaDiscoveryClient)); + } + private static void ConfigureEurekaServices(IServiceCollection services) { services.AddApplicationInstanceInfo(); @@ -86,9 +91,9 @@ private static void ConfigureEurekaInstanceOptions(IServiceCollection services) private static void AddEurekaServices(IServiceCollection services) { services.TryAddSingleton(TimeProvider.System); - services.AddSingleton(); - services.TryAddEnumerable(ServiceDescriptor.Singleton()); - services.AddSingleton(); + services.TryAddSingleton(); + services.AddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(serviceProvider => serviceProvider.GetRequiredService()); @@ -103,9 +108,9 @@ private static void AddEurekaServices(IServiceCollection services) private static void AddEurekaClient(IServiceCollection services) { services.TryAddSingleton(); - services.TryAddSingleton>(); + services.AddSingleton>(); services.TryAddSingleton(); - services.TryAddSingleton(); + services.AddSingleton(); services.ConfigureCertificateOptions("Eureka"); IHttpClientBuilder eurekaHttpClientBuilder = services.AddHttpClient("Eureka"); diff --git a/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs b/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs index 10b2856e2f..90f12d9797 100644 --- a/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs +++ b/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs @@ -179,4 +179,16 @@ public async Task RegistersHostedService() serviceProvider.GetServices().OfType().Should().ContainSingle(); } + + [Fact] + public void Does_not_register_multiple_times() + { + var services = new ServiceCollection(); + services.AddConfigurationDiscoveryClient(); + int beforeServiceCount = services.Count; + + services.AddConfigurationDiscoveryClient(); + + services.Count.Should().Be(beforeServiceCount); + } } diff --git a/src/Discovery/test/HttpClients.Test/RegisterMultipleDiscoveryClientsTest.cs b/src/Discovery/test/HttpClients.Test/RegisterMultipleDiscoveryClientsTest.cs index 98d8f09d57..24812b8fd8 100644 --- a/src/Discovery/test/HttpClients.Test/RegisterMultipleDiscoveryClientsTest.cs +++ b/src/Discovery/test/HttpClients.Test/RegisterMultipleDiscoveryClientsTest.cs @@ -873,4 +873,20 @@ public async Task WithMultipleClients_AddsDiscoveryClients() serviceProvider.GetServices().OfType().Should().ContainSingle(); serviceProvider.GetServices().OfType().Should().BeEmpty(); } + + [Fact] + public void WithMultipleClients_DotNotRegisterMultipleTimes() + { + var services = new ServiceCollection(); + services.AddConfigurationDiscoveryClient(); + services.AddConsulDiscoveryClient(); + services.AddEurekaDiscoveryClient(); + int beforeServiceCount = services.Count; + + services.AddConfigurationDiscoveryClient(); + services.AddConsulDiscoveryClient(); + services.AddEurekaDiscoveryClient(); + + services.Count.Should().Be(beforeServiceCount); + } } diff --git a/src/Logging/src/DynamicSerilog/SerilogLoggingBuilderExtensions.cs b/src/Logging/src/DynamicSerilog/SerilogLoggingBuilderExtensions.cs index 1569425b83..40bb2ce13c 100644 --- a/src/Logging/src/DynamicSerilog/SerilogLoggingBuilderExtensions.cs +++ b/src/Logging/src/DynamicSerilog/SerilogLoggingBuilderExtensions.cs @@ -104,7 +104,7 @@ public static ILoggingBuilder AddDynamicSerilog(this ILoggingBuilder builder, Lo private static bool IsSerilogDynamicLoggerProviderAlreadyRegistered(ILoggingBuilder builder) { return builder.Services.Any(descriptor => - descriptor.ServiceType == typeof(ILoggerProvider) && descriptor.SafeGetImplementationType() == typeof(DynamicSerilogLoggerProvider)); + descriptor.SafeGetImplementationType() == typeof(DynamicSerilogLoggerProvider) && descriptor.ServiceType == typeof(ILoggerProvider)); } private static void AssertNoDynamicLoggerProviderRegistered(ILoggingBuilder builder) From e7bbe7294ccf2af4d1ae762e1621a9fcf4f39537 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Fri, 24 Oct 2025 00:36:45 +0200 Subject: [PATCH 12/81] Capture more diagnostics when memory dump test fails, sync Steeltoe logs with CLR dump logs (#1610) --- .../ThreadDump/EventPipeThreadDumper.cs | 41 +++++++++++++------ .../ThreadDump/EventPipeThreadDumperTest.cs | 7 +++- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/Management/src/Endpoint/Actuators/ThreadDump/EventPipeThreadDumper.cs b/src/Management/src/Endpoint/Actuators/ThreadDump/EventPipeThreadDumper.cs index 4270877b40..9acde2baca 100644 --- a/src/Management/src/Endpoint/Actuators/ThreadDump/EventPipeThreadDumper.cs +++ b/src/Management/src/Endpoint/Actuators/ThreadDump/EventPipeThreadDumper.cs @@ -88,12 +88,12 @@ public async Task> DumpThreadsAsync(CancellationToken cancella internal async Task CaptureLogOutputAsync(Func> action, CancellationToken cancellationToken) { - bool isLogEnabled = _logger.IsEnabled(LogLevel.Trace); + bool isTraceLogEnabled = _logger.IsEnabled(LogLevel.Trace); using var logStream = new MemoryStream(); Exception? error = null; TResult? result = default; - await using (TextWriter logWriter = isLogEnabled ? new StreamWriter(logStream, leaveOpen: true) : TextWriter.Null) + await using (TextWriter logWriter = isTraceLogEnabled ? new StreamWriter(logStream, leaveOpen: true) : TextWriter.Null) { try { @@ -108,7 +108,7 @@ internal async Task CaptureLogOutputAsync(Func CaptureLogOutputAsync(Func> GetThreadsFromEventPipeSessionAsync(EventPi var computer = new SampleProfilerThreadTimeComputer(eventLog, symbolReader); computer.GenerateThreadTimeStacks(stackSource); - List results = ReadStackSource(stackSource, symbolReader).ToList(); + List results = ReadStackSource(stackSource, symbolReader, logWriter).ToList(); - _logger.LogTrace("Finished thread walk."); + _logger.LogTrace("Finished thread walk, found {Count} results.", results.Count); return results; } finally @@ -231,12 +231,14 @@ private static SymbolReader CreateSymbolReader(TextWriter logWriter) }; } - private IEnumerable ReadStackSource(MutableTraceEventStackSource stackSource, SymbolReader symbolReader) + private IEnumerable ReadStackSource(MutableTraceEventStackSource stackSource, SymbolReader symbolReader, TextWriter logWriter) { var samplesForThread = new Dictionary>(); stackSource.ForEach(sample => { + logWriter.WriteLine($"[Steeltoe] Tracking sample: {sample}"); + StackSourceCallStackIndex stackIndex = sample.StackIndex; while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false).StartsWith(ThreadIdTemplate, StringComparison.Ordinal)) @@ -257,10 +259,17 @@ private IEnumerable ReadStackSource(MutableTraceEventStackSource sta } }); + // Workaround for Sonar bug, which incorrectly flags the code as unreachable. +#pragma warning disable S2589 // Boolean expressions should not be gratuitous + logWriter.WriteLine(samplesForThread.Count == 0 + ? "[Steeltoe] WARN: No managed samples found in memory dump." + : $"[Steeltoe] Start analyzing all {samplesForThread.Count} threads."); +#pragma warning restore S2589 // Boolean expressions should not be gratuitous + // For every thread recorded in our trace, use the first stack. foreach ((int threadId, List samples) in samplesForThread) { - _logger.LogDebug("Found {Stacks} stacks for thread {Thread}.", samples.Count, threadId); + logWriter.WriteLine($"[Steeltoe] Found {samples.Count} samples for thread {threadId}, analyzing the first one."); var threadInfo = new ThreadInfo { @@ -269,7 +278,13 @@ private IEnumerable ReadStackSource(MutableTraceEventStackSource sta ThreadName = $"Thread-{threadId:D5}" }; - List stackTrace = GetStackTrace(threadId, samples[0], stackSource, symbolReader).ToList(); + List stackTrace = GetStackTrace(threadId, samples[0], stackSource, symbolReader, logWriter).ToList(); + + if (logWriter != TextWriter.Null) + { + int managedCount = stackTrace.Count(frame => !frame.IsNativeMethod); + logWriter.WriteLine($"[Steeltoe] Found {managedCount} of {stackTrace.Count} frames in managed code for thread {threadId}."); + } foreach (StackTraceElement stackFrame in stackTrace) { @@ -289,10 +304,10 @@ private static int ExtractThreadId(string frameName) return int.Parse(frameName.AsSpan(ThreadIdTemplate.Length, firstIndex - ThreadIdTemplate.Length), CultureInfo.InvariantCulture); } - private IEnumerable GetStackTrace(int threadId, StackSourceSample stackSourceSample, TraceEventStackSource stackSource, - SymbolReader symbolReader) + private static IEnumerable GetStackTrace(int threadId, StackSourceSample stackSourceSample, TraceEventStackSource stackSource, + SymbolReader symbolReader, TextWriter logWriter) { - _logger.LogDebug("Processing thread with ID: {Thread}.", threadId); + logWriter.WriteLine($"[Steeltoe] Walking stack frames of thread {threadId}."); StackSourceCallStackIndex stackIndex = stackSourceSample.StackIndex; StackSourceFrameIndex frameIndex = stackSource.GetFrameIndex(stackIndex); diff --git a/src/Management/test/Endpoint.Test/Actuators/ThreadDump/EventPipeThreadDumperTest.cs b/src/Management/test/Endpoint.Test/Actuators/ThreadDump/EventPipeThreadDumperTest.cs index 01346608b3..0cedf51ef4 100644 --- a/src/Management/test/Endpoint.Test/Actuators/ThreadDump/EventPipeThreadDumperTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/ThreadDump/EventPipeThreadDumperTest.cs @@ -35,7 +35,12 @@ public async Task Can_resolve_source_location_from_pdb() StackTraceElement? backgroundThreadFrame = threads.SelectMany(thread => thread.StackTrace) .FirstOrDefault(frame => frame.MethodName == "BackgroundThreadCallback(class System.Object)"); - backgroundThreadFrame.Should().NotBeNull(); + if (backgroundThreadFrame == null) + { + string logs = loggerProvider.GetAsText(); + throw new InvalidOperationException($"Failed to find expected stack frame. Captured log:{System.Environment.NewLine}{logs}"); + } + backgroundThreadFrame.IsNativeMethod.Should().BeFalse(); backgroundThreadFrame.ModuleName.Should().Be(GetType().Assembly.GetName().Name); backgroundThreadFrame.ClassName.Should().Be(typeof(NestedType).FullName); From 034e2d0d99ca1dfee9471444a0626a2118f8173e Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 30 Oct 2025 14:39:35 +0100 Subject: [PATCH 13/81] Update dump dependencies, fix gcdump (#1611) * Update dependent package versions to capture dumps * Fixed: assembly load exception during gcdump in consuming app Steeltoe employs a trick to download the gcdump assembly without referencing it (because adding a PackageReference to a tool package is not possible). The same needs to be done in consuming apps. --- .../Steeltoe.Common.TestResources.csproj | 1 + .../Build/Steeltoe.Management.Endpoint.props | 30 +++++++++++++++++++ .../Steeltoe.Management.Endpoint.csproj | 4 +++ versions.props | 11 ++++--- 4 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 src/Management/src/Endpoint/Build/Steeltoe.Management.Endpoint.props diff --git a/src/Common/test/TestResources/Steeltoe.Common.TestResources.csproj b/src/Common/test/TestResources/Steeltoe.Common.TestResources.csproj index 0eb15372e0..c531e83b69 100644 --- a/src/Common/test/TestResources/Steeltoe.Common.TestResources.csproj +++ b/src/Common/test/TestResources/Steeltoe.Common.TestResources.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Management/src/Endpoint/Build/Steeltoe.Management.Endpoint.props b/src/Management/src/Endpoint/Build/Steeltoe.Management.Endpoint.props new file mode 100644 index 0000000000..be526711e5 --- /dev/null +++ b/src/Management/src/Endpoint/Build/Steeltoe.Management.Endpoint.props @@ -0,0 +1,30 @@ + + + 9.0.652701 + + + + + + + + + + + $(Pkgdotnet-gcdump)\tools\net8.0\any\dotnet-gcdump.dll + + + + $(Pkgdotnet-gcdump)\tools\net8.0\any\Microsoft.Diagnostics.FastSerialization.dll + + + diff --git a/src/Management/src/Endpoint/Steeltoe.Management.Endpoint.csproj b/src/Management/src/Endpoint/Steeltoe.Management.Endpoint.csproj index 6f7828f732..7fb4343e38 100755 --- a/src/Management/src/Endpoint/Steeltoe.Management.Endpoint.csproj +++ b/src/Management/src/Endpoint/Steeltoe.Management.Endpoint.csproj @@ -9,6 +9,10 @@ + + + + diff --git a/versions.props b/versions.props index a11c13fea6..07e90fa26a 100644 --- a/versions.props +++ b/versions.props @@ -26,7 +26,7 @@ 2.0.0-beta4.24324.3 8.12.* 4.9.* - 17.14.* + 18.0.* 2.0.* 3.1.* @@ -49,7 +49,10 @@ 2.2.* 1.7.14.* - 9.0.621003 + + + 9.0.652701 + 8.0.* - - diff --git a/shared-package.props b/shared-package.props index e3a3c7141d..1e582fed51 100644 --- a/shared-package.props +++ b/shared-package.props @@ -33,6 +33,14 @@ True + + + $(NoWarn);CA1873 + + + $(NoWarn);ASPDEPR004;ASPDEPR008 + + $(MSBuildThisFileDirectory)\Steeltoe.Debug.ruleset diff --git a/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj b/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj index 106df9355a..677a96c46d 100644 --- a/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj +++ b/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Automatically configure Steeltoe packages that are referenced by a project. This is not a meta package, other packages must be added separately. autoconfiguration;automatic;configuration;application;bootstrapping;starter true diff --git a/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj b/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj index 16e099632f..6e06050f02 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj +++ b/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Bootstrap/test/EmptyAutoConfiguration.Test/Steeltoe.Bootstrap.EmptyAutoConfiguration.Test.csproj b/src/Bootstrap/test/EmptyAutoConfiguration.Test/Steeltoe.Bootstrap.EmptyAutoConfiguration.Test.csproj index 19e169e9b3..8b5c6233b2 100644 --- a/src/Bootstrap/test/EmptyAutoConfiguration.Test/Steeltoe.Bootstrap.EmptyAutoConfiguration.Test.csproj +++ b/src/Bootstrap/test/EmptyAutoConfiguration.Test/Steeltoe.Bootstrap.EmptyAutoConfiguration.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Common/src/Certificates/ConfigureCertificateOptions.cs b/src/Common/src/Certificates/ConfigureCertificateOptions.cs index e70acc06e6..3a83066e04 100644 --- a/src/Common/src/Certificates/ConfigureCertificateOptions.cs +++ b/src/Common/src/Certificates/ConfigureCertificateOptions.cs @@ -42,12 +42,14 @@ public void Configure(string? name, CertificateOptions options) string? privateKeyFilePath = _configuration.GetValue(GetConfigurationKey(name, "PrivateKeyFilePath")); +#pragma warning disable SYSLIB0057 // Type or member is obsolete options.Certificate = privateKeyFilePath != null && File.Exists(privateKeyFilePath) ? X509Certificate2.CreateFromPemFile(certificateFilePath, privateKeyFilePath) : new X509Certificate2(certificateFilePath); X509Certificate2[] certificateChain = CertificateRegex.Matches(File.ReadAllText(certificateFilePath)) .Select(x => new X509Certificate2(Encoding.ASCII.GetBytes(x.Value))).ToArray(); +#pragma warning restore SYSLIB0057 // Type or member is obsolete foreach (X509Certificate2 issuer in certificateChain.Skip(1)) { diff --git a/src/Common/src/Certificates/LocalCertificateWriter.cs b/src/Common/src/Certificates/LocalCertificateWriter.cs index 8c0ded40c1..65a7c2c6c7 100644 --- a/src/Common/src/Certificates/LocalCertificateWriter.cs +++ b/src/Common/src/Certificates/LocalCertificateWriter.cs @@ -63,7 +63,9 @@ public void Write(Guid orgId, Guid spaceId) } else { +#pragma warning disable SYSLIB0057 // Type or member is obsolete caCertificate = new X509Certificate2(RootCaPfxPath); +#pragma warning restore SYSLIB0057 // Type or member is obsolete } // Create the intermediate certificate if it doesn't already exist (can be shared by multiple applications) @@ -76,7 +78,9 @@ public void Write(Guid orgId, Guid spaceId) } else { +#pragma warning disable SYSLIB0057 // Type or member is obsolete intermediateCertificate = new X509Certificate2(IntermediatePfxPath); +#pragma warning restore SYSLIB0057 // Type or member is obsolete } var subjectAlternativeNameBuilder = new SubjectAlternativeNameBuilder(); diff --git a/src/Common/src/Certificates/Steeltoe.Common.Certificates.csproj b/src/Common/src/Certificates/Steeltoe.Common.Certificates.csproj index 85bf2ab00d..9ed450c61d 100644 --- a/src/Common/src/Certificates/Steeltoe.Common.Certificates.csproj +++ b/src/Common/src/Certificates/Steeltoe.Common.Certificates.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Steeltoe shared library for working with certificates. security;pem;certificate true diff --git a/src/Common/src/Common/Steeltoe.Common.csproj b/src/Common/src/Common/Steeltoe.Common.csproj index 902abac125..5df742c940 100644 --- a/src/Common/src/Common/Steeltoe.Common.csproj +++ b/src/Common/src/Common/Steeltoe.Common.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Abstractions and code shared by many Steeltoe components. common;utility true diff --git a/src/Common/src/Hosting/Steeltoe.Common.Hosting.csproj b/src/Common/src/Hosting/Steeltoe.Common.Hosting.csproj index aec68f1ecc..329b5c48bb 100644 --- a/src/Common/src/Hosting/Steeltoe.Common.Hosting.csproj +++ b/src/Common/src/Hosting/Steeltoe.Common.Hosting.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Steeltoe library for commonly-used ASP.NET Core hosting-related functions. hosting true diff --git a/src/Common/src/Http/Steeltoe.Common.Http.csproj b/src/Common/src/Http/Steeltoe.Common.Http.csproj index 21c4ac1934..0a6b1fbc0c 100644 --- a/src/Common/src/Http/Steeltoe.Common.Http.csproj +++ b/src/Common/src/Http/Steeltoe.Common.Http.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Shared code related to HTTP, primarily for working with HttpClient. http true diff --git a/src/Common/src/Logging/BootstrapLoggerFactory.cs b/src/Common/src/Logging/BootstrapLoggerFactory.cs index 3e74ffd9a1..fb96382ddb 100644 --- a/src/Common/src/Logging/BootstrapLoggerFactory.cs +++ b/src/Common/src/Logging/BootstrapLoggerFactory.cs @@ -4,6 +4,13 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using LockPrimitive = +#if NET10_0_OR_GREATER + System.Threading.Lock +#else + object +#endif + ; namespace Steeltoe.Common.Logging; @@ -30,7 +37,7 @@ public sealed class BootstrapLoggerFactory : ILoggerFactory loggingBuilder.AddConfiguration(configuration); }; - private readonly object _lock = new(); + private readonly LockPrimitive _lock = new(); private readonly Dictionary _loggersByCategoryName = []; private ILoggerFactory _innerFactory; diff --git a/src/Common/src/Logging/Steeltoe.Common.Logging.csproj b/src/Common/src/Logging/Steeltoe.Common.Logging.csproj index 7525f85ba4..43274bcead 100644 --- a/src/Common/src/Logging/Steeltoe.Common.Logging.csproj +++ b/src/Common/src/Logging/Steeltoe.Common.Logging.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Abstractions and code for bootstrap logging, before the IoC container is built. Bootstrap logging is replaced by the runtime logging (what the rest of your application is using) after the IoC container is built. bootstrap;logging true diff --git a/src/Common/src/Net/Steeltoe.Common.Net.csproj b/src/Common/src/Net/Steeltoe.Common.Net.csproj index 7e03a8fcb4..89b4174c1e 100644 --- a/src/Common/src/Net/Steeltoe.Common.Net.csproj +++ b/src/Common/src/Net/Steeltoe.Common.Net.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Abstractions and code for using Windows network files shares. Windows-file-sharing;SMB;CIFS true diff --git a/src/Common/test/Certificates.Test/Steeltoe.Common.Certificates.Test.csproj b/src/Common/test/Certificates.Test/Steeltoe.Common.Certificates.Test.csproj index b20e9f161e..3397fd9de8 100644 --- a/src/Common/test/Certificates.Test/Steeltoe.Common.Certificates.Test.csproj +++ b/src/Common/test/Certificates.Test/Steeltoe.Common.Certificates.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Common/test/Common.Test/Steeltoe.Common.Test.csproj b/src/Common/test/Common.Test/Steeltoe.Common.Test.csproj index 8180cf05ca..11c155dabe 100644 --- a/src/Common/test/Common.Test/Steeltoe.Common.Test.csproj +++ b/src/Common/test/Common.Test/Steeltoe.Common.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Common/test/Hosting.Test/Steeltoe.Common.Hosting.Test.csproj b/src/Common/test/Hosting.Test/Steeltoe.Common.Hosting.Test.csproj index ff20f8c687..c64bb90b71 100644 --- a/src/Common/test/Hosting.Test/Steeltoe.Common.Hosting.Test.csproj +++ b/src/Common/test/Hosting.Test/Steeltoe.Common.Hosting.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Common/test/Http.Test/Steeltoe.Common.Http.Test.csproj b/src/Common/test/Http.Test/Steeltoe.Common.Http.Test.csproj index eb735987a2..b119974f75 100644 --- a/src/Common/test/Http.Test/Steeltoe.Common.Http.Test.csproj +++ b/src/Common/test/Http.Test/Steeltoe.Common.Http.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Common/test/Logging.Test/Steeltoe.Common.Logging.Test.csproj b/src/Common/test/Logging.Test/Steeltoe.Common.Logging.Test.csproj index c7ba7f6ae8..d6a1a9d34e 100644 --- a/src/Common/test/Logging.Test/Steeltoe.Common.Logging.Test.csproj +++ b/src/Common/test/Logging.Test/Steeltoe.Common.Logging.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Common/test/Net.Test/Steeltoe.Common.Net.Test.csproj b/src/Common/test/Net.Test/Steeltoe.Common.Net.Test.csproj index ede213de15..1b0953f58f 100644 --- a/src/Common/test/Net.Test/Steeltoe.Common.Net.Test.csproj +++ b/src/Common/test/Net.Test/Steeltoe.Common.Net.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Common/test/TestResources/Steeltoe.Common.TestResources.csproj b/src/Common/test/TestResources/Steeltoe.Common.TestResources.csproj index c531e83b69..a258d5c8ba 100644 --- a/src/Common/test/TestResources/Steeltoe.Common.TestResources.csproj +++ b/src/Common/test/TestResources/Steeltoe.Common.TestResources.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 false diff --git a/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs b/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs index b9523cf738..5cdf77cfa6 100644 --- a/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs +++ b/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs @@ -97,10 +97,10 @@ public void Set(string key, string? value) LogSet(GetType().Name, key, value); - if (ConfigurationRoot != null) - { - ConfigurationRoot[key] = value; - } +#pragma warning disable S1121 // Assignments should not be made from within sub-expressions + // Justification: Workaround for Sonar bug https://github.com/SonarSource/sonar-dotnet/issues/9761. + ConfigurationRoot?[key] = value; +#pragma warning restore S1121 // Assignments should not be made from within sub-expressions } public void Dispose() diff --git a/src/Configuration/src/Abstractions/Steeltoe.Configuration.Abstractions.csproj b/src/Configuration/src/Abstractions/Steeltoe.Configuration.Abstractions.csproj index f4aaf7ddd5..9efd29ee97 100644 --- a/src/Configuration/src/Abstractions/Steeltoe.Configuration.Abstractions.csproj +++ b/src/Configuration/src/Abstractions/Steeltoe.Configuration.Abstractions.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Steeltoe.Configuration Abstractions and code shared by Steeltoe Configuration libraries. abstractions;configuration diff --git a/src/Configuration/src/CloudFoundry/Steeltoe.Configuration.CloudFoundry.csproj b/src/Configuration/src/CloudFoundry/Steeltoe.Configuration.CloudFoundry.csproj index 64f150d345..80c40aa143 100644 --- a/src/Configuration/src/CloudFoundry/Steeltoe.Configuration.CloudFoundry.csproj +++ b/src/Configuration/src/CloudFoundry/Steeltoe.Configuration.CloudFoundry.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Configuration provider and IOptions support for reading Cloud Foundry environment variables. configuration;ConfigurationProvider;CloudFoundry;vcap;vcap_application;vcap_services;tanzu true diff --git a/src/Configuration/src/ConfigServer/Steeltoe.Configuration.ConfigServer.csproj b/src/Configuration/src/ConfigServer/Steeltoe.Configuration.ConfigServer.csproj index a1144d9d89..a4297db81d 100644 --- a/src/Configuration/src/ConfigServer/Steeltoe.Configuration.ConfigServer.csproj +++ b/src/Configuration/src/ConfigServer/Steeltoe.Configuration.ConfigServer.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Configuration provider for Spring Cloud Config Server. configuration;ConfigurationProvider;spring-cloud;Spring;Cloud;Config;Server;Spring-Cloud-Config-Server;tanzu true diff --git a/src/Configuration/src/Encryption/Steeltoe.Configuration.Encryption.csproj b/src/Configuration/src/Encryption/Steeltoe.Configuration.Encryption.csproj index ee14db7fcd..99ea4150a5 100644 --- a/src/Configuration/src/Encryption/Steeltoe.Configuration.Encryption.csproj +++ b/src/Configuration/src/Encryption/Steeltoe.Configuration.Encryption.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Configuration provider for decrypting encrypted configuration values. configuration;ConfigurationProvider;cryptography;decryption;Spring;Boot true diff --git a/src/Configuration/src/Kubernetes.ServiceBindings/Steeltoe.Configuration.Kubernetes.ServiceBindings.csproj b/src/Configuration/src/Kubernetes.ServiceBindings/Steeltoe.Configuration.Kubernetes.ServiceBindings.csproj index 502fe1e4d4..7439614f6f 100644 --- a/src/Configuration/src/Kubernetes.ServiceBindings/Steeltoe.Configuration.Kubernetes.ServiceBindings.csproj +++ b/src/Configuration/src/Kubernetes.ServiceBindings/Steeltoe.Configuration.Kubernetes.ServiceBindings.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Configuration provider for reading from Service Binding Specification for Kubernetes. configuration;ConfigurationProvider;kubernetes;k8s;bindings;service-bindings;cloud-native-bindings;cnb;tanzu true diff --git a/src/Configuration/src/Placeholder/Steeltoe.Configuration.Placeholder.csproj b/src/Configuration/src/Placeholder/Steeltoe.Configuration.Placeholder.csproj index 18dbfa28b7..3d5ee8bfee 100644 --- a/src/Configuration/src/Placeholder/Steeltoe.Configuration.Placeholder.csproj +++ b/src/Configuration/src/Placeholder/Steeltoe.Configuration.Placeholder.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Configuration provider for resolving property placeholders in configuration values. configuration;ConfigurationProvider;placeholders;Spring;Boot true diff --git a/src/Configuration/src/RandomValue/Steeltoe.Configuration.RandomValue.csproj b/src/Configuration/src/RandomValue/Steeltoe.Configuration.RandomValue.csproj index 842b7e774d..1873d57c88 100644 --- a/src/Configuration/src/RandomValue/Steeltoe.Configuration.RandomValue.csproj +++ b/src/Configuration/src/RandomValue/Steeltoe.Configuration.RandomValue.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Configuration provider for generating random values. configuration;ConfigurationProvider;random true diff --git a/src/Configuration/src/SpringBoot/SpringBootEnvironmentVariableProvider.cs b/src/Configuration/src/SpringBoot/SpringBootEnvironmentVariableProvider.cs index 825cab291d..ed2685a5f7 100644 --- a/src/Configuration/src/SpringBoot/SpringBootEnvironmentVariableProvider.cs +++ b/src/Configuration/src/SpringBoot/SpringBootEnvironmentVariableProvider.cs @@ -61,11 +61,8 @@ public override void Load() { string? value = Data[key]; - if (value != null) - { - string newKey = key.Contains('.') ? key.Replace('.', ':') : key; - data[newKey] = value; - } + string newKey = key.Contains('.') ? key.Replace('.', ':') : key; + data[newKey] = value; } } diff --git a/src/Configuration/src/SpringBoot/Steeltoe.Configuration.SpringBoot.csproj b/src/Configuration/src/SpringBoot/Steeltoe.Configuration.SpringBoot.csproj index a8d87f95fb..5606d85532 100644 --- a/src/Configuration/src/SpringBoot/Steeltoe.Configuration.SpringBoot.csproj +++ b/src/Configuration/src/SpringBoot/Steeltoe.Configuration.SpringBoot.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Configuration provider for reading Spring Boot-style configuration. configuration;ConfigurationProvider;Spring;Boot true diff --git a/src/Configuration/test/CloudFoundry.Test/CloudfoundryConfigurationProviderTest.cs b/src/Configuration/test/CloudFoundry.Test/CloudfoundryConfigurationProviderTest.cs index 05bbbfd55f..d461d659a7 100644 --- a/src/Configuration/test/CloudFoundry.Test/CloudfoundryConfigurationProviderTest.cs +++ b/src/Configuration/test/CloudFoundry.Test/CloudfoundryConfigurationProviderTest.cs @@ -12,7 +12,13 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Steeltoe.Common.TestResources; -using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; +using IPNetworkAlias = +#if NET10_0_OR_GREATER + System.Net.IPNetwork +#else + Microsoft.AspNetCore.HttpOverrides.IPNetwork +#endif + ; namespace Steeltoe.Configuration.CloudFoundry.Test; @@ -192,8 +198,8 @@ public void Load_VCAP_APPLICATION_Allows_Reload_Without_Throwing_Exception() { const string environment = """ { - "name": "my-app", - "version": "fb8fbcc6-8d58-479e-bcc7-3b4ce5a7f0ca" + "application_version": "fb8fbcc6-8d58-479e-bcc7-3b4ce5a7f0ca", + "space_name": "test-space" } """; @@ -204,7 +210,7 @@ public void Load_VCAP_APPLICATION_Allows_Reload_Without_Throwing_Exception() IConfigurationRoot configurationRoot = configurationBuilder.Build(); - VcapApp? options = null; + CloudFoundryApplicationOptions? options = null; using var tokenSource = new CancellationTokenSource(250.Milliseconds()); @@ -219,12 +225,12 @@ public void Load_VCAP_APPLICATION_Allows_Reload_Without_Throwing_Exception() while (!tokenSource.IsCancellationRequested) { - options = configurationRoot.GetSection("vcap:application").Get(); + options = configurationRoot.GetSection("vcap:application").Get(); } options.Should().NotBeNull(); - options.Name.Should().Be("my-app"); - options.Version.Should().Be("fb8fbcc6-8d58-479e-bcc7-3b4ce5a7f0ca"); + options.ApplicationVersion.Should().Be("fb8fbcc6-8d58-479e-bcc7-3b4ce5a7f0ca"); + options.SpaceName.Should().Be("test-space"); } [Theory] @@ -244,14 +250,22 @@ public async Task ForwardedHeadersOptions_unrestricted_when_running_on_CloudFoun { options.ForwardedHeaders.Should().HaveFlag(ForwardedHeaders.XForwardedFor); options.ForwardedHeaders.Should().HaveFlag(ForwardedHeaders.XForwardedProto); +#if NET10_0_OR_GREATER + options.KnownIPNetworks.Should().BeEmpty(); +#else options.KnownNetworks.Should().BeEmpty(); +#endif options.KnownProxies.Should().BeEmpty(); } else { options.ForwardedHeaders.Should().NotHaveFlag(ForwardedHeaders.XForwardedFor); options.ForwardedHeaders.Should().NotHaveFlag(ForwardedHeaders.XForwardedProto); - options.KnownNetworks.Should().ContainSingle().Which.Should().BeEquivalentTo(IPNetwork.Parse("127.0.0.1/8")); +#if NET10_0_OR_GREATER + options.KnownIPNetworks.Should().ContainSingle().Which.Should().BeEquivalentTo(IPNetworkAlias.Parse("127.0.0.1/8")); +#else + options.KnownNetworks.Should().ContainSingle().Which.Should().BeEquivalentTo(IPNetworkAlias.Parse("127.0.0.1/8")); +#endif options.KnownProxies.Should().ContainSingle().Which.Should().Be(IPAddress.Parse("::1")); } } @@ -467,14 +481,4 @@ public void Loads_VCAP_SERVICES_from_stream() provider.TryGet("p-mysql:1:credentials:uri", out value).Should().BeTrue(); value.Should().Be("mysql://gxXQb2pMbzFsZQW8:lvMkGf6oJQvKSOwn@192.168.0.97:3306/cf_b2d83697_5fa1_4a51_991b_975c9d7e5515?reconnect=true"); } - - private sealed class VcapApp - { -#pragma warning disable S3459 // Unassigned members should be removed -#pragma warning disable S1144 // Unused private types or members should be removed - public string? Name { get; set; } - public string? Version { get; set; } -#pragma warning restore S1144 // Unused private types or members should be removed -#pragma warning restore S3459 // Unassigned members should be removed - } } diff --git a/src/Configuration/test/CloudFoundry.Test/Steeltoe.Configuration.CloudFoundry.Test.csproj b/src/Configuration/test/CloudFoundry.Test/Steeltoe.Configuration.CloudFoundry.Test.csproj index 5e3cb6423f..095019fc55 100644 --- a/src/Configuration/test/CloudFoundry.Test/Steeltoe.Configuration.CloudFoundry.Test.csproj +++ b/src/Configuration/test/CloudFoundry.Test/Steeltoe.Configuration.CloudFoundry.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Configuration/test/ConfigServer.Discovery.Test/Steeltoe.Configuration.ConfigServer.Discovery.Test.csproj b/src/Configuration/test/ConfigServer.Discovery.Test/Steeltoe.Configuration.ConfigServer.Discovery.Test.csproj index 466f805b9b..3d62140336 100644 --- a/src/Configuration/test/ConfigServer.Discovery.Test/Steeltoe.Configuration.ConfigServer.Discovery.Test.csproj +++ b/src/Configuration/test/ConfigServer.Discovery.Test/Steeltoe.Configuration.ConfigServer.Discovery.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Configuration/test/ConfigServer.Integration.Test/Steeltoe.Configuration.ConfigServer.Integration.Test.csproj b/src/Configuration/test/ConfigServer.Integration.Test/Steeltoe.Configuration.ConfigServer.Integration.Test.csproj index 300d587d5c..017de0c119 100644 --- a/src/Configuration/test/ConfigServer.Integration.Test/Steeltoe.Configuration.ConfigServer.Integration.Test.csproj +++ b/src/Configuration/test/ConfigServer.Integration.Test/Steeltoe.Configuration.ConfigServer.Integration.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Configuration/test/ConfigServer.Test/Steeltoe.Configuration.ConfigServer.Test.csproj b/src/Configuration/test/ConfigServer.Test/Steeltoe.Configuration.ConfigServer.Test.csproj index 972ea07add..be6dd615fb 100644 --- a/src/Configuration/test/ConfigServer.Test/Steeltoe.Configuration.ConfigServer.Test.csproj +++ b/src/Configuration/test/ConfigServer.Test/Steeltoe.Configuration.ConfigServer.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Configuration/test/Encryption.Test/Steeltoe.Configuration.Encryption.Test.csproj b/src/Configuration/test/Encryption.Test/Steeltoe.Configuration.Encryption.Test.csproj index 9e238c989c..93d3d367de 100644 --- a/src/Configuration/test/Encryption.Test/Steeltoe.Configuration.Encryption.Test.csproj +++ b/src/Configuration/test/Encryption.Test/Steeltoe.Configuration.Encryption.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Configuration/test/Kubernetes.ServiceBindings.Test/Steeltoe.Configuration.Kubernetes.ServiceBindings.Test.csproj b/src/Configuration/test/Kubernetes.ServiceBindings.Test/Steeltoe.Configuration.Kubernetes.ServiceBindings.Test.csproj index 5bfd14b7d6..ff3cd2f418 100644 --- a/src/Configuration/test/Kubernetes.ServiceBindings.Test/Steeltoe.Configuration.Kubernetes.ServiceBindings.Test.csproj +++ b/src/Configuration/test/Kubernetes.ServiceBindings.Test/Steeltoe.Configuration.Kubernetes.ServiceBindings.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Configuration/test/Placeholder.Test/Steeltoe.Configuration.Placeholder.Test.csproj b/src/Configuration/test/Placeholder.Test/Steeltoe.Configuration.Placeholder.Test.csproj index 77b1d43a97..9226d06016 100644 --- a/src/Configuration/test/Placeholder.Test/Steeltoe.Configuration.Placeholder.Test.csproj +++ b/src/Configuration/test/Placeholder.Test/Steeltoe.Configuration.Placeholder.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Configuration/test/RandomValue.Test/Steeltoe.Configuration.RandomValue.Test.csproj b/src/Configuration/test/RandomValue.Test/Steeltoe.Configuration.RandomValue.Test.csproj index 4b782da24a..102dbdb165 100644 --- a/src/Configuration/test/RandomValue.Test/Steeltoe.Configuration.RandomValue.Test.csproj +++ b/src/Configuration/test/RandomValue.Test/Steeltoe.Configuration.RandomValue.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Configuration/test/SpringBoot.Test/SpringBootEnvironmentVariableProviderTest.cs b/src/Configuration/test/SpringBoot.Test/SpringBootEnvironmentVariableProviderTest.cs index ac78944af9..739a75f992 100644 --- a/src/Configuration/test/SpringBoot.Test/SpringBootEnvironmentVariableProviderTest.cs +++ b/src/Configuration/test/SpringBoot.Test/SpringBootEnvironmentVariableProviderTest.cs @@ -63,14 +63,16 @@ public void TryGet_Tree() value.Should().Be("q"); provider.TryGet("r", out value).Should().BeTrue(); - value.Should().BeEmpty(); + value.Should().BeNull(); provider.TryGet("s:t", out value).Should().BeTrue(); - value.Should().BeEmpty(); + value.Should().BeNull(); - provider.TryGet("u", out _).Should().BeFalse(); + provider.TryGet("u", out value).Should().BeTrue(); + value.Should().BeNull(); - provider.TryGet("v:w", out _).Should().BeFalse(); + provider.TryGet("v:w", out value).Should().BeTrue(); + value.Should().BeNull(); } [Fact] @@ -113,14 +115,16 @@ public void TryGet_Array() value.Should().Be("q"); provider.TryGet("a:b:c:2:r", out value).Should().BeTrue(); - value.Should().BeEmpty(); + value.Should().BeNull(); provider.TryGet("a:b:c:2:s:t", out value).Should().BeTrue(); - value.Should().BeEmpty(); + value.Should().BeNull(); - provider.TryGet("a:b:c:2:u", out _).Should().BeFalse(); + provider.TryGet("a:b:c:2:u", out value).Should().BeTrue(); + value.Should().BeNull(); - provider.TryGet("a:b:c:2:v:w", out _).Should().BeFalse(); + provider.TryGet("a:b:c:2:v:w", out value).Should().BeTrue(); + value.Should().BeNull(); } [Fact] diff --git a/src/Configuration/test/SpringBoot.Test/Steeltoe.Configuration.SpringBoot.Test.csproj b/src/Configuration/test/SpringBoot.Test/Steeltoe.Configuration.SpringBoot.Test.csproj index a9357e6088..0961801249 100644 --- a/src/Configuration/test/SpringBoot.Test/Steeltoe.Configuration.SpringBoot.Test.csproj +++ b/src/Configuration/test/SpringBoot.Test/Steeltoe.Configuration.SpringBoot.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Connectors/src/Connectors/Connector.cs b/src/Connectors/src/Connectors/Connector.cs index 8ee78b99d0..5d885e8883 100644 --- a/src/Connectors/src/Connectors/Connector.cs +++ b/src/Connectors/src/Connectors/Connector.cs @@ -4,6 +4,13 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using LockPrimitive = +#if NET10_0_OR_GREATER + System.Threading.Lock +#else + object +#endif + ; namespace Steeltoe.Connectors; @@ -25,7 +32,7 @@ public sealed class Connector : IDisposable private readonly bool _useSingletonConnection; private readonly IOptionsMonitor _optionsMonitor; - private readonly object _singletonLock = new(); + private readonly LockPrimitive _singletonLock = new(); private ConnectionWithOptionsSnapshot? _singletonSnapshot; private bool _singletonIsDisposed; diff --git a/src/Connectors/src/Connectors/DynamicTypeAccess/ConnectorFactoryShim.cs b/src/Connectors/src/Connectors/DynamicTypeAccess/ConnectorFactoryShim.cs index 4ad4609884..51d468747d 100644 --- a/src/Connectors/src/Connectors/DynamicTypeAccess/ConnectorFactoryShim.cs +++ b/src/Connectors/src/Connectors/DynamicTypeAccess/ConnectorFactoryShim.cs @@ -60,7 +60,7 @@ private static TypeAccessor MakeGenericTypeAccessor(Type connectionType) public ConnectorShim Get(string serviceBindingName) { - object instance = InstanceAccessor.InvokeMethodOverload(nameof(ConnectorFactory.Get), true, [typeof(string)], serviceBindingName)!; + object instance = InstanceAccessor.InvokeMethodOverload(nameof(ConnectorFactory<,>.Get), true, [typeof(string)], serviceBindingName)!; return new ConnectorShim(_connectionType, instance); } diff --git a/src/Connectors/src/Connectors/DynamicTypeAccess/ConnectorShim.cs b/src/Connectors/src/Connectors/DynamicTypeAccess/ConnectorShim.cs index ff13dba205..e3479f0232 100644 --- a/src/Connectors/src/Connectors/DynamicTypeAccess/ConnectorShim.cs +++ b/src/Connectors/src/Connectors/DynamicTypeAccess/ConnectorShim.cs @@ -25,7 +25,7 @@ private static InstanceAccessor CreateAccessor(Type connectionType, object insta public object GetConnection() { - return InstanceAccessor.InvokeMethod(nameof(Connector.GetConnection), true)!; + return InstanceAccessor.InvokeMethod(nameof(Connector<,>.GetConnection), true)!; } public void Dispose() diff --git a/src/Connectors/src/Connectors/Steeltoe.Connectors.csproj b/src/Connectors/src/Connectors/Steeltoe.Connectors.csproj index 3f1411d7b2..5401c5d45a 100644 --- a/src/Connectors/src/Connectors/Steeltoe.Connectors.csproj +++ b/src/Connectors/src/Connectors/Steeltoe.Connectors.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Connectors for using service bindings in your application. connectors;service-bindings;vcap_services;cnb;tanzu true diff --git a/src/Connectors/src/EntityFrameworkCore/Steeltoe.Connectors.EntityFrameworkCore.csproj b/src/Connectors/src/EntityFrameworkCore/Steeltoe.Connectors.EntityFrameworkCore.csproj index 7c621a888d..ccd06f1162 100644 --- a/src/Connectors/src/EntityFrameworkCore/Steeltoe.Connectors.EntityFrameworkCore.csproj +++ b/src/Connectors/src/EntityFrameworkCore/Steeltoe.Connectors.EntityFrameworkCore.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Extensions for using Steeltoe Connectors with Entity Framework Core. connectors;EFCore;EntityFrameworkCore;EF;Entity;Framework;Core;entity-framework-core;services;tanzu true diff --git a/src/Connectors/test/Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs index 321ecc35b5..abf1a00a0a 100644 --- a/src/Connectors/test/Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs @@ -39,7 +39,7 @@ public async Task Not_Connected_Returns_Down_Status() result.Description.Should().Be("MongoDB health check failed"); result.Details.Should().Contain("host", "localhost"); result.Details.Should().Contain("service", "Example"); - result.Details.Should().ContainKey("error").WhoseValue.As().Should().StartWith("TimeoutException: A timeout occurred after 1ms selecting "); + result.Details.Should().ContainKey("error").WhoseValue.As().Should().StartWith("TimeoutException: A timeout occurred after"); } [Fact] diff --git a/src/Connectors/test/Connectors.Test/Steeltoe.Connectors.Test.csproj b/src/Connectors/test/Connectors.Test/Steeltoe.Connectors.Test.csproj index b7414c8f5d..bf2405d3f8 100644 --- a/src/Connectors/test/Connectors.Test/Steeltoe.Connectors.Test.csproj +++ b/src/Connectors/test/Connectors.Test/Steeltoe.Connectors.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs b/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs index 048e5f9085..17a40ea71e 100644 --- a/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs +++ b/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs @@ -16,7 +16,11 @@ namespace Steeltoe.Connectors.EntityFrameworkCore.Test.MySql.Pomelo; public sealed class MySqlDbContextOptionsBuilderExtensionsTest { +#if NET10_0_OR_GREATER + [Fact(Skip = "Temporary workaround: Unstable EF Core 10 package for Pomelo.EntityFrameworkCore.MySql is not available yet.")] +#else [Fact] +#endif public async Task Registers_connection_string_for_default_service_binding() { var appSettings = new Dictionary @@ -42,7 +46,11 @@ public async Task Registers_connection_string_for_default_service_binding() "Server=localhost;User ID=steeltoe;Password=steeltoe;Database=myDb;Allow User Variables=True;Connection Timeout=15;Use Affected Rows=False;Use Compression=False"); } +#if NET10_0_OR_GREATER + [Fact(Skip = "Temporary workaround: Unstable EF Core 10 package for Pomelo.EntityFrameworkCore.MySql is not available yet.")] +#else [Fact] +#endif public async Task Registers_connection_string_for_named_service_binding() { var appSettings = new Dictionary diff --git a/src/Connectors/test/EntityFrameworkCore.Test/SqlServer/SqlServerDbContextOptionsBuilderExtensionsTest.cs b/src/Connectors/test/EntityFrameworkCore.Test/SqlServer/SqlServerDbContextOptionsBuilderExtensionsTest.cs index 66d5001276..fe02b0faaa 100644 --- a/src/Connectors/test/EntityFrameworkCore.Test/SqlServer/SqlServerDbContextOptionsBuilderExtensionsTest.cs +++ b/src/Connectors/test/EntityFrameworkCore.Test/SqlServer/SqlServerDbContextOptionsBuilderExtensionsTest.cs @@ -34,7 +34,7 @@ public async Task Registers_connection_string_for_default_service_binding() await using var dbContext = scope.ServiceProvider.GetRequiredService(); string? connectionString = dbContext.Database.GetConnectionString(); - connectionString.Should().Be("Data Source=localhost;Initial Catalog=myDb;User ID=steeltoe;Password=steeltoe;Max Pool Size=50;Encrypt=false"); + connectionString.Should().StartWith("Data Source=localhost;Initial Catalog=myDb;User ID=steeltoe;Password=steeltoe;Max Pool Size=50;Encrypt="); } [Fact] @@ -56,6 +56,6 @@ public async Task Registers_connection_string_for_named_service_binding() await using var dbContext = scope.ServiceProvider.GetRequiredService(); string? connectionString = dbContext.Database.GetConnectionString(); - connectionString.Should().Be("Data Source=localhost;Initial Catalog=myDb;User ID=steeltoe;Password=steeltoe;Max Pool Size=50;Encrypt=false"); + connectionString.Should().StartWith("Data Source=localhost;Initial Catalog=myDb;User ID=steeltoe;Password=steeltoe;Max Pool Size=50;Encrypt="); } } diff --git a/src/Connectors/test/EntityFrameworkCore.Test/Steeltoe.Connectors.EntityFrameworkCore.Test.csproj b/src/Connectors/test/EntityFrameworkCore.Test/Steeltoe.Connectors.EntityFrameworkCore.Test.csproj index ab56fa7758..9ee50b7ba8 100644 --- a/src/Connectors/test/EntityFrameworkCore.Test/Steeltoe.Connectors.EntityFrameworkCore.Test.csproj +++ b/src/Connectors/test/EntityFrameworkCore.Test/Steeltoe.Connectors.EntityFrameworkCore.Test.csproj @@ -1,16 +1,29 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 + + + + $(NoWarn);NU1608 + + + + - + diff --git a/src/Discovery/src/Configuration/Steeltoe.Discovery.Configuration.csproj b/src/Discovery/src/Configuration/Steeltoe.Discovery.Configuration.csproj index 6cca535d40..db968e9da4 100644 --- a/src/Discovery/src/Configuration/Steeltoe.Discovery.Configuration.csproj +++ b/src/Discovery/src/Configuration/Steeltoe.Discovery.Configuration.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Client for service discovery from application configuration. service-discovery;service-registry;configuration-based true diff --git a/src/Discovery/src/Consul/Steeltoe.Discovery.Consul.csproj b/src/Discovery/src/Consul/Steeltoe.Discovery.Consul.csproj index 965bebd0f1..85ae4ecaff 100644 --- a/src/Discovery/src/Consul/Steeltoe.Discovery.Consul.csproj +++ b/src/Discovery/src/Consul/Steeltoe.Discovery.Consul.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Client for service discovery and registration with Hashicorp Consul. service-discovery;service-registry;Consul;hashicorp true diff --git a/src/Discovery/src/Eureka/EurekaApplicationInfoManager.cs b/src/Discovery/src/Eureka/EurekaApplicationInfoManager.cs index 86a915b8a4..d76ae11a4b 100644 --- a/src/Discovery/src/Eureka/EurekaApplicationInfoManager.cs +++ b/src/Discovery/src/Eureka/EurekaApplicationInfoManager.cs @@ -6,6 +6,13 @@ using Microsoft.Extensions.Options; using Steeltoe.Discovery.Eureka.AppInfo; using Steeltoe.Discovery.Eureka.Configuration; +using LockPrimitive = +#if NET10_0_OR_GREATER + System.Threading.Lock +#else + object +#endif + ; namespace Steeltoe.Discovery.Eureka; @@ -19,7 +26,7 @@ public sealed class EurekaApplicationInfoManager : IDisposable private readonly TimeProvider _timeProvider; private readonly IDisposable? _instanceOptionsChangeToken; private readonly ILogger _logger; - private readonly object _instanceWriteLock = new(); + private readonly LockPrimitive _instanceWriteLock = new(); // Readers must never be blocked, as it may delay the periodic heartbeat. // Updates from user code must be synchronized with configuration changes. diff --git a/src/Discovery/src/Eureka/EurekaServiceUriStateManager.cs b/src/Discovery/src/Eureka/EurekaServiceUriStateManager.cs index fc1c4b7d7a..85b1113930 100644 --- a/src/Discovery/src/Eureka/EurekaServiceUriStateManager.cs +++ b/src/Discovery/src/Eureka/EurekaServiceUriStateManager.cs @@ -6,6 +6,13 @@ using Microsoft.Extensions.Options; using Steeltoe.Discovery.Eureka.Configuration; using Steeltoe.Discovery.Eureka.Transport; +using LockPrimitive = +#if NET10_0_OR_GREATER + System.Threading.Lock +#else + object +#endif + ; namespace Steeltoe.Discovery.Eureka; @@ -17,7 +24,7 @@ public sealed class EurekaServiceUriStateManager private readonly IOptionsMonitor _optionsMonitor; private readonly ILogger _logger; - private readonly object _lockObject = new(); + private readonly LockPrimitive _lockObject = new(); private readonly HashSet _failedServiceUris = []; private Uri? _lastWorkingServiceUri; diff --git a/src/Discovery/src/Eureka/Steeltoe.Discovery.Eureka.csproj b/src/Discovery/src/Eureka/Steeltoe.Discovery.Eureka.csproj index 2afc81bb51..726a65c3a4 100644 --- a/src/Discovery/src/Eureka/Steeltoe.Discovery.Eureka.csproj +++ b/src/Discovery/src/Eureka/Steeltoe.Discovery.Eureka.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Client for service discovery and registration with Netflix Eureka. service-discovery;service-registry;Netflix-Eureka;eureka;tanzu;spring-cloud;Spring;Cloud;Netflix true diff --git a/src/Discovery/src/HttpClients/Steeltoe.Discovery.HttpClients.csproj b/src/Discovery/src/HttpClients/Steeltoe.Discovery.HttpClients.csproj index 2987e9dacd..27d2549b4b 100644 --- a/src/Discovery/src/HttpClients/Steeltoe.Discovery.HttpClients.csproj +++ b/src/Discovery/src/HttpClients/Steeltoe.Discovery.HttpClients.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Enables HTTP clients to load-balance over discovered service instances. service-discovery;service-registry;load-balancing;round-robin;httpclient;httpclientfactory true diff --git a/src/Discovery/test/Configuration.Test/Steeltoe.Discovery.Configuration.Test.csproj b/src/Discovery/test/Configuration.Test/Steeltoe.Discovery.Configuration.Test.csproj index a1d257dfa7..7d56666bcb 100644 --- a/src/Discovery/test/Configuration.Test/Steeltoe.Discovery.Configuration.Test.csproj +++ b/src/Discovery/test/Configuration.Test/Steeltoe.Discovery.Configuration.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Discovery/test/Consul.Test/Steeltoe.Discovery.Consul.Test.csproj b/src/Discovery/test/Consul.Test/Steeltoe.Discovery.Consul.Test.csproj index 0c13f5fb1c..9d04c2d612 100644 --- a/src/Discovery/test/Consul.Test/Steeltoe.Discovery.Consul.Test.csproj +++ b/src/Discovery/test/Consul.Test/Steeltoe.Discovery.Consul.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Discovery/test/Eureka.Test/Steeltoe.Discovery.Eureka.Test.csproj b/src/Discovery/test/Eureka.Test/Steeltoe.Discovery.Eureka.Test.csproj index f79a91c068..28d96ac0da 100644 --- a/src/Discovery/test/Eureka.Test/Steeltoe.Discovery.Eureka.Test.csproj +++ b/src/Discovery/test/Eureka.Test/Steeltoe.Discovery.Eureka.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Discovery/test/HttpClients.Test/Steeltoe.Discovery.HttpClients.Test.csproj b/src/Discovery/test/HttpClients.Test/Steeltoe.Discovery.HttpClients.Test.csproj index 9a42b744de..e9458c6127 100644 --- a/src/Discovery/test/HttpClients.Test/Steeltoe.Discovery.HttpClients.Test.csproj +++ b/src/Discovery/test/HttpClients.Test/Steeltoe.Discovery.HttpClients.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Logging/src/Abstractions/Steeltoe.Logging.Abstractions.csproj b/src/Logging/src/Abstractions/Steeltoe.Logging.Abstractions.csproj index ac8b3aa41c..df179a4e84 100644 --- a/src/Logging/src/Abstractions/Steeltoe.Logging.Abstractions.csproj +++ b/src/Logging/src/Abstractions/Steeltoe.Logging.Abstractions.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Steeltoe.Logging Abstractions for managing minimum logging levels at runtime. abstractions;logging;dynamic-logging;log-management diff --git a/src/Logging/src/DynamicConsole/Steeltoe.Logging.DynamicConsole.csproj b/src/Logging/src/DynamicConsole/Steeltoe.Logging.DynamicConsole.csproj index 1d56db4af0..fb04b352d7 100644 --- a/src/Logging/src/DynamicConsole/Steeltoe.Logging.DynamicConsole.csproj +++ b/src/Logging/src/DynamicConsole/Steeltoe.Logging.DynamicConsole.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Manage minimum logging levels at runtime using ConsoleLogger. logging;dynamic-logging;console;log-management;monitoring true diff --git a/src/Logging/src/DynamicSerilog/DynamicSerilogLoggerProvider.cs b/src/Logging/src/DynamicSerilog/DynamicSerilogLoggerProvider.cs index 899746dd3d..3751d64dbb 100644 --- a/src/Logging/src/DynamicSerilog/DynamicSerilogLoggerProvider.cs +++ b/src/Logging/src/DynamicSerilog/DynamicSerilogLoggerProvider.cs @@ -7,6 +7,13 @@ using Serilog.Core; using Serilog.Events; using Serilog.Extensions.Logging; +using LockPrimitive = +#if NET10_0_OR_GREATER + System.Threading.Lock +#else + object +#endif + ; namespace Steeltoe.Logging.DynamicSerilog; @@ -15,7 +22,7 @@ namespace Steeltoe.Logging.DynamicSerilog; /// public sealed class DynamicSerilogLoggerProvider : DynamicLoggerProvider { - private static readonly object LoggerLock = new(); + private static readonly LockPrimitive LoggerLock = new(); private static Logger? _serilogLogger; private readonly IDisposable? _optionsChangeListener; diff --git a/src/Logging/src/DynamicSerilog/Steeltoe.Logging.DynamicSerilog.csproj b/src/Logging/src/DynamicSerilog/Steeltoe.Logging.DynamicSerilog.csproj index 791445379b..07804c6a7c 100644 --- a/src/Logging/src/DynamicSerilog/Steeltoe.Logging.DynamicSerilog.csproj +++ b/src/Logging/src/DynamicSerilog/Steeltoe.Logging.DynamicSerilog.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Manage minimum logging levels at runtime using Serilog. logging;dynamic-logging;serilog;log-management;monitoring true diff --git a/src/Logging/test/DynamicConsole.Test/DynamicConsoleLoggerProviderTest.cs b/src/Logging/test/DynamicConsole.Test/DynamicConsoleLoggerProviderTest.cs index 5c17d14573..9e237a86a9 100644 --- a/src/Logging/test/DynamicConsole.Test/DynamicConsoleLoggerProviderTest.cs +++ b/src/Logging/test/DynamicConsole.Test/DynamicConsoleLoggerProviderTest.cs @@ -7,6 +7,8 @@ using Microsoft.Extensions.Logging; using Steeltoe.Common.TestResources; +#pragma warning disable CA1873 // Avoid potentially expensive logging + namespace Steeltoe.Logging.DynamicConsole.Test; public sealed class DynamicConsoleLoggerProviderTest : IDisposable @@ -489,7 +491,6 @@ public async Task CanUseJsonFormatterWithScopes() "Category": "Fully.Qualified.Type", "Message": "Processing of { RequestUrl = https://www.example.com, UserAgent = Steeltoe } started.", "State": { - "Message": "Processing of { RequestUrl = https://www.example.com, UserAgent = Steeltoe } started.", "@IncomingRequest": "{ RequestUrl = https://www.example.com, UserAgent = Steeltoe }", "{OriginalFormat}": "Processing of {@IncomingRequest} started." }, diff --git a/src/Logging/test/DynamicConsole.Test/Steeltoe.Logging.DynamicConsole.Test.csproj b/src/Logging/test/DynamicConsole.Test/Steeltoe.Logging.DynamicConsole.Test.csproj index 3a3430b390..ec171ac8c0 100644 --- a/src/Logging/test/DynamicConsole.Test/Steeltoe.Logging.DynamicConsole.Test.csproj +++ b/src/Logging/test/DynamicConsole.Test/Steeltoe.Logging.DynamicConsole.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Logging/test/DynamicSerilog.Test/DynamicSerilogLoggerProviderTest.cs b/src/Logging/test/DynamicSerilog.Test/DynamicSerilogLoggerProviderTest.cs index 637c93dffe..36c985c212 100644 --- a/src/Logging/test/DynamicSerilog.Test/DynamicSerilogLoggerProviderTest.cs +++ b/src/Logging/test/DynamicSerilog.Test/DynamicSerilogLoggerProviderTest.cs @@ -8,6 +8,8 @@ using Serilog.Context; using Steeltoe.Common.TestResources; +#pragma warning disable CA1873 // Avoid potentially expensive logging + namespace Steeltoe.Logging.DynamicSerilog.Test; public sealed class DynamicSerilogLoggerProviderTest : IDisposable diff --git a/src/Logging/test/DynamicSerilog.Test/Steeltoe.Logging.DynamicSerilog.Test.csproj b/src/Logging/test/DynamicSerilog.Test/Steeltoe.Logging.DynamicSerilog.Test.csproj index 9120fbd62a..0620235176 100644 --- a/src/Logging/test/DynamicSerilog.Test/Steeltoe.Logging.DynamicSerilog.Test.csproj +++ b/src/Logging/test/DynamicSerilog.Test/Steeltoe.Logging.DynamicSerilog.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Management/src/Abstractions/Configuration/EndpointOptions.cs b/src/Management/src/Abstractions/Configuration/EndpointOptions.cs index 5a70dd8e64..c64d22c55b 100644 --- a/src/Management/src/Abstractions/Configuration/EndpointOptions.cs +++ b/src/Management/src/Abstractions/Configuration/EndpointOptions.cs @@ -6,8 +6,6 @@ namespace Steeltoe.Management.Configuration; public abstract class EndpointOptions { - private string? _path; - /// /// Gets or sets a value indicating whether this endpoint is enabled. /// @@ -23,8 +21,8 @@ public abstract class EndpointOptions /// public virtual string? Path { - get => _path ?? Id; - set => _path = value; + get => field ?? Id; + set; } /// diff --git a/src/Management/src/Abstractions/Steeltoe.Management.Abstractions.csproj b/src/Management/src/Abstractions/Steeltoe.Management.Abstractions.csproj index 620ad8a35b..8f01a65466 100644 --- a/src/Management/src/Abstractions/Steeltoe.Management.Abstractions.csproj +++ b/src/Management/src/Abstractions/Steeltoe.Management.Abstractions.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Steeltoe.Management Abstractions for application management and monitoring. abstractions;actuator diff --git a/src/Management/src/Endpoint/Actuators/RouteMappings/AspNetEndpointProvider.cs b/src/Management/src/Endpoint/Actuators/RouteMappings/AspNetEndpointProvider.cs index 70f810c77f..39c995b7a9 100644 --- a/src/Management/src/Endpoint/Actuators/RouteMappings/AspNetEndpointProvider.cs +++ b/src/Management/src/Endpoint/Actuators/RouteMappings/AspNetEndpointProvider.cs @@ -167,7 +167,7 @@ private static IEnumerable FromActuatorEndpoint(RouteEndpoint en if (endpointOptions != null) { string displayName = endpoint.DisplayName ?? string.Empty; - MethodInfo handlerMethod = typeof(EndpointMiddleware<,>).GetMethod(nameof(EndpointMiddleware.InvokeAsync))!; + MethodInfo handlerMethod = typeof(EndpointMiddleware<,>).GetMethod(nameof(EndpointMiddleware<,>.InvokeAsync))!; var metadataProvider = endpoint.Metadata.GetMetadata()!; foreach (string httpMethod in endpointOptions.GetSafeAllowedVerbs()) diff --git a/src/Management/src/Endpoint/Actuators/ThreadDump/EventPipeThreadDumper.cs b/src/Management/src/Endpoint/Actuators/ThreadDump/EventPipeThreadDumper.cs index 9acde2baca..5163eef70d 100644 --- a/src/Management/src/Endpoint/Actuators/ThreadDump/EventPipeThreadDumper.cs +++ b/src/Management/src/Endpoint/Actuators/ThreadDump/EventPipeThreadDumper.cs @@ -259,12 +259,9 @@ private IEnumerable ReadStackSource(MutableTraceEventStackSource sta } }); - // Workaround for Sonar bug, which incorrectly flags the code as unreachable. -#pragma warning disable S2589 // Boolean expressions should not be gratuitous logWriter.WriteLine(samplesForThread.Count == 0 ? "[Steeltoe] WARN: No managed samples found in memory dump." : $"[Steeltoe] Start analyzing all {samplesForThread.Count} threads."); -#pragma warning restore S2589 // Boolean expressions should not be gratuitous // For every thread recorded in our trace, use the first stack. foreach ((int threadId, List samples) in samplesForThread) diff --git a/src/Management/src/Endpoint/ConfigurationSchema.json b/src/Management/src/Endpoint/ConfigurationSchema.json index 979706704a..883f3049cd 100644 --- a/src/Management/src/Endpoint/ConfigurationSchema.json +++ b/src/Management/src/Endpoint/ConfigurationSchema.json @@ -624,9 +624,13 @@ "SerializerOptions": { "type": "object", "properties": { + "AllowDuplicateProperties": { + "type": "boolean", + "description": "Gets or sets a value that indicates whether duplicate property names are allowed when deserializing JSON objects." + }, "AllowOutOfOrderMetadataProperties": { "type": "boolean", - "description": "Allows JSON metadata properties to be specified after regular properties in a deserialized JSON object." + "description": "Gets or sets a value that indicates whether JSON metadata properties can be specified after regular properties in a deserialized JSON object." }, "AllowTrailingCommas": { "type": "boolean", @@ -641,7 +645,9 @@ "Never", "Always", "WhenWritingDefault", - "WhenWritingNull" + "WhenWritingNull", + "WhenWriting", + "WhenReading" ], "description": "Gets or sets a value that determines when properties with default values are ignored during serialization or deserialization. The default value is 'System.Text.Json.Serialization.JsonIgnoreCondition.Never'." }, diff --git a/src/Management/src/Endpoint/Steeltoe.Management.Endpoint.csproj b/src/Management/src/Endpoint/Steeltoe.Management.Endpoint.csproj index 7fb4343e38..9ad83198a2 100755 --- a/src/Management/src/Endpoint/Steeltoe.Management.Endpoint.csproj +++ b/src/Management/src/Endpoint/Steeltoe.Management.Endpoint.csproj @@ -1,6 +1,6 @@ - + - net8.0 + net10.0;net8.0 Steeltoe management endpoints, also known as actuators. Includes support for Cloud Foundry integration. actuator;actuators;management;monitoring;Spring;Boot;dbmigrations;health;heap-dump;loggers;route-mappings;thread-dump;tanzu true diff --git a/src/Management/src/Prometheus/Steeltoe.Management.Prometheus.csproj b/src/Management/src/Prometheus/Steeltoe.Management.Prometheus.csproj index 138d7bc362..f37e894adf 100644 --- a/src/Management/src/Prometheus/Steeltoe.Management.Prometheus.csproj +++ b/src/Management/src/Prometheus/Steeltoe.Management.Prometheus.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Adds Prometheus support for Steeltoe management endpoints. actuator;actuators;management;monitoring;metrics;prometheus;tanzu;appmetrics;aspnetcore true diff --git a/src/Management/src/Tasks/Steeltoe.Management.Tasks.csproj b/src/Management/src/Tasks/Steeltoe.Management.Tasks.csproj index c283adb44f..375ca05744 100644 --- a/src/Management/src/Tasks/Steeltoe.Management.Tasks.csproj +++ b/src/Management/src/Tasks/Steeltoe.Management.Tasks.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Extensions for running tasks embedded in your .NET application. Ideal for cf run-task in Cloud Foundry. tasks;management;Spring;Cloud;cf;run-task true diff --git a/src/Management/src/Tracing/Steeltoe.Management.Tracing.csproj b/src/Management/src/Tracing/Steeltoe.Management.Tracing.csproj index c409ab9324..fc918c0843 100644 --- a/src/Management/src/Tracing/Steeltoe.Management.Tracing.csproj +++ b/src/Management/src/Tracing/Steeltoe.Management.Tracing.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Adds trace information to logging output in distributed systems. management;monitoring;distributed;tracing true diff --git a/src/Management/test/Endpoint.Test/Steeltoe.Management.Endpoint.Test.csproj b/src/Management/test/Endpoint.Test/Steeltoe.Management.Endpoint.Test.csproj index afaade1a7f..99a87cce8c 100644 --- a/src/Management/test/Endpoint.Test/Steeltoe.Management.Endpoint.Test.csproj +++ b/src/Management/test/Endpoint.Test/Steeltoe.Management.Endpoint.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Management/test/Prometheus.Test/Steeltoe.Management.Prometheus.Test.csproj b/src/Management/test/Prometheus.Test/Steeltoe.Management.Prometheus.Test.csproj index 95444f1e2c..484afb7186 100644 --- a/src/Management/test/Prometheus.Test/Steeltoe.Management.Prometheus.Test.csproj +++ b/src/Management/test/Prometheus.Test/Steeltoe.Management.Prometheus.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Management/test/RazorPagesTestWebApp/Steeltoe.Management.Endpoint.RazorPagesTestWebApp.csproj b/src/Management/test/RazorPagesTestWebApp/Steeltoe.Management.Endpoint.RazorPagesTestWebApp.csproj index 260873cc43..69cfd6dd8d 100644 --- a/src/Management/test/RazorPagesTestWebApp/Steeltoe.Management.Endpoint.RazorPagesTestWebApp.csproj +++ b/src/Management/test/RazorPagesTestWebApp/Steeltoe.Management.Endpoint.RazorPagesTestWebApp.csproj @@ -1,6 +1,6 @@ - + - net9.0;net8.0 + net10.0;net9.0;net8.0 false diff --git a/src/Management/test/Tasks.Test/Steeltoe.Management.Tasks.Test.csproj b/src/Management/test/Tasks.Test/Steeltoe.Management.Tasks.Test.csproj index dc3b1e985f..c8c75a12ee 100644 --- a/src/Management/test/Tasks.Test/Steeltoe.Management.Tasks.Test.csproj +++ b/src/Management/test/Tasks.Test/Steeltoe.Management.Tasks.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Management/test/Tracing.Test/Steeltoe.Management.Tracing.Test.csproj b/src/Management/test/Tracing.Test/Steeltoe.Management.Tracing.Test.csproj index 5e73996d4e..c28991c23d 100644 --- a/src/Management/test/Tracing.Test/Steeltoe.Management.Tracing.Test.csproj +++ b/src/Management/test/Tracing.Test/Steeltoe.Management.Tracing.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj b/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj index 20eb1cf039..713f082555 100644 --- a/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj +++ b/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Library for using JWT Bearer tokens with UAA-based systems, including Cloud Foundry. CloudFoundry;uaa;security;jwt;bearer;tanzu;aspnetcore true diff --git a/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj b/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj index 427806c6ce..e6a41ad31c 100644 --- a/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj +++ b/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Library for using OpenID Connect with UAA-based systems, including Cloud Foundry. CloudFoundry;uaa;security;sso;openid;oidc;tanzu;aspnetcore true diff --git a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs index 924ed389c7..9540636576 100644 --- a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs +++ b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs @@ -39,8 +39,10 @@ public void PostConfigure(string? name, CertificateAuthenticationOptions options if (!string.IsNullOrEmpty(systemCertPath)) { +#pragma warning disable SYSLIB0057 // Type or member is obsolete X509Certificate2[] systemCertificates = Directory.GetFiles(systemCertPath).Select(certificateFilename => new X509Certificate2(certificateFilename)).ToArray(); +#pragma warning restore SYSLIB0057 // Type or member is obsolete options.CustomTrustStore.AddRange(systemCertificates); } diff --git a/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj b/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj index d132e72847..5f75794484 100644 --- a/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj +++ b/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Provides support for authorization with client certificates. authorization;security;x509;certificate;mutualtls;CloudFoundry;tanzu;aspnetcore true diff --git a/src/Security/src/DataProtection.Redis/Steeltoe.Security.DataProtection.Redis.csproj b/src/Security/src/DataProtection.Redis/Steeltoe.Security.DataProtection.Redis.csproj index 312dff9d2b..6d7a61d9fe 100644 --- a/src/Security/src/DataProtection.Redis/Steeltoe.Security.DataProtection.Redis.csproj +++ b/src/Security/src/DataProtection.Redis/Steeltoe.Security.DataProtection.Redis.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0;net8.0 Support for storing data protection keys in Redis. CloudFoundry;security;dataprotection;redis;aspnetcore true diff --git a/src/Security/test/Authentication.JwtBearer.Test/Steeltoe.Security.Authentication.JwtBearer.Test.csproj b/src/Security/test/Authentication.JwtBearer.Test/Steeltoe.Security.Authentication.JwtBearer.Test.csproj index d738e78d77..abdce38f96 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/Steeltoe.Security.Authentication.JwtBearer.Test.csproj +++ b/src/Security/test/Authentication.JwtBearer.Test/Steeltoe.Security.Authentication.JwtBearer.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj b/src/Security/test/Authentication.OpenIdConnect.Test/Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj index 4273872dc9..7a11ec9e00 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj +++ b/src/Security/test/Authentication.OpenIdConnect.Test/Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Security/test/Authorization.Certificate.Test/Steeltoe.Security.Authorization.Certificate.Test.csproj b/src/Security/test/Authorization.Certificate.Test/Steeltoe.Security.Authorization.Certificate.Test.csproj index c06e4381d5..9235a977d6 100644 --- a/src/Security/test/Authorization.Certificate.Test/Steeltoe.Security.Authorization.Certificate.Test.csproj +++ b/src/Security/test/Authorization.Certificate.Test/Steeltoe.Security.Authorization.Certificate.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Security/test/DataProtection.Redis.Test/Steeltoe.Security.DataProtection.Redis.Test.csproj b/src/Security/test/DataProtection.Redis.Test/Steeltoe.Security.DataProtection.Redis.Test.csproj index 71284f6de5..39e575c9a1 100644 --- a/src/Security/test/DataProtection.Redis.Test/Steeltoe.Security.DataProtection.Redis.Test.csproj +++ b/src/Security/test/DataProtection.Redis.Test/Steeltoe.Security.DataProtection.Redis.Test.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 diff --git a/src/Tools/src/ConfigurationSchemaGenerator/Program.cs b/src/Tools/src/ConfigurationSchemaGenerator/Program.cs index 9bd9ca1f6c..0a11766498 100644 --- a/src/Tools/src/ConfigurationSchemaGenerator/Program.cs +++ b/src/Tools/src/ConfigurationSchemaGenerator/Program.cs @@ -15,4 +15,4 @@ #endif var rootCommand = RootGenerateCommand.GetCommand(); -return await rootCommand.Parse(args).InvokeAsync(CancellationToken.None).ConfigureAwait(false); +return await rootCommand.Parse(args).InvokeAsync().ConfigureAwait(false); diff --git a/src/Tools/src/ConfigurationSchemaGenerator/RootGenerateCommand.cs b/src/Tools/src/ConfigurationSchemaGenerator/RootGenerateCommand.cs index 534db7d6be..e2403188cb 100644 --- a/src/Tools/src/ConfigurationSchemaGenerator/RootGenerateCommand.cs +++ b/src/Tools/src/ConfigurationSchemaGenerator/RootGenerateCommand.cs @@ -6,56 +6,49 @@ #pragma warning disable using System.CommandLine; -using System.CommandLine.Invocation; namespace ConfigurationSchemaGenerator; internal static class RootGenerateCommand { - private static readonly GenerateCommandDefaultHandler s_formatCommandHandler = new(); - - private static readonly CliOption s_inputOption = new("--input") + private static readonly Option s_inputOption = new("--input") { Required = true, Description = "The assembly to generate a ConfigurationSchema.json file for.", }; - private static readonly CliOption s_referencesOption = new("--reference") + private static readonly Option s_referencesOption = new("--reference") { AllowMultipleArgumentsPerToken = true, Required = true, Description = "The assemblies referenced by the input assembly.", }; - private static readonly CliOption s_outputOption = new("--output") + private static readonly Option s_outputOption = new("--output") { Required = true, Description = "The FilePath assembly to generate a ConfigurationSchema.json file for.", }; - public static CliRootCommand GetCommand() + public static RootCommand GetCommand() { - var formatCommand = new CliRootCommand("Generates ConfigurationSchema.json files.") + var formatCommand = new RootCommand("Generates ConfigurationSchema.json files.") { s_inputOption, s_referencesOption, s_outputOption, }; - formatCommand.Action = s_formatCommandHandler; - return formatCommand; - } - private sealed class GenerateCommandDefaultHandler : SynchronousCliAction - { - public override int Invoke(ParseResult parseResult) + formatCommand.SetAction(static parseResult => { - var inputAssembly = parseResult.GetValue(s_inputOption); - var references = parseResult.GetValue(s_referencesOption); - var outputFile = parseResult.GetValue(s_outputOption); + var inputAssembly = parseResult.GetValue(s_inputOption); + var references = parseResult.GetValue(s_referencesOption); + var outputFile = parseResult.GetValue(s_outputOption); ConfigSchemaGenerator.GenerateSchema(inputAssembly, references, outputFile); - return 0; - } + }); + + return formatCommand; } } diff --git a/src/Tools/test/ConfigurationSchemaGenerator.Tests/ConfigurationSchemaGenerator.Tests.csproj b/src/Tools/test/ConfigurationSchemaGenerator.Tests/ConfigurationSchemaGenerator.Tests.csproj index b3b0ca4194..c54c1fa2e4 100644 --- a/src/Tools/test/ConfigurationSchemaGenerator.Tests/ConfigurationSchemaGenerator.Tests.csproj +++ b/src/Tools/test/ConfigurationSchemaGenerator.Tests/ConfigurationSchemaGenerator.Tests.csproj @@ -1,6 +1,6 @@ - net9.0;net8.0 + net10.0;net9.0;net8.0 false enable true diff --git a/versions.props b/versions.props index 07e90fa26a..e4d8fa7106 100644 --- a/versions.props +++ b/versions.props @@ -8,35 +8,45 @@ 9.0.* 6.0.* 7.2.* - 3.51.* + 3.54.* 4.14.* - 6.0.* + 6.1.* 7.0.* - 3.4.* + 3.5.* 4.20.69 - 2.4.* - 9.3.* + 2.5.* + 9.5.* 13.0.* 4.14.* - 7.1.* + 7.2.* 4.0.* 8.4.* - 10.9.0.115408 + 10.15.0.120848 1.2.0-beta.556 - 2.0.0-beta4.24324.3 - 8.12.* + 2.0.* + 8.14.* 4.9.* 18.0.* - 2.0.* + 3.2.* 3.1.* 8.0.* + $(EntityFrameworkCoreTestVersion) - 9.0.*-* + 9.0.* + $(EntityFrameworkCoreTestVersion) + + + + 10.0.*-* + + + 9.0.* + @@ -57,24 +67,24 @@ - 9.0.* + 10.0.* - 8.12.* + 8.14.* 0.2.652701 3.1.26 - 1.12.*-* - 1.12.* + 1.14.*-* + 1.14.* 9.0.* - 6.0.* + 6.1.* 8.0.* @@ -85,10 +95,21 @@ 9.0.* + + + + + 10.0.* + + From ac04a11d772982d2dd00ff3c3ef1c7ff9657c621 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:59:27 +0100 Subject: [PATCH 16/81] Fix gcdump publish and transitive references (#1619) * Fix gcdump publish and transitive references * Remove comment --- .../Build/Steeltoe.Management.Endpoint.props | 30 --------------- .../Steeltoe.Management.Endpoint.csproj | 37 ++++++++++--------- versions.props | 7 +--- 3 files changed, 22 insertions(+), 52 deletions(-) delete mode 100644 src/Management/src/Endpoint/Build/Steeltoe.Management.Endpoint.props diff --git a/src/Management/src/Endpoint/Build/Steeltoe.Management.Endpoint.props b/src/Management/src/Endpoint/Build/Steeltoe.Management.Endpoint.props deleted file mode 100644 index be526711e5..0000000000 --- a/src/Management/src/Endpoint/Build/Steeltoe.Management.Endpoint.props +++ /dev/null @@ -1,30 +0,0 @@ - - - 9.0.652701 - - - - - - - - - - - $(Pkgdotnet-gcdump)\tools\net8.0\any\dotnet-gcdump.dll - - - - $(Pkgdotnet-gcdump)\tools\net8.0\any\Microsoft.Diagnostics.FastSerialization.dll - - - diff --git a/src/Management/src/Endpoint/Steeltoe.Management.Endpoint.csproj b/src/Management/src/Endpoint/Steeltoe.Management.Endpoint.csproj index 9ad83198a2..eba86e4344 100755 --- a/src/Management/src/Endpoint/Steeltoe.Management.Endpoint.csproj +++ b/src/Management/src/Endpoint/Steeltoe.Management.Endpoint.csproj @@ -9,10 +9,6 @@ - - - - @@ -28,19 +24,6 @@ - - - $(Pkgdotnet-gcdump)\tools\net8.0\any\dotnet-gcdump.dll - - - - $(Pkgdotnet-gcdump)\tools\net8.0\any\Microsoft.Diagnostics.FastSerialization.dll - - - @@ -48,4 +31,24 @@ + + + + + $(Pkgdotnet-gcdump)\tools\net8.0\any\dotnet-gcdump.dll + + + + + + + <_DotNetGCDumpNet10 Include="$(MSBuildProjectDirectory)\bin\$(Configuration)\net10.0\dotnet-gcdump.dll" Pack="True" PackagePath="lib\net10.0" /> + <_DotNetGCDumpNet8 Include="$(MSBuildProjectDirectory)\bin\$(Configuration)\net8.0\dotnet-gcdump.dll" Pack="True" PackagePath="lib\net8.0" /> + + + + diff --git a/versions.props b/versions.props index e4d8fa7106..20d9d10375 100644 --- a/versions.props +++ b/versions.props @@ -59,10 +59,7 @@ 2.2.* 1.7.14.* - - - 9.0.652701 - + 9.0.652701 8.0.* + + + + @@ -101,7 +107,7 @@ - + @@ -123,7 +129,7 @@ + Text="ConfigurationSchema.json is out of date for $(MSBuildProjectFile). Run 'dotnet build --no-incremental /p:UpdateConfigurationSchema=true' to update it." /> diff --git a/macos-dump-entitlements.plist b/macos-dump-entitlements.plist new file mode 100644 index 0000000000..41bb93a0c7 --- /dev/null +++ b/macos-dump-entitlements.plist @@ -0,0 +1,16 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + com.apple.security.cs.debugger + + com.apple.security.get-task-allow + + + \ No newline at end of file diff --git a/shared-test.props b/shared-test.props index 16dad857a8..120065dd28 100644 --- a/shared-test.props +++ b/shared-test.props @@ -1,13 +1,8 @@ Exe - $(NoWarn);S2094;S3717;SA1602;CA1062;CA1707;NU5104 - - - - false - true + $(NoWarn);S2094;S3717;SA1602;CA1062;CA1707;NU5104 diff --git a/src/Common/src/Common/Net/DomainNameResolver.cs b/src/Common/src/Common/Net/DomainNameResolver.cs index 0b1da12ddd..30fd0e55bd 100644 --- a/src/Common/src/Common/Net/DomainNameResolver.cs +++ b/src/Common/src/Common/Net/DomainNameResolver.cs @@ -9,6 +9,8 @@ namespace Steeltoe.Common.Net; internal sealed class DomainNameResolver : IDomainNameResolver { + private static readonly bool IsInDiagnosticsMode = Environment.GetEnvironmentVariable("STEELTOE_MACOS_DIAGNOSE_HOSTNAME_LOOKUP") == "true"; + public static DomainNameResolver Instance { get; } = new(); private DomainNameResolver() @@ -42,22 +44,44 @@ private DomainNameResolver() public string? ResolveHostName(bool throwOnError = false) { + // Gather diagnostic information to investigate intermittent failures on macOS. + string? resultFromGetHostName = null; + string? resultFromGetHostEntry = null; + bool? workaroundApplied = null; + try { string hostName = Dns.GetHostName(); + resultFromGetHostName = hostName; if (string.IsNullOrEmpty(hostName)) { // Workaround for failure when running on macOS. // See https://github.com/actions/runner-images/issues/1335 and https://github.com/dotnet/runtime/issues/36849. + hostName = "localhost"; + workaroundApplied = true; } IPHostEntry hostEntry = Dns.GetHostEntry(hostName); - return hostEntry.HostName; + resultFromGetHostEntry = hostEntry.HostName; + + if (IsInDiagnosticsMode && string.IsNullOrEmpty(resultFromGetHostEntry)) + { + throw new InvalidOperationException($"IPHostEntry.HostName returned {GetTextFor(resultFromGetHostEntry)}."); + } + + return resultFromGetHostEntry; } - catch (Exception) + catch (Exception exception) { + if (IsInDiagnosticsMode) + { + throw new InvalidOperationException( + $"Failed to resolve hostname. GetHostName={GetTextFor(resultFromGetHostName)}, GetHostEntry={GetTextFor(resultFromGetHostEntry)}, WorkaroundApplied={workaroundApplied}", + exception); + } + if (throwOnError) { throw; @@ -66,4 +90,14 @@ private DomainNameResolver() return null; } } + + private static string GetTextFor(string? value) + { + if (value == null) + { + return "(null)"; + } + + return value.Length == 0 ? "(empty)" : value; + } } diff --git a/src/Common/test/TestResources/FluentAssertionsExtensions.cs b/src/Common/test/TestResources/FluentAssertionsExtensions.cs index ef96ceb3b3..2b41838878 100644 --- a/src/Common/test/TestResources/FluentAssertionsExtensions.cs +++ b/src/Common/test/TestResources/FluentAssertionsExtensions.cs @@ -52,4 +52,48 @@ private static string ToJsonString(JsonDocument document) writer.Flush(); return Encoding.UTF8.GetString(stream.ToArray()); } + + /// + /// Same as the built-in Be() method, but allows specifying a custom comparer. + /// + /// + /// The source text to assert on. + /// + /// + /// The expected text. + /// + /// + /// An equality comparer to compare values. + /// + [CustomAssertion] + public static void Be(this StringAssertions source, string expected, IEqualityComparer comparer) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(expected); + ArgumentNullException.ThrowIfNull(comparer); + + object subject = source.Subject; + subject.Should().Be(expected, comparer); + } + + /// + /// Same as the built-in Contain() method, but normalizes line endings upfront. + /// + /// + /// The source text to assert on. + /// + /// + /// The expected text. + /// + [CustomAssertion] + public static void ContainLines(this StringAssertions source, string expected) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(expected); + + string sourceText = source.Subject.ReplaceLineEndings(); + string expectedText = expected.ReplaceLineEndings(); + + sourceText.Should().Contain(expectedText); + } } diff --git a/src/Discovery/src/Consul/Registry/ConsulRegistration.cs b/src/Discovery/src/Consul/Registry/ConsulRegistration.cs index c8861fd4d4..81bd573afc 100644 --- a/src/Discovery/src/Consul/Registry/ConsulRegistration.cs +++ b/src/Discovery/src/Consul/Registry/ConsulRegistration.cs @@ -35,7 +35,7 @@ internal sealed class ConsulRegistration : IServiceInstance public bool IsSecure => _optionsMonitor.CurrentValue.EffectiveScheme == "https"; /// - public Uri Uri => new($"{_optionsMonitor.CurrentValue.EffectiveScheme}://{Host}:{Port}"); + public Uri Uri => FormatUri(); /// public Uri? NonSecureUri => IsSecure ? null : Uri; @@ -73,6 +73,20 @@ internal ConsulRegistration(AgentServiceRegistration innerRegistration, IOptions Metadata = innerRegistration.Meta.AsReadOnly(); } + private Uri FormatUri() + { + string scheme = _optionsMonitor.CurrentValue.EffectiveScheme; + + try + { + return new Uri($"{scheme}://{Host}:{Port}"); + } + catch (UriFormatException exception) + { + throw new UriFormatException($"Failed to build URI from components. Scheme={scheme}, Host={Host},Port={Port}.", exception); + } + } + /// /// Creates a registration for the currently running app, to be submitted to the Consul server. /// diff --git a/src/Logging/test/DynamicConsole.Test/DynamicConsoleLoggerProviderTest.cs b/src/Logging/test/DynamicConsole.Test/DynamicConsoleLoggerProviderTest.cs index 9e237a86a9..3e85796499 100644 --- a/src/Logging/test/DynamicConsole.Test/DynamicConsoleLoggerProviderTest.cs +++ b/src/Logging/test/DynamicConsole.Test/DynamicConsoleLoggerProviderTest.cs @@ -504,7 +504,7 @@ public async Task CanUseJsonFormatterWithScopes() ] } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] diff --git a/src/Logging/test/DynamicSerilog.Test/DynamicSerilogLoggerProviderTest.cs b/src/Logging/test/DynamicSerilog.Test/DynamicSerilogLoggerProviderTest.cs index 36c985c212..682a90fdc4 100644 --- a/src/Logging/test/DynamicSerilog.Test/DynamicSerilogLoggerProviderTest.cs +++ b/src/Logging/test/DynamicSerilog.Test/DynamicSerilogLoggerProviderTest.cs @@ -287,7 +287,7 @@ public void CanUseScopes() [INF] Fully.Qualified.Type: {InnerScopeKey="InnerScopeValue", Scope=["OuterScope", "InnerScope=InnerScopeValue"]} TestInfo - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -331,7 +331,7 @@ public void CanUseSerilogEnrichers() [INF] Fully.Qualified.Type: {A=1} Carries property A = 1, again - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -360,7 +360,7 @@ public void CanUseSerilogDestructuring() logOutput.Should().Be(""" [INF] Fully.Qualified.Type: Processing of {"RequestUrl": "https://www.example.com", "UserAgent": "Steeltoe"} started. - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -391,7 +391,7 @@ public void CallsIntoMessageProcessors() [INF] {SourceContext="Test", Scope=["TwoOne"]} Three - """); + """, IgnoreLineEndingsComparer.Instance); } private static IDynamicLoggerProvider CreateLoggerProvider(Action? configure = null) diff --git a/src/Logging/test/DynamicSerilog.Test/HostBuilderTest.cs b/src/Logging/test/DynamicSerilog.Test/HostBuilderTest.cs index fa38276223..151b001ca2 100644 --- a/src/Logging/test/DynamicSerilog.Test/HostBuilderTest.cs +++ b/src/Logging/test/DynamicSerilog.Test/HostBuilderTest.cs @@ -91,12 +91,12 @@ public async Task CanPreserveDefaultConsoleLoggerProvider() logOutput.Should().Contain("SERILOG [INF] TestInfo"); logOutput.Should().Contain("SERILOG [ERR] TestError"); - logOutput.Should().Contain($""" + logOutput.Should().ContainLines($""" info: {typeof(HostBuilderTest)}[0] TestInfo """); - logOutput.Should().Contain($""" + logOutput.Should().ContainLines($""" fail: {typeof(HostBuilderTest)}[0] TestError """); @@ -133,7 +133,7 @@ public async Task CanConfigureSerilogWithoutLevelsConfiguration() [INF] TestInfo [ERR] TestError - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -167,7 +167,7 @@ public async Task CanConfigureSerilogFromConfigurationWithDefaultLevel() logOutput.Should().Be(""" [ERR] TestError - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -201,7 +201,7 @@ public async Task CanConfigureSerilogFromConfigurationWithShortKeyForDefaultLeve logOutput.Should().Be(""" [ERR] TestError - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -236,7 +236,7 @@ public async Task CanConfigureSerilogFromConfigurationWithOnlyOverrides() logOutput.Should().Be(""" [ERR] TestError - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -273,7 +273,7 @@ public async Task CanConfigureSerilogFromCodeWithDefaultLevel() logOutput.Should().Be(""" [ERR] TestError - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -311,7 +311,7 @@ public async Task CanConfigureSerilogFromCodeWithOnlyOverrides() logOutput.Should().Be(""" [ERR] TestError - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] diff --git a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/ApiControllerTest.cs b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/ApiControllerTest.cs index 24036e793d..5d17186e73 100644 --- a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/ApiControllerTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/ApiControllerTest.cs @@ -91,7 +91,7 @@ public async Task Can_get_routes_for_simple_controller() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -208,7 +208,7 @@ public async Task Can_get_routes_for_controller_with_parameters_and_annotations( } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -398,7 +398,7 @@ public async Task Can_get_routes_for_multiple_verbs_in_single_action_method() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -461,7 +461,7 @@ public async Task Can_get_routes_for_any_verb_in_single_action_method() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -536,7 +536,7 @@ public async Task Can_get_routes_using_WebHostBuilder() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -613,6 +613,6 @@ public async Task Can_get_routes_using_HostBuilder() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } } diff --git a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/MinimalApiTest.cs b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/MinimalApiTest.cs index f4c586a17f..774a7248e1 100644 --- a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/MinimalApiTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/MinimalApiTest.cs @@ -83,7 +83,7 @@ public async Task Can_get_routes_for_handler_method() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -145,7 +145,7 @@ public async Task Can_get_routes_for_inline_lambda() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -268,7 +268,7 @@ public async Task Can_get_routes_for_inline_lambda_with_parameters_and_annotatio } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -364,7 +364,7 @@ public async Task Can_get_routes_for_multiple_verbs_in_single_endpoint() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -432,7 +432,7 @@ public async Task Can_get_routes_for_any_verb_in_single_endpoint() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -523,7 +523,7 @@ public async Task Can_get_routes_for_separate_verbs_in_single_endpoint() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -615,7 +615,7 @@ public async Task Can_get_routes_for_groups_using_same_handler_method() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -720,7 +720,7 @@ public async Task Can_get_routes_for_groups_using_inline_lambdas() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -781,7 +781,7 @@ public async Task Can_get_routes_using_WebHostBuilder() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -845,7 +845,7 @@ public async Task Can_get_routes_using_HostBuilder() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } private static string HandlePingRequest() diff --git a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/MvcControllerTest.cs b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/MvcControllerTest.cs index f261f0b125..f61f0f26ff 100644 --- a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/MvcControllerTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/MvcControllerTest.cs @@ -136,7 +136,7 @@ public async Task Can_get_routes_for_simple_controller() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -300,7 +300,7 @@ public async Task Can_get_routes_for_controller_with_parameters_and_annotations( } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -432,7 +432,7 @@ public async Task Can_get_routes_for_multiple_verbs_in_single_action_method() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -511,7 +511,7 @@ public async Task Can_get_routes_for_any_verb_in_single_action_method() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -630,7 +630,7 @@ public async Task Can_get_routes_using_WebHostBuilder() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } [Fact] @@ -750,6 +750,6 @@ public async Task Can_get_routes_using_HostBuilder() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } } diff --git a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/RazorPagesExternalAppTest.cs b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/RazorPagesExternalAppTest.cs index 83bbcdaa92..8b0f91b08a 100644 --- a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/RazorPagesExternalAppTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/AppTypes/RazorPagesExternalAppTest.cs @@ -3,14 +3,13 @@ // See the LICENSE file in the project root for more information. using System.Net; -using Microsoft.AspNetCore.Mvc.Testing; -using Steeltoe.Management.Endpoint.RazorPagesTestWebApp.Pages; +using Steeltoe.Common.TestResources; namespace Steeltoe.Management.Endpoint.Test.Actuators.RouteMappings.AppTypes; -public sealed class RazorPagesExternalAppTest(WebApplicationFactory factory) : IClassFixture> +public sealed class RazorPagesExternalAppTest(RazorPagesWebApplicationFactory factory) : IClassFixture { - private readonly WebApplicationFactory _factory = factory; + private readonly RazorPagesWebApplicationFactory _factory = factory; [Fact] public async Task Can_get_routes_for_razor_pages() @@ -272,6 +271,6 @@ public async Task Can_get_routes_for_razor_pages() } } } - """); + """, IgnoreLineEndingsComparer.Instance); } } diff --git a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RazorPagesWebApplicationFactory.cs b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RazorPagesWebApplicationFactory.cs new file mode 100644 index 0000000000..322e07311b --- /dev/null +++ b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RazorPagesWebApplicationFactory.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Steeltoe.Management.Endpoint.RazorPagesTestWebApp.Pages; +using Steeltoe.Management.Endpoint.Test.Actuators.RouteMappings.AppTypes; + +namespace Steeltoe.Management.Endpoint.Test.Actuators.RouteMappings; + +public sealed class RazorPagesWebApplicationFactory : WebApplicationFactory +{ + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + string? testAssemblyName = typeof(RazorPagesExternalAppTest).Assembly.GetName().Name; + string? appAssemblyName = typeof(IndexModel).Assembly.GetName().Name; + + string absoluteContentRoot = System.Environment.CurrentDirectory; + absoluteContentRoot = absoluteContentRoot.Replace($"/{testAssemblyName}/", $"/{appAssemblyName}/", StringComparison.Ordinal); + absoluteContentRoot = absoluteContentRoot.Replace($@"\{testAssemblyName}\", $@"\{appAssemblyName}\", StringComparison.Ordinal); + + // Workaround for https://github.com/dotnet/aspnetcore/issues/55867. + builder.UseContentRoot(absoluteContentRoot); + + // Workaround for https://github.com/dotnet/aspnetcore/issues/55867#issuecomment-3046941805. + builder.UseEnvironment("Production"); + } +} diff --git a/src/Steeltoe.All.sln b/src/Steeltoe.All.sln index 1e8a438ee2..43d8724a28 100644 --- a/src/Steeltoe.All.sln +++ b/src/Steeltoe.All.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.0.11222.15 d18.0 +VisualStudioVersion = 18.0.11222.15 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common", "Common\src\Common\Steeltoe.Common.csproj", "{61812938-5132-4AB6-B48D-2DF4189B3E37}" EndProject diff --git a/src/testenvironments.json b/src/testenvironments.json index 539ead6b04..ebd64f1380 100644 --- a/src/testenvironments.json +++ b/src/testenvironments.json @@ -1,10 +1,15 @@ { - "version": "1", - "environments": [ - { - "name": "Ubuntu", - "type": "wsl", - "wslDistribution": "Ubuntu" - } - ] + "version": "1", + "environments": [ + { + "name": "Ubuntu", + "type": "wsl", + "wslDistribution": "Ubuntu" + }, + { + "name": "Ubuntu-22.04", + "type": "wsl", + "wslDistribution": "Ubuntu-22.04" + } + ] } From eb22697a86caa4a967985fa1af7c513788954ddd Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:45:59 +0100 Subject: [PATCH 23/81] Configuration Schema Generator: Port changes from https://github.com/dotnet/aspire/pull/13529 (#1634) --- .../ConfigSchemaEmitter.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Tools/src/ConfigurationSchemaGenerator/ConfigSchemaEmitter.cs b/src/Tools/src/ConfigurationSchemaGenerator/ConfigSchemaEmitter.cs index be41d09a7f..aa1b9ece30 100644 --- a/src/Tools/src/ConfigurationSchemaGenerator/ConfigSchemaEmitter.cs +++ b/src/Tools/src/ConfigurationSchemaGenerator/ConfigSchemaEmitter.cs @@ -721,18 +721,13 @@ private static string[] CreateExclusionPaths(List? exclusionPaths) private static void ReplaceNodeWithKeyCasingChange(JsonObject jsonObject, string key, JsonNode value) { - // In System.Text.Json v9, the casing of the new key is not adapted. See https://github.com/dotnet/runtime/issues/108790. - // So instead, remove the existing node and insert a new one with the updated key. - var index = jsonObject.IndexOf(key); - if (index != -1) + if (!jsonObject.TryAdd(key, value, out var index)) { + // Starting from System.Text.Json v9, the casing of the new key is not adapted. See https://github.com/dotnet/runtime/issues/108790. + // So instead, remove the existing node and insert a new one with the updated key. jsonObject.RemoveAt(index); jsonObject.Insert(index, key, value); } - else - { - jsonObject[key] = value; - } } private sealed class SchemaOrderJsonNodeConverter : JsonConverter From 16973f2fd05aeff12153db309e84fcabbd565c7d Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:30:33 +0100 Subject: [PATCH 24/81] Fix scoped ASP.NET health checks, correct AddHealthContributor docs (#1636) * Create a service scope for each ASP.NET health check, correct AddHealthContributor documentation * Update src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Common/HealthChecks/HealthAggregator.cs | 6 ++- .../TestResources/TestHostBuilderFactory.cs | 1 + .../TestWebApplicationBuilderFactory.cs | 2 + .../EndpointServiceCollectionExtensions.cs | 4 +- .../Actuators/Health/HealthAggregationTest.cs | 48 +++++++++++++++++++ .../Steeltoe.Management.Endpoint.Test.csproj | 1 + 6 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/Common/src/Common/HealthChecks/HealthAggregator.cs b/src/Common/src/Common/HealthChecks/HealthAggregator.cs index 92c1e7d88a..972837e11d 100644 --- a/src/Common/src/Common/HealthChecks/HealthAggregator.cs +++ b/src/Common/src/Common/HealthChecks/HealthAggregator.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Concurrent; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Steeltoe.Common.Extensions; using MicrosoftHealthCheckResult = Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult; @@ -108,7 +109,10 @@ private static async Task RunMicrosoftHealthCheckAsyn try { - IHealthCheck check = registration.Factory(serviceProvider); + // Match the behavior of ASP.NET's HealthCheckService, which creates a scope for each check. + await using AsyncServiceScope serviceScope = serviceProvider.CreateAsyncScope(); + + IHealthCheck check = registration.Factory(serviceScope.ServiceProvider); MicrosoftHealthCheckResult result = await check.CheckHealthAsync(context, cancellationToken); healthCheckResult.Status = ToHealthStatus(result.Status); diff --git a/src/Common/test/TestResources/TestHostBuilderFactory.cs b/src/Common/test/TestResources/TestHostBuilderFactory.cs index 5821d0c7ae..a4ddb1c0aa 100644 --- a/src/Common/test/TestResources/TestHostBuilderFactory.cs +++ b/src/Common/test/TestResources/TestHostBuilderFactory.cs @@ -48,6 +48,7 @@ private static void ConfigureBuilder(HostBuilder builder, bool configureWebHost, { builder.ConfigureWebHostDefaults(webHostBuilder => { + webHostBuilder.UseDefaultServiceProvider(ConfigureServiceProvider); webHostBuilder.Configure(EmptyAction); if (useTestServer) diff --git a/src/Common/test/TestResources/TestWebApplicationBuilderFactory.cs b/src/Common/test/TestResources/TestWebApplicationBuilderFactory.cs index 5583766952..f86e176836 100644 --- a/src/Common/test/TestResources/TestWebApplicationBuilderFactory.cs +++ b/src/Common/test/TestResources/TestWebApplicationBuilderFactory.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; namespace Steeltoe.Common.TestResources; @@ -89,6 +90,7 @@ public static WebApplicationBuilder CreateDefault(bool useTestServer) private static void ConfigureBuilder(WebApplicationBuilder builder, bool useTestServer, bool deactivateDiagnostics) { + builder.Host.UseDefaultServiceProvider(ConfigureServiceProvider); builder.WebHost.UseDefaultServiceProvider(ConfigureServiceProvider); if (useTestServer) diff --git a/src/Management/src/Endpoint/Actuators/Health/EndpointServiceCollectionExtensions.cs b/src/Management/src/Endpoint/Actuators/Health/EndpointServiceCollectionExtensions.cs index 1226c49cc4..02019af59d 100644 --- a/src/Management/src/Endpoint/Actuators/Health/EndpointServiceCollectionExtensions.cs +++ b/src/Management/src/Endpoint/Actuators/Health/EndpointServiceCollectionExtensions.cs @@ -76,7 +76,7 @@ private static void RegisterDefaultHealthContributors(IServiceCollection service } /// - /// Adds the specified to the D/I container as a scoped service. + /// Adds the specified to the D/I container as a singleton service. /// /// /// The type of health contributor to add. @@ -98,7 +98,7 @@ public static IServiceCollection AddHealthContributor(this IServiceCollection } /// - /// Adds the specified to the D/I container as a scoped service. + /// Adds the specified to the D/I container as a singleton service. /// /// /// The to add services to. diff --git a/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs b/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs index 5717db1942..216764db61 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs @@ -7,6 +7,7 @@ using FluentAssertions.Extensions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.TestHost; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -516,6 +517,50 @@ public async Task Converts_AspNet_health_check_results() """); } + [Fact] + public async Task Can_use_scoped_AspNet_health_check() + { + WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); + builder.Configuration.AddInMemoryCollection(AppSettings); + builder.Services.AddDbContext(options => options.UseInMemoryDatabase(Guid.NewGuid().ToString())); + builder.Services.AddHealthChecks().AddDbContextCheck(); + builder.Services.AddHealthActuator(); + await using WebApplication host = builder.Build(); + + // ReSharper disable once AccessToDisposedClosure + Action action = () => host.Services.GetRequiredService(); + action.Should().ThrowExactly(); + + host.MapHealthChecks("/health"); + await host.StartAsync(TestContext.Current.CancellationToken); + using HttpClient httpClient = host.GetTestClient(); + + HttpResponseMessage actuatorResponse = await httpClient.GetAsync(new Uri("http://localhost/actuator/health"), TestContext.Current.CancellationToken); + + actuatorResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + string actuatorResponseBody = await actuatorResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); + + actuatorResponseBody.Should().BeJson(""" + { + "status": "UP", + "components": { + "TestDbContext": { + "status": "UP" + } + } + } + """); + + HttpResponseMessage aspNetResponse = await httpClient.GetAsync(new Uri("http://localhost/health"), TestContext.Current.CancellationToken); + + aspNetResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + string aspNetResponseBody = await aspNetResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); + + aspNetResponseBody.Should().Be("Healthy"); + } + private sealed class AspNetHealthyCheck : IHealthCheck { public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) @@ -562,4 +607,7 @@ public Task CheckHealthAsync(HealthCheckContext cont throw new InvalidOperationException("test-exception"); } } + + private sealed class TestDbContext(DbContextOptions options) + : DbContext(options); } diff --git a/src/Management/test/Endpoint.Test/Steeltoe.Management.Endpoint.Test.csproj b/src/Management/test/Endpoint.Test/Steeltoe.Management.Endpoint.Test.csproj index 99a87cce8c..97607ba184 100644 --- a/src/Management/test/Endpoint.Test/Steeltoe.Management.Endpoint.Test.csproj +++ b/src/Management/test/Endpoint.Test/Steeltoe.Management.Endpoint.Test.csproj @@ -16,6 +16,7 @@ + From 3f7be3e67e966cc11ed8890928af4105bf713000 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:53:03 +0100 Subject: [PATCH 25/81] Update R# code style settings (#1637) * Clear non-indented HTML tags (used to be: html,body,thead,tbody,tfoot), hide hint to add return statement before local function * Reformat RazorPagesTestWebApp --- .../Pages/Shared/_Layout.cshtml | 84 +++++++++---------- src/Steeltoe.All.sln.DotSettings | 2 + 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/Management/test/RazorPagesTestWebApp/Pages/Shared/_Layout.cshtml b/src/Management/test/RazorPagesTestWebApp/Pages/Shared/_Layout.cshtml index ee77ef62e6..4748f9de45 100644 --- a/src/Management/test/RazorPagesTestWebApp/Pages/Shared/_Layout.cshtml +++ b/src/Management/test/RazorPagesTestWebApp/Pages/Shared/_Layout.cshtml @@ -1,50 +1,50 @@ - - - - @ViewData["Title"] - Steeltoe.Management.Endpoint.RazorPagesTestWebApp - - - - -
- -
-
-
- @RenderBody() -
-
-
-
- © 2025 - Steeltoe.Management.Endpoint.RazorPagesTestWebApp - Privacy -
-
+
+
+ © 2025 - Steeltoe.Management.Endpoint.RazorPagesTestWebApp - Privacy +
+
- - - + + + -@await RenderSectionAsync("Scripts", false) - + @await RenderSectionAsync("Scripts", false) + diff --git a/src/Steeltoe.All.sln.DotSettings b/src/Steeltoe.All.sln.DotSettings index 4980f6d113..a27b0a5ed6 100644 --- a/src/Steeltoe.All.sln.DotSettings +++ b/src/Steeltoe.All.sln.DotSettings @@ -83,6 +83,7 @@ WARNING WARNING DO_NOT_SHOW + DO_NOT_SHOW WARNING WARNING WARNING @@ -164,6 +165,7 @@ WRAP_IF_LONG True True + 2 True 2 From ca23480da673dc785a29237fb7f85ad4da7617ac Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Fri, 9 Jan 2026 17:37:20 +0100 Subject: [PATCH 26/81] Rename job 'analyze' to 'build' in workflow (#1638) --- .github/workflows/Steeltoe.All.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Steeltoe.All.yml b/.github/workflows/Steeltoe.All.yml index bb88a9492c..13c8e49972 100644 --- a/.github/workflows/Steeltoe.All.yml +++ b/.github/workflows/Steeltoe.All.yml @@ -26,7 +26,7 @@ env: --settings coverlet.runsettings --blame-crash --blame-hang-timeout 1m jobs: - analyze: + build: name: Build and Test timeout-minutes: 60 strategy: From 91e2d4e1546f877ab3802608ad7b800116b1fbcd Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:15:18 +0100 Subject: [PATCH 27/81] Make skipped tests appear on test summary instead of hiding them entirely (#1641) --- .../test/TestResources/FactSkippedOnPlatformAttribute.cs | 6 +++++- .../test/TestResources/TheorySkippedOnPlatformAttribute.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Common/test/TestResources/FactSkippedOnPlatformAttribute.cs b/src/Common/test/TestResources/FactSkippedOnPlatformAttribute.cs index 0e3efaba45..b96559158c 100644 --- a/src/Common/test/TestResources/FactSkippedOnPlatformAttribute.cs +++ b/src/Common/test/TestResources/FactSkippedOnPlatformAttribute.cs @@ -23,7 +23,11 @@ public FactSkippedOnPlatformAttribute(params string[] platformNames) { foreach (OSPlatform platform in platformNames.Select(OSPlatform.Create)) { - Assert.SkipWhen(RuntimeInformation.IsOSPlatform(platform), $"Skipping test on platform {platform}."); + if (RuntimeInformation.IsOSPlatform(platform)) + { + Skip = $"Skipping test on incompatible platform {platform}."; + break; + } } } } diff --git a/src/Common/test/TestResources/TheorySkippedOnPlatformAttribute.cs b/src/Common/test/TestResources/TheorySkippedOnPlatformAttribute.cs index c09160b5c4..3e834c1cc1 100644 --- a/src/Common/test/TestResources/TheorySkippedOnPlatformAttribute.cs +++ b/src/Common/test/TestResources/TheorySkippedOnPlatformAttribute.cs @@ -23,7 +23,11 @@ public TheorySkippedOnPlatformAttribute(params string[] platformNames) { foreach (OSPlatform platform in platformNames.Select(OSPlatform.Create)) { - Assert.SkipWhen(RuntimeInformation.IsOSPlatform(platform), $"Skipping test on platform {platform}."); + if (RuntimeInformation.IsOSPlatform(platform)) + { + Skip = $"Skipping test on incompatible platform {platform}."; + break; + } } } } From ece58a05fc497403bd961480572d9a4ccd35ca43 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:38:49 +0100 Subject: [PATCH 28/81] Update .gitignore (#1643) --- .gitignore | 69 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 1cb051b459..c056419fa6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ *.user *.userosscache *.sln.docstates +*.env # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs @@ -21,17 +22,37 @@ mono_crash.* [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ -x64/ -x86/ + +[Dd]ebug/x64/ +[Dd]ebugPublic/x64/ +[Rr]elease/x64/ +[Rr]eleases/x64/ +bin/x64/ +obj/x64/ + +[Dd]ebug/x86/ +[Dd]ebugPublic/x86/ +[Rr]elease/x86/ +[Rr]eleases/x86/ +bin/x86/ +obj/x86/ + [Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ +[Aa][Rr][Mm]64[Ee][Cc]/ bld/ -[Bb]in/ [Oo]bj/ +[Oo]ut/ [Ll]og/ [Ll]ogs/ +# Build results on 'Bin' directories +**/[Bb]in/* +# Uncomment if you have tasks that rely on *.refresh files to move binaries +# (https://github.com/github/gitignore/pull/3736) +#!**/[Bb]in/*.refresh + # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot @@ -43,12 +64,16 @@ Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* +*.trx # NUnit *.VisualState.xml TestResult.xml nunit-*.xml +# Approval Tests result files +*.received.* + # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ @@ -75,6 +100,7 @@ StyleCopReport.xml *.ilk *.meta *.obj +*.idb *.iobj *.pch *.pdb @@ -82,6 +108,8 @@ StyleCopReport.xml *.pgc *.pgd *.rsp +# but not Directory.Build.rsp, as it configures directory-level build defaults +!Directory.Build.rsp *.sbr *.tlb *.tli @@ -153,6 +181,7 @@ coverage*.info # NCrunch _NCrunch_* +.NCrunch_* .*crunch*.local.xml nCrunchTemp_* @@ -294,9 +323,6 @@ node_modules/ # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - # Visual Studio 6 workspace and project file (working project files containing files to include in project) *.dsw *.dsp @@ -314,22 +340,22 @@ node_modules/ _Pvt_Extensions # Paket dependency manager -.paket/paket.exe +**/.paket/paket.exe paket-files/ # FAKE - F# Make -.fake/ +**/.fake/ # CodeRush personal settings -.cr/personal +**/.cr/personal # Python Tools for Visual Studio (PTVS) -__pycache__/ +**/__pycache__/ *.pyc # Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config +#tools/** +#!tools/packages.config # Tabs Studio *.tss @@ -351,15 +377,19 @@ ASALocalRun/ # MSBuild Binary and Structured Log *.binlog +MSBuild_Logs/ + +# AWS SAM Build and Temporary Artifacts folder +.aws-sam # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder -.mfractor/ +**/.mfractor/ # Local History for Visual Studio -.localhistory/ +**/.localhistory/ # Visual Studio History (VSHistory) files .vshistory/ @@ -371,7 +401,7 @@ healthchecksdb MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder -.ionide/ +**/.ionide/ # Fody - auto-generated XML schema FodyWeavers.xsd @@ -382,12 +412,14 @@ FodyWeavers.xsd !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json -*.code-workspace -/.vscode/settings.json +!.vscode/*.code-snippets # Local History for Visual Studio Code .history/ +# Built Visual Studio Code Extensions +*.vsix + # Windows Installer files from build outputs *.cab *.msi @@ -395,9 +427,6 @@ FodyWeavers.xsd *.msm *.msp -# JetBrains Rider -*.sln.iml - ############################################# ### Additions specific to this repository ### ############################################# From c3aec3dcc676390bf3ea97f2f46bb531e9759dc3 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:39:09 -0600 Subject: [PATCH 29/81] Add .NET runtime information to /info actuator endpoint (#1640) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Tim Hess Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Contributors/RuntimeInfoContributor.cs | 28 +++++++++++++++++++ .../EndpointServiceCollectionExtensions.cs | 1 + .../Actuators/Info/InfoActuatorTest.cs | 17 +++++++++++ src/Steeltoe.All.sln.DotSettings | 1 + 4 files changed, 47 insertions(+) create mode 100644 src/Management/src/Endpoint/Actuators/Info/Contributors/RuntimeInfoContributor.cs diff --git a/src/Management/src/Endpoint/Actuators/Info/Contributors/RuntimeInfoContributor.cs b/src/Management/src/Endpoint/Actuators/Info/Contributors/RuntimeInfoContributor.cs new file mode 100644 index 0000000000..dc940d8a8b --- /dev/null +++ b/src/Management/src/Endpoint/Actuators/Info/Contributors/RuntimeInfoContributor.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; + +namespace Steeltoe.Management.Endpoint.Actuators.Info.Contributors; + +internal sealed class RuntimeInfoContributor : IInfoContributor +{ + public Task ContributeAsync(InfoBuilder builder, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(builder); + + builder.WithInfo("runtime", new Dictionary + { + ["runtimeName"] = RuntimeInformation.FrameworkDescription, + ["runtimeVersion"] = System.Environment.Version.ToString(), + ["runtimeIdentifier"] = RuntimeInformation.RuntimeIdentifier, + ["processArchitecture"] = RuntimeInformation.ProcessArchitecture.ToString(), + ["osArchitecture"] = RuntimeInformation.OSArchitecture.ToString(), + ["osDescription"] = RuntimeInformation.OSDescription, + ["osVersion"] = System.Environment.OSVersion.ToString() + }); + + return Task.CompletedTask; + } +} diff --git a/src/Management/src/Endpoint/Actuators/Info/EndpointServiceCollectionExtensions.cs b/src/Management/src/Endpoint/Actuators/Info/EndpointServiceCollectionExtensions.cs index dd699748b6..93bca54b2e 100644 --- a/src/Management/src/Endpoint/Actuators/Info/EndpointServiceCollectionExtensions.cs +++ b/src/Management/src/Endpoint/Actuators/Info/EndpointServiceCollectionExtensions.cs @@ -54,6 +54,7 @@ private static void RegisterDefaultInfoContributors(IServiceCollection services) services.TryAddEnumerable(ServiceDescriptor.Singleton()); services.TryAddEnumerable(ServiceDescriptor.Singleton()); services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); } /// diff --git a/src/Management/test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs index 56350212d3..7d7756a273 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs @@ -4,6 +4,7 @@ using System.Net; using System.Reflection; +using System.Runtime.InteropServices; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; @@ -33,6 +34,13 @@ public sealed class InfoActuatorTest private static readonly Assembly SteeltoeAssembly = typeof(IInfoContributor).Assembly; private static readonly string SteeltoeFileVersion = SteeltoeAssembly.GetCustomAttribute()!.Version; private static readonly string SteeltoeProductVersion = SteeltoeAssembly.GetCustomAttribute()!.InformationalVersion; + private static readonly string RuntimeName = RuntimeInformation.FrameworkDescription; + private static readonly string RuntimeVersion = System.Environment.Version.ToString(); + private static readonly string RuntimeIdentifier = RuntimeInformation.RuntimeIdentifier; + private static readonly string ProcessArchitecture = RuntimeInformation.ProcessArchitecture.ToString(); + private static readonly string OSArchitecture = RuntimeInformation.OSArchitecture.ToString(); + private static readonly string OSDescription = RuntimeInformation.OSDescription; + private static readonly string OSVersion = System.Environment.OSVersion.ToString(); [Fact] public async Task Registers_dependent_services() @@ -184,6 +192,15 @@ public async Task Endpoint_returns_expected_data(HostBuilderType hostBuilderType }, "build": { "version": "{{AppAssemblyVersion}}" + }, + "runtime": { + "runtimeName": "{{RuntimeName}}", + "runtimeVersion": "{{RuntimeVersion}}", + "runtimeIdentifier": "{{RuntimeIdentifier}}", + "processArchitecture": "{{ProcessArchitecture}}", + "osArchitecture": "{{OSArchitecture}}", + "osDescription": "{{OSDescription}}", + "osVersion": "{{OSVersion}}" } } """); diff --git a/src/Steeltoe.All.sln.DotSettings b/src/Steeltoe.All.sln.DotSettings index a27b0a5ed6..112d0c37d2 100644 --- a/src/Steeltoe.All.sln.DotSettings +++ b/src/Steeltoe.All.sln.DotSettings @@ -604,6 +604,7 @@ See the LICENSE file in the project root for more information. IO IP MQ + OS OSX UAA False From 80b359a9fe30ba7c55a75f2e2e3ac5c8ba15a974 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:06:58 +0100 Subject: [PATCH 30/81] Enable skipping ASP.NET health checks at actuator endpoint (#1644) * Enable skipping AspNet health checks at actuator endpoint * Rename tag to ExcludeFromHealthActuator --- .../Common/HealthChecks/HealthAggregator.cs | 8 ++-- .../Actuators/Health/HealthAggregationTest.cs | 47 +++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/Common/src/Common/HealthChecks/HealthAggregator.cs b/src/Common/src/Common/HealthChecks/HealthAggregator.cs index 972837e11d..076928ed0b 100644 --- a/src/Common/src/Common/HealthChecks/HealthAggregator.cs +++ b/src/Common/src/Common/HealthChecks/HealthAggregator.cs @@ -68,7 +68,10 @@ await Parallel.ForEachAsync(contributors, cancellationToken, async (contributor, private static async Task> AggregateMicrosoftHealthChecksAsync(ICollection contributors, ICollection healthCheckRegistrations, IServiceProvider serviceProvider, CancellationToken cancellationToken) { - if (healthCheckRegistrations.Count == 0) + HealthCheckRegistration[] activeHealthCheckRegistrations = + healthCheckRegistrations.Where(registration => !registration.Tags.Contains("ExcludeFromHealthActuator")).ToArray(); + + if (activeHealthCheckRegistrations.Length == 0) { return new Dictionary(); } @@ -76,8 +79,7 @@ private static async Task> Aggreg var healthChecks = new ConcurrentDictionary(); var keys = new ConcurrentBag(contributors.Select(contributor => contributor.Id)); - // run all HealthCheckRegistration checks in parallel - await Parallel.ForEachAsync(healthCheckRegistrations, cancellationToken, async (registration, _) => + await Parallel.ForEachAsync(activeHealthCheckRegistrations, cancellationToken, async (registration, _) => { string contributorName = GetKey(keys, registration.Name); SteeltoeHealthCheckResult healthCheckResult; diff --git a/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs b/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs index 216764db61..ad76b9eaad 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs @@ -517,6 +517,53 @@ public async Task Converts_AspNet_health_check_results() """); } + [Fact] + public async Task Can_skip_AspNet_health_check() + { + WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); + builder.Configuration.AddInMemoryCollection(AppSettings); + builder.Services.AddHealthActuator(); + + IHealthChecksBuilder checksBuilder = builder.Services.AddHealthChecks(); + checksBuilder.AddCheck("aspnet-unhealthy-check", tags: ["ExcludeFromHealthActuator"]); + checksBuilder.AddCheck("aspnet-healthy-check"); + + await using WebApplication host = builder.Build(); + + host.MapHealthChecks("/health"); + await host.StartAsync(TestContext.Current.CancellationToken); + using HttpClient httpClient = host.GetTestClient(); + + HttpResponseMessage actuatorResponse = await httpClient.GetAsync(new Uri("http://localhost/actuator/health"), TestContext.Current.CancellationToken); + + actuatorResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + string actuatorResponseBody = await actuatorResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); + + actuatorResponseBody.Should().BeJson(""" + { + "status": "UP", + "components": { + "aspnet-healthy-check": { + "status": "UP", + "description": "healthy-description", + "details": { + "healthy-data-key": "healthy-data-value" + } + } + } + } + """); + + HttpResponseMessage aspNetResponse = await httpClient.GetAsync(new Uri("http://localhost/health"), TestContext.Current.CancellationToken); + + aspNetResponse.StatusCode.Should().Be(HttpStatusCode.ServiceUnavailable); + + string aspNetResponseBody = await aspNetResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); + + aspNetResponseBody.Should().Be("Unhealthy"); + } + [Fact] public async Task Can_use_scoped_AspNet_health_check() { From 98c6e55789d364ab4c98e7cbae8d2b747bacd981 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Wed, 28 Jan 2026 14:55:45 -0600 Subject: [PATCH 31/81] build Steeltoe 4.1 --- shared-package.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-package.props b/shared-package.props index 1e582fed51..c4cb7f60aa 100644 --- a/shared-package.props +++ b/shared-package.props @@ -12,7 +12,7 @@ - 4.0.1 + 4.1.0 pre Broadcom PackageIcon.png From eea288020d772745f17936141194a09945b75843 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:15:39 +0100 Subject: [PATCH 32/81] Package updates (#1646) --- .../CompositeConfigurationProvider.cs | 4 ---- .../src/Connectors/PublicAPI.Unshipped.txt | 2 ++ ...oe.Connectors.EntityFrameworkCore.Test.csproj | 4 +--- .../src/Abstractions/PublicAPI.Unshipped.txt | 1 + versions.props | 16 ++++++++-------- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs b/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs index 5cdf77cfa6..0215563c03 100644 --- a/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs +++ b/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs @@ -96,11 +96,7 @@ public void Set(string key, string? value) ArgumentNullException.ThrowIfNull(key); LogSet(GetType().Name, key, value); - -#pragma warning disable S1121 // Assignments should not be made from within sub-expressions - // Justification: Workaround for Sonar bug https://github.com/SonarSource/sonar-dotnet/issues/9761. ConfigurationRoot?[key] = value; -#pragma warning restore S1121 // Assignments should not be made from within sub-expressions } public void Dispose() diff --git a/src/Connectors/src/Connectors/PublicAPI.Unshipped.txt b/src/Connectors/src/Connectors/PublicAPI.Unshipped.txt index 7dc5c58110..4a50d3c1f5 100644 --- a/src/Connectors/src/Connectors/PublicAPI.Unshipped.txt +++ b/src/Connectors/src/Connectors/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +*REMOVED*virtual Steeltoe.Connectors.ConnectorCreateConnection.Invoke(System.IServiceProvider! serviceProvider, string! serviceBindingName) -> object! +*REMOVED*virtual Steeltoe.Connectors.ConnectorCreateHealthContributor.Invoke(System.IServiceProvider! serviceProvider, string! serviceBindingName) -> Steeltoe.Common.HealthChecks.IHealthContributor! diff --git a/src/Connectors/test/EntityFrameworkCore.Test/Steeltoe.Connectors.EntityFrameworkCore.Test.csproj b/src/Connectors/test/EntityFrameworkCore.Test/Steeltoe.Connectors.EntityFrameworkCore.Test.csproj index 9ee50b7ba8..626d5effb2 100644 --- a/src/Connectors/test/EntityFrameworkCore.Test/Steeltoe.Connectors.EntityFrameworkCore.Test.csproj +++ b/src/Connectors/test/EntityFrameworkCore.Test/Steeltoe.Connectors.EntityFrameworkCore.Test.csproj @@ -8,9 +8,7 @@ $(NoWarn);NU1608 diff --git a/src/Logging/src/Abstractions/PublicAPI.Unshipped.txt b/src/Logging/src/Abstractions/PublicAPI.Unshipped.txt index 7dc5c58110..fd374ba889 100644 --- a/src/Logging/src/Abstractions/PublicAPI.Unshipped.txt +++ b/src/Logging/src/Abstractions/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +*REMOVED*virtual Steeltoe.Logging.LoggerFilter.Invoke(Microsoft.Extensions.Logging.LogLevel level) -> bool diff --git a/versions.props b/versions.props index 20d9d10375..7281d21072 100644 --- a/versions.props +++ b/versions.props @@ -8,23 +8,23 @@ 9.0.* 6.0.* 7.2.* - 3.54.* - 4.14.* + 3.57.* + 5.0.* 6.1.* 7.0.* - 3.5.* + 3.6.* 4.20.69 2.5.* - 9.5.* + 9.6.* 13.0.* - 4.14.* + 3.3.* 7.2.* 4.0.* 8.4.* - 10.15.0.120848 + 10.18.0.131500 1.2.0-beta.556 2.0.* - 8.14.* + 8.15.* 4.9.* 18.0.* 3.2.* @@ -42,7 +42,7 @@ - 10.0.*-* + 10.0.* 9.0.* From 186ff028c4504ad84f24ac884c69736efe6bc108 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Fri, 30 Jan 2026 16:42:18 +0100 Subject: [PATCH 33/81] Run public-api-mark-shipped.ps1 (#1647) --- src/Common/src/Common/PublicAPI.Shipped.txt | 3 +++ src/Common/src/Common/PublicAPI.Unshipped.txt | 3 --- src/Connectors/src/Connectors/PublicAPI.Shipped.txt | 2 -- src/Connectors/src/Connectors/PublicAPI.Unshipped.txt | 2 -- src/Discovery/src/Configuration/PublicAPI.Shipped.txt | 3 +++ src/Discovery/src/Configuration/PublicAPI.Unshipped.txt | 3 --- src/Discovery/src/Eureka/PublicAPI.Shipped.txt | 1 + src/Discovery/src/Eureka/PublicAPI.Unshipped.txt | 1 - src/Logging/src/Abstractions/PublicAPI.Shipped.txt | 1 - src/Logging/src/Abstractions/PublicAPI.Unshipped.txt | 1 - 10 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/Common/src/Common/PublicAPI.Shipped.txt b/src/Common/src/Common/PublicAPI.Shipped.txt index abdf747864..1222919d08 100644 --- a/src/Common/src/Common/PublicAPI.Shipped.txt +++ b/src/Common/src/Common/PublicAPI.Shipped.txt @@ -27,9 +27,12 @@ Steeltoe.Common.Discovery.IDiscoveryClient.GetServiceIdsAsync(System.Threading.C Steeltoe.Common.Discovery.IDiscoveryClient.ShutdownAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Steeltoe.Common.Discovery.IServiceInstance Steeltoe.Common.Discovery.IServiceInstance.Host.get -> string! +Steeltoe.Common.Discovery.IServiceInstance.InstanceId.get -> string! Steeltoe.Common.Discovery.IServiceInstance.IsSecure.get -> bool Steeltoe.Common.Discovery.IServiceInstance.Metadata.get -> System.Collections.Generic.IReadOnlyDictionary! +Steeltoe.Common.Discovery.IServiceInstance.NonSecureUri.get -> System.Uri? Steeltoe.Common.Discovery.IServiceInstance.Port.get -> int +Steeltoe.Common.Discovery.IServiceInstance.SecureUri.get -> System.Uri? Steeltoe.Common.Discovery.IServiceInstance.ServiceId.get -> string! Steeltoe.Common.Discovery.IServiceInstance.Uri.get -> System.Uri! Steeltoe.Common.Extensions.ServiceCollectionExtensions diff --git a/src/Common/src/Common/PublicAPI.Unshipped.txt b/src/Common/src/Common/PublicAPI.Unshipped.txt index 4d8338db45..7dc5c58110 100644 --- a/src/Common/src/Common/PublicAPI.Unshipped.txt +++ b/src/Common/src/Common/PublicAPI.Unshipped.txt @@ -1,4 +1 @@ #nullable enable -Steeltoe.Common.Discovery.IServiceInstance.InstanceId.get -> string! -Steeltoe.Common.Discovery.IServiceInstance.NonSecureUri.get -> System.Uri? -Steeltoe.Common.Discovery.IServiceInstance.SecureUri.get -> System.Uri? diff --git a/src/Connectors/src/Connectors/PublicAPI.Shipped.txt b/src/Connectors/src/Connectors/PublicAPI.Shipped.txt index 85c0cffef8..8403385a15 100644 --- a/src/Connectors/src/Connectors/PublicAPI.Shipped.txt +++ b/src/Connectors/src/Connectors/PublicAPI.Shipped.txt @@ -110,5 +110,3 @@ Steeltoe.Connectors.SqlServer.SqlServerHostApplicationBuilderExtensions Steeltoe.Connectors.SqlServer.SqlServerOptions Steeltoe.Connectors.SqlServer.SqlServerOptions.SqlServerOptions() -> void Steeltoe.Connectors.SqlServer.SqlServerServiceCollectionExtensions -virtual Steeltoe.Connectors.ConnectorCreateConnection.Invoke(System.IServiceProvider! serviceProvider, string! serviceBindingName) -> object! -virtual Steeltoe.Connectors.ConnectorCreateHealthContributor.Invoke(System.IServiceProvider! serviceProvider, string! serviceBindingName) -> Steeltoe.Common.HealthChecks.IHealthContributor! diff --git a/src/Connectors/src/Connectors/PublicAPI.Unshipped.txt b/src/Connectors/src/Connectors/PublicAPI.Unshipped.txt index 4a50d3c1f5..7dc5c58110 100644 --- a/src/Connectors/src/Connectors/PublicAPI.Unshipped.txt +++ b/src/Connectors/src/Connectors/PublicAPI.Unshipped.txt @@ -1,3 +1 @@ #nullable enable -*REMOVED*virtual Steeltoe.Connectors.ConnectorCreateConnection.Invoke(System.IServiceProvider! serviceProvider, string! serviceBindingName) -> object! -*REMOVED*virtual Steeltoe.Connectors.ConnectorCreateHealthContributor.Invoke(System.IServiceProvider! serviceProvider, string! serviceBindingName) -> Steeltoe.Common.HealthChecks.IHealthContributor! diff --git a/src/Discovery/src/Configuration/PublicAPI.Shipped.txt b/src/Discovery/src/Configuration/PublicAPI.Shipped.txt index 103d547fe5..77bcde4a11 100644 --- a/src/Discovery/src/Configuration/PublicAPI.Shipped.txt +++ b/src/Discovery/src/Configuration/PublicAPI.Shipped.txt @@ -15,11 +15,14 @@ Steeltoe.Discovery.Configuration.ConfigurationServiceInstance Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.ConfigurationServiceInstance() -> void Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.Host.get -> string? Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.Host.set -> void +Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.InstanceId.get -> string! Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.IsSecure.get -> bool Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.IsSecure.set -> void Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.Metadata.get -> System.Collections.Generic.IDictionary! +Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.NonSecureUri.get -> System.Uri? Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.Port.get -> int Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.Port.set -> void +Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.SecureUri.get -> System.Uri? Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.ServiceId.get -> string? Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.ServiceId.set -> void Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.Uri.get -> System.Uri! diff --git a/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt b/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt index d7fc263414..7dc5c58110 100644 --- a/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt +++ b/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt @@ -1,4 +1 @@ #nullable enable -Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.InstanceId.get -> string! -Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.NonSecureUri.get -> System.Uri? -Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.SecureUri.get -> System.Uri? diff --git a/src/Discovery/src/Eureka/PublicAPI.Shipped.txt b/src/Discovery/src/Eureka/PublicAPI.Shipped.txt index 3741b7ac79..d200217507 100644 --- a/src/Discovery/src/Eureka/PublicAPI.Shipped.txt +++ b/src/Discovery/src/Eureka/PublicAPI.Shipped.txt @@ -75,6 +75,7 @@ Steeltoe.Discovery.Eureka.AppInfo.InstanceInfo.Status.get -> Steeltoe.Discovery. Steeltoe.Discovery.Eureka.AppInfo.InstanceInfo.Status.init -> void Steeltoe.Discovery.Eureka.AppInfo.InstanceInfo.StatusPageUrl.get -> string? Steeltoe.Discovery.Eureka.AppInfo.InstanceInfo.StatusPageUrl.init -> void +Steeltoe.Discovery.Eureka.AppInfo.InstanceInfo.ToServiceInstance() -> Steeltoe.Common.Discovery.IServiceInstance! Steeltoe.Discovery.Eureka.AppInfo.InstanceInfo.VipAddress.get -> string? Steeltoe.Discovery.Eureka.AppInfo.InstanceInfo.VipAddress.init -> void Steeltoe.Discovery.Eureka.AppInfo.InstanceStatus diff --git a/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt b/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt index d808bd9917..7dc5c58110 100644 --- a/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt +++ b/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt @@ -1,2 +1 @@ #nullable enable -Steeltoe.Discovery.Eureka.AppInfo.InstanceInfo.ToServiceInstance() -> Steeltoe.Common.Discovery.IServiceInstance! diff --git a/src/Logging/src/Abstractions/PublicAPI.Shipped.txt b/src/Logging/src/Abstractions/PublicAPI.Shipped.txt index 265845c2f6..df3b837d54 100644 --- a/src/Logging/src/Abstractions/PublicAPI.Shipped.txt +++ b/src/Logging/src/Abstractions/PublicAPI.Shipped.txt @@ -34,5 +34,4 @@ Steeltoe.Logging.MessageProcessingLogger.MessageProcessingLogger(Microsoft.Exten Steeltoe.Logging.MessageProcessingLogger.MessageProcessors.get -> System.Collections.Generic.IReadOnlyCollection! virtual Steeltoe.Logging.DynamicLoggerProvider.CreateMessageProcessingLogger(string! categoryName) -> Steeltoe.Logging.MessageProcessingLogger! virtual Steeltoe.Logging.DynamicLoggerProvider.Dispose(bool disposing) -> void -virtual Steeltoe.Logging.LoggerFilter.Invoke(Microsoft.Extensions.Logging.LogLevel level) -> bool virtual Steeltoe.Logging.MessageProcessingLogger.Log(Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, TState state, System.Exception? exception, System.Func! formatter) -> void diff --git a/src/Logging/src/Abstractions/PublicAPI.Unshipped.txt b/src/Logging/src/Abstractions/PublicAPI.Unshipped.txt index fd374ba889..7dc5c58110 100644 --- a/src/Logging/src/Abstractions/PublicAPI.Unshipped.txt +++ b/src/Logging/src/Abstractions/PublicAPI.Unshipped.txt @@ -1,2 +1 @@ #nullable enable -*REMOVED*virtual Steeltoe.Logging.LoggerFilter.Invoke(Microsoft.Extensions.Logging.LogLevel level) -> bool From da33d9ca600e624710970c751c1425531da0bc6e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:48:39 +0100 Subject: [PATCH 34/81] Bump Steeltoe version from 4.1.0 to 4.1.1. (#1648) > [!TIP] > Close and reopen this pull request to run status checks. Co-authored-by: github-actions[bot] --- shared-package.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-package.props b/shared-package.props index c4cb7f60aa..5457c82531 100644 --- a/shared-package.props +++ b/shared-package.props @@ -12,7 +12,7 @@ - 4.1.0 + 4.1.1 pre Broadcom PackageIcon.png From fbe5079b6dab4146ae18442d9c76eded4795556a Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:16:13 +0100 Subject: [PATCH 35/81] Fix awkward sentences in comments (#1650) * Fix awkward sentences in comments * Update src/Discovery/src/Eureka/DynamicPortAssignmentHostedService.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/Common/src/Logging/BootstrapLoggerFactory.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Common/src/Logging/BootstrapLoggerFactory.cs | 4 ++-- src/Common/test/TestResources/CapturingLoggerProvider.cs | 2 +- src/Common/test/TestResources/EnvironmentVariableScope.cs | 2 +- src/Common/test/TestResources/TestFailureTracer.cs | 2 +- .../ConfigServerConfigurationBuilderExtensions.cs | 2 +- .../src/Eureka/DynamicPortAssignmentHostedService.cs | 2 +- src/Logging/src/Abstractions/DynamicLoggerProvider.cs | 2 +- .../src/DynamicSerilog/SerilogLoggingBuilderExtensions.cs | 4 ++-- src/Logging/src/DynamicSerilog/SerilogOptions.cs | 4 ++-- .../Endpoint/Configuration/IEndpointOptionsMonitorProvider.cs | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Common/src/Logging/BootstrapLoggerFactory.cs b/src/Common/src/Logging/BootstrapLoggerFactory.cs index fb96382ddb..2ad4ad0ad8 100644 --- a/src/Common/src/Logging/BootstrapLoggerFactory.cs +++ b/src/Common/src/Logging/BootstrapLoggerFactory.cs @@ -58,7 +58,7 @@ public static BootstrapLoggerFactory CreateConsole() /// Creates a new that writes to the console. /// /// - /// Enables to further configure the bootstrap logger from code. + /// Enables further configuring the bootstrap logger from code. /// public static BootstrapLoggerFactory CreateConsole(Action configure) { @@ -75,7 +75,7 @@ public static BootstrapLoggerFactory CreateConsole(Action confi /// Creates a new empty . ///
/// - /// Enables to fully configure the bootstrap logger from code. + /// Enables fully configuring the bootstrap logger from code. /// public static BootstrapLoggerFactory CreateEmpty(Action configure) { diff --git a/src/Common/test/TestResources/CapturingLoggerProvider.cs b/src/Common/test/TestResources/CapturingLoggerProvider.cs index 38825a5a19..5fa4262460 100644 --- a/src/Common/test/TestResources/CapturingLoggerProvider.cs +++ b/src/Common/test/TestResources/CapturingLoggerProvider.cs @@ -8,7 +8,7 @@ namespace Steeltoe.Common.TestResources; /// -/// Enables to capture log messages in tests. +/// Enables capturing log messages in tests. /// public sealed class CapturingLoggerProvider : ILoggerProvider { diff --git a/src/Common/test/TestResources/EnvironmentVariableScope.cs b/src/Common/test/TestResources/EnvironmentVariableScope.cs index 8925d7436a..6d93899ff1 100644 --- a/src/Common/test/TestResources/EnvironmentVariableScope.cs +++ b/src/Common/test/TestResources/EnvironmentVariableScope.cs @@ -5,7 +5,7 @@ namespace Steeltoe.Common.TestResources; /// -/// Enables to temporarily set/change an environment variable from a test. The original value is restored when disposed. +/// Enables temporarily setting/changing an environment variable from a test. The original value is restored when disposed. /// public sealed class EnvironmentVariableScope : IDisposable { diff --git a/src/Common/test/TestResources/TestFailureTracer.cs b/src/Common/test/TestResources/TestFailureTracer.cs index c686a6e4ff..ef763e7d38 100644 --- a/src/Common/test/TestResources/TestFailureTracer.cs +++ b/src/Common/test/TestResources/TestFailureTracer.cs @@ -8,7 +8,7 @@ namespace Steeltoe.Common.TestResources; /// -/// Enables to capture log output in failing tests. Call or use to hook up. When an assertion fails, +/// Enables capturing log output in failing tests. Call or use to hook up. When an assertion fails, /// the log output is included in the exception message. /// public sealed class TestFailureTracer : IDisposable diff --git a/src/Configuration/src/ConfigServer/ConfigServerConfigurationBuilderExtensions.cs b/src/Configuration/src/ConfigServer/ConfigServerConfigurationBuilderExtensions.cs index f0869e72c3..187eff2ff8 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerConfigurationBuilderExtensions.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerConfigurationBuilderExtensions.cs @@ -55,7 +55,7 @@ public static IConfigurationBuilder AddConfigServer(this IConfigurationBuilder b /// The to add configuration to. /// /// - /// Enables to configure Config Server from code. + /// Enables configuring Config Server from code. /// /// /// Used for internal logging. Pass to disable logging. diff --git a/src/Discovery/src/Eureka/DynamicPortAssignmentHostedService.cs b/src/Discovery/src/Eureka/DynamicPortAssignmentHostedService.cs index fa036c5b2f..e58cefb6bf 100644 --- a/src/Discovery/src/Eureka/DynamicPortAssignmentHostedService.cs +++ b/src/Discovery/src/Eureka/DynamicPortAssignmentHostedService.cs @@ -95,7 +95,7 @@ public Task StoppedAsync(CancellationToken cancellationToken) } /// - /// Enables to trigger change in . + /// Enables triggering change in . /// internal sealed class EurekaInstanceOptionsChangeTokenSource : IOptionsChangeTokenSource { diff --git a/src/Logging/src/Abstractions/DynamicLoggerProvider.cs b/src/Logging/src/Abstractions/DynamicLoggerProvider.cs index 5bab89d486..41118d530a 100644 --- a/src/Logging/src/Abstractions/DynamicLoggerProvider.cs +++ b/src/Logging/src/Abstractions/DynamicLoggerProvider.cs @@ -8,7 +8,7 @@ namespace Steeltoe.Logging; /// -/// Provides access to categories and their minimum log levels and enables to decorate log messages. +/// Provides access to categories and their minimum log levels and enables decorating log messages. /// public abstract class DynamicLoggerProvider : IDynamicLoggerProvider { diff --git a/src/Logging/src/DynamicSerilog/SerilogLoggingBuilderExtensions.cs b/src/Logging/src/DynamicSerilog/SerilogLoggingBuilderExtensions.cs index 40bb2ce13c..c01090d0ca 100644 --- a/src/Logging/src/DynamicSerilog/SerilogLoggingBuilderExtensions.cs +++ b/src/Logging/src/DynamicSerilog/SerilogLoggingBuilderExtensions.cs @@ -35,7 +35,7 @@ public static ILoggingBuilder AddDynamicSerilog(this ILoggingBuilder builder) /// The to configure. /// /// - /// Enables to configure Serilog from code instead of configuration. + /// Enables configuring Serilog from code instead of configuration. /// /// /// The incoming so that additional calls can be chained. @@ -69,7 +69,7 @@ public static ILoggingBuilder AddDynamicSerilog(this ILoggingBuilder builder, bo /// The to configure. /// /// - /// Enables to configure Serilog from code instead of configuration. + /// Enables configuring Serilog from code instead of configuration. /// /// /// When set to true, does not remove existing logger providers. diff --git a/src/Logging/src/DynamicSerilog/SerilogOptions.cs b/src/Logging/src/DynamicSerilog/SerilogOptions.cs index 949e4ff716..871e3a9a63 100644 --- a/src/Logging/src/DynamicSerilog/SerilogOptions.cs +++ b/src/Logging/src/DynamicSerilog/SerilogOptions.cs @@ -23,7 +23,7 @@ public sealed class SerilogOptions public MinimumLevel? MinimumLevel { get; set; } /// - /// Enables to bind from configuration. + /// Enables binding from configuration. /// /// /// The configuration to bind from. @@ -56,7 +56,7 @@ internal void SetSerilogOptions(IConfiguration configuration) } /// - /// Enables to configure programmatically. + /// Enables configuring programmatically. /// /// /// The instance to obtain settings from. diff --git a/src/Management/src/Endpoint/Configuration/IEndpointOptionsMonitorProvider.cs b/src/Management/src/Endpoint/Configuration/IEndpointOptionsMonitorProvider.cs index bce0a71eb8..9f80e3b911 100644 --- a/src/Management/src/Endpoint/Configuration/IEndpointOptionsMonitorProvider.cs +++ b/src/Management/src/Endpoint/Configuration/IEndpointOptionsMonitorProvider.cs @@ -8,7 +8,7 @@ namespace Steeltoe.Management.Endpoint.Configuration; /// -/// Enables to register multiple typed providers to enumerate all s for the various +/// Enables registering multiple typed providers to enumerate all s for the various /// types. /// internal interface IEndpointOptionsMonitorProvider From ccd55f6d789292d189a94ad0bceb88c96d13f4c8 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:12:43 +0100 Subject: [PATCH 36/81] Use source generators for logging and regular expressions (#1652) * Use logger source generator, cleanup log messages * Use regex source generator, optimize flags * Update to latest Sonar analyzer to match analysis in Sonar Cloud * Update to latest R# version * Revert switch to extension method syntax --- .config/dotnet-tools.json | 2 +- .../src/AutoConfiguration/BootstrapScanner.cs | 112 ++++++++++--- .../ConfigureCertificateOptions.cs | 11 +- .../ConfigurationKeyConverter.cs | 8 +- src/Common/src/Common/Net/InetUtils.cs | 55 +++++-- .../CompositeConfigurationProvider.cs | 11 +- .../CloudFoundryPostProcessor.cs | 18 +- .../EurekaCloudFoundryPostProcessor.cs | 8 +- .../IdentityCloudFoundryPostProcessor.cs | 8 +- .../ConfigServerConfigurationProvider.cs | 148 +++++++++++++---- .../ConfigServerDiscoveryService.cs | 22 ++- .../ConfigServerHealthContributor.cs | 32 +++- .../DecryptionConfigurationProvider.cs | 5 +- .../DecryptionConfigurationSource.cs | 2 +- .../PlaceholderConfigurationSource.cs | 2 +- .../Placeholder/PropertyPlaceHolderHelper.cs | 7 +- .../src/RandomValue/RandomValueProvider.cs | 7 +- .../TestConfigurationSource.cs | 2 +- .../CosmosDb/CosmosDbHealthContributor.cs | 17 +- .../MongoDb/MongoDbHealthContributor.cs | 17 +- .../RabbitMQ/RabbitMQHealthContributor.cs | 17 +- .../Redis/RedisHealthContributor.cs | 17 +- .../RelationalDatabaseHealthContributor.cs | 17 +- .../MigrateDbContextTask.cs | 19 ++- src/Discovery/src/Consul/PeriodicHeartbeat.cs | 31 +++- .../Consul/Registry/ConsulServiceRegistrar.cs | 37 ++++- .../Consul/Registry/ConsulServiceRegistry.cs | 28 +++- src/Discovery/src/Consul/TtlScheduler.cs | 13 +- .../src/Eureka/AppInfo/ApplicationInfo.cs | 7 +- .../Configuration/EurekaInstanceOptions.cs | 34 +++- .../Eureka/EurekaApplicationInfoManager.cs | 37 ++++- src/Discovery/src/Eureka/EurekaClient.cs | 55 +++++-- .../src/Eureka/EurekaDiscoveryClient.cs | 155 ++++++++++++++---- .../Eureka/EurekaServiceUriStateManager.cs | 7 +- .../LoadBalancers/RandomLoadBalancer.cs | 17 +- .../LoadBalancers/RoundRobinLoadBalancer.cs | 17 +- .../LoadBalancers/ServiceInstancesResolver.cs | 17 +- .../Eureka.Test/Transport/EurekaClientTest.cs | 53 ++++-- src/Management/src/Endpoint/ActuatorMapper.cs | 19 ++- .../CloudFoundrySecurityMiddleware.cs | 19 ++- .../CloudFoundry/PermissionsProvider.cs | 23 ++- .../DbMigrationsEndpointHandler.cs | 12 +- .../Environment/EnvironmentEndpointHandler.cs | 7 +- .../Availability/ApplicationAvailability.cs | 7 +- .../AvailabilityStateHealthContributor.cs | 18 +- .../Actuators/Health/HealthEndpointHandler.cs | 16 +- .../Health/HealthEndpointMiddleware.cs | 12 +- .../HeapDump/HeapDumpEndpointHandler.cs | 7 +- .../HeapDump/HeapDumpEndpointMiddleware.cs | 7 +- .../Endpoint/Actuators/HeapDump/HeapDumper.cs | 17 +- .../Diagnostics/DiagnosticObserver.cs | 17 +- .../Diagnostics/DiagnosticsManager.cs | 7 +- .../Diagnostics/DiagnosticsService.cs | 12 +- .../HttpExchangesEndpointHandler.cs | 7 +- .../HttpExchanges/HttpExchangesRepository.cs | 7 +- .../Actuators/Hypermedia/HypermediaService.cs | 12 +- .../Info/Contributors/GitInfoContributor.cs | 7 +- .../Actuators/Info/InfoEndpointHandler.cs | 8 +- .../Loggers/LoggersEndpointHandler.cs | 12 +- .../Loggers/LoggersEndpointMiddleware.cs | 17 +- .../Refresh/RefreshEndpointHandler.cs | 7 +- .../RouteMappings/AspNetEndpointProvider.cs | 7 +- .../ThreadDump/EventPipeThreadDumper.cs | 36 ++-- .../ThreadDump/ThreadDumpEndpointHandler.cs | 7 +- .../ManagementPortMiddleware.cs | 26 +-- .../Endpoint/Middleware/EndpointMiddleware.cs | 28 +++- .../SpringBootAdminPeriodicRefresh.cs | 40 +++-- .../SpringBootAdminRefreshRunner.cs | 32 +++- .../src/Prometheus/PrometheusExtensions.cs | 21 ++- .../src/Tasks/TaskHostExtensions.cs | 7 +- ...dFoundrySecurityMiddlewareTestScenarios.cs | 75 +++++---- .../CloudFoundry/PermissionsProviderTest.cs | 2 +- .../ApplicationInstanceCertificate.cs | 26 +-- .../CertificateAuthorizationHandler.cs | 15 +- .../CertificateHttpClientBuilderExtensions.cs | 14 +- ...nfigureCertificateAuthenticationOptions.cs | 8 +- src/Steeltoe.All.sln.DotSettings | 1 + versions.props | 2 +- 78 files changed, 1245 insertions(+), 451 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 0763ef1c5b..826a95c4eb 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2025.3.0.3", + "version": "2025.3.3", "commands": [ "jb" ], diff --git a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs index 2c1b85a862..a3c471e64d 100644 --- a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs +++ b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs @@ -38,7 +38,7 @@ namespace Steeltoe.Bootstrap.AutoConfiguration; -internal sealed class BootstrapScanner +internal sealed partial class BootstrapScanner { private readonly HostBuilderWrapper _wrapper; private readonly AssemblyLoader _loader; @@ -93,21 +93,21 @@ private void WireConfigServer() { _wrapper.AddConfigServer(_loggerFactory); - _logger.LogInformation("Configured Config Server configuration provider"); + LogConfigServerConfigured(); } private void WireCloudFoundryConfiguration() { _wrapper.AddCloudFoundryConfiguration(_loggerFactory); - _logger.LogInformation("Configured Cloud Foundry configuration provider"); + LogCloudFoundryConfigured(); } private void WireRandomValueProvider() { _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddRandomValueSource(_loggerFactory)); - _logger.LogInformation("Configured random value configuration provider"); + LogRandomValueConfigured(); } private void WireSpringBootProvider() @@ -117,21 +117,21 @@ private void WireSpringBootProvider() string[] args = Environment.GetCommandLineArgs().Skip(1).ToArray(); _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddSpringBootFromCommandLine(args, _loggerFactory)); - _logger.LogInformation("Configured Spring Boot configuration provider"); + LogSpringBootConfigured(); } private void WireDecryptionProvider() { _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddDecryption(_loggerFactory)); - _logger.LogInformation("Configured decryption configuration provider"); + LogDecryptionConfigured(); } private void WirePlaceholderResolver() { _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddPlaceholderResolver(_loggerFactory)); - _logger.LogInformation("Configured placeholder configuration provider"); + LogPlaceholderConfigured(); } private void WireConnectors() @@ -150,7 +150,7 @@ private void WireCosmosDbConnector() _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.ConfigureCosmosDb()); _wrapper.ConfigureServices((host, services) => services.AddCosmosDb(host.Configuration)); - _logger.LogInformation("Configured CosmosDB connector"); + LogCosmosDbConfigured(); } private void WireMongoDbConnector() @@ -158,7 +158,7 @@ private void WireMongoDbConnector() _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.ConfigureMongoDb()); _wrapper.ConfigureServices((host, services) => services.AddMongoDb(host.Configuration)); - _logger.LogInformation("Configured MongoDB connector"); + LogMongoDbConfigured(); } private void WireMySqlConnector() @@ -166,7 +166,7 @@ private void WireMySqlConnector() _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.ConfigureMySql()); _wrapper.ConfigureServices((host, services) => services.AddMySql(host.Configuration)); - _logger.LogInformation("Configured MySQL connector"); + LogMySqlConfigured(); } private void WirePostgreSqlConnector() @@ -174,7 +174,7 @@ private void WirePostgreSqlConnector() _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.ConfigurePostgreSql()); _wrapper.ConfigureServices((host, services) => services.AddPostgreSql(host.Configuration)); - _logger.LogInformation("Configured PostgreSQL connector"); + LogPostgreSqlConfigured(); } private void WireRabbitMQConnector() @@ -182,7 +182,7 @@ private void WireRabbitMQConnector() _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.ConfigureRabbitMQ()); _wrapper.ConfigureServices((host, services) => services.AddRabbitMQ(host.Configuration)); - _logger.LogInformation("Configured RabbitMQ connector"); + LogRabbitMQConfigured(); } private void WireRedisConnector() @@ -190,12 +190,12 @@ private void WireRedisConnector() _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.ConfigureRedis()); _wrapper.ConfigureServices((host, services) => services.AddRedis(host.Configuration)); - _logger.LogInformation("Configured StackExchange Redis connector"); + LogRedisConfigured(); // Intentionally ignoring excluded assemblies here. if (MicrosoftRedisPackageResolver.Default.IsAvailable()) { - _logger.LogInformation("Configured Redis distributed cache connector"); + LogRedisDistributedCacheConfigured(); } } @@ -204,63 +204,63 @@ private void WireSqlServerConnector() _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.ConfigureSqlServer()); _wrapper.ConfigureServices((host, services) => services.AddSqlServer(host.Configuration)); - _logger.LogInformation("Configured SQL Server connector"); + LogSqlServerConfigured(); } private void WireDynamicSerilog() { _wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddDynamicSerilog()); - _logger.LogInformation("Configured dynamic console logger for Serilog"); + LogDynamicSerilogConfigured(); } private void WireDynamicConsole() { _wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddDynamicConsole()); - _logger.LogInformation("Configured dynamic console logger"); + LogDynamicConsoleConfigured(); } private void WireDiscoveryConfiguration() { _wrapper.ConfigureServices(services => services.AddConfigurationDiscoveryClient()); - _logger.LogInformation("Configured configuration discovery client"); + LogConfigurationDiscoveryConfigured(); } private void WireDiscoveryConsul() { _wrapper.ConfigureServices(services => services.AddConsulDiscoveryClient()); - _logger.LogInformation("Configured Consul discovery client"); + LogConsulDiscoveryConfigured(); } private void WireDiscoveryEureka() { _wrapper.ConfigureServices(services => services.AddEurekaDiscoveryClient()); - _logger.LogInformation("Configured Eureka discovery client"); + LogEurekaDiscoveryConfigured(); } private void WireAllActuators() { _wrapper.ConfigureServices(services => services.AddAllActuators()); - _logger.LogInformation("Configured actuators"); + LogActuatorsConfigured(); } private void WirePrometheus() { _wrapper.ConfigureServices(services => services.AddPrometheusActuator()); - _logger.LogInformation("Configured Prometheus"); + LogPrometheusConfigured(); } private void WireDistributedTracingLogProcessor() { _wrapper.ConfigureServices(services => services.AddTracingLogProcessor()); - _logger.LogInformation("Configured distributed tracing log processor"); + LogDistributedTracingConfigured(); } private bool WireIfLoaded(Action wireAction, string assemblyName) @@ -281,4 +281,70 @@ private void WireIfAnyLoaded(Action wireAction, params PackageResolver[] package wireAction(); } } + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured Config Server configuration provider.")] + private partial void LogConfigServerConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured Cloud Foundry configuration provider.")] + private partial void LogCloudFoundryConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured random value configuration provider.")] + private partial void LogRandomValueConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured Spring Boot configuration provider.")] + private partial void LogSpringBootConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured decryption configuration provider.")] + private partial void LogDecryptionConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured placeholder configuration provider.")] + private partial void LogPlaceholderConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured CosmosDB connector.")] + private partial void LogCosmosDbConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured MongoDB connector.")] + private partial void LogMongoDbConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured MySQL connector.")] + private partial void LogMySqlConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured PostgreSQL connector.")] + private partial void LogPostgreSqlConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured RabbitMQ connector.")] + private partial void LogRabbitMQConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured StackExchange Redis connector.")] + private partial void LogRedisConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured Redis distributed cache connector.")] + private partial void LogRedisDistributedCacheConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured SQL Server connector.")] + private partial void LogSqlServerConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured dynamic console logger for Serilog.")] + private partial void LogDynamicSerilogConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured dynamic console logger.")] + private partial void LogDynamicConsoleConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured configuration discovery client.")] + private partial void LogConfigurationDiscoveryConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured Consul discovery client.")] + private partial void LogConsulDiscoveryConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured Eureka discovery client.")] + private partial void LogEurekaDiscoveryConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured actuators.")] + private partial void LogActuatorsConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured Prometheus.")] + private partial void LogPrometheusConfigured(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Configured distributed tracing log processor.")] + private partial void LogDistributedTracingConfigured(); } diff --git a/src/Common/src/Certificates/ConfigureCertificateOptions.cs b/src/Common/src/Certificates/ConfigureCertificateOptions.cs index 3a83066e04..e65c5bd924 100644 --- a/src/Common/src/Certificates/ConfigureCertificateOptions.cs +++ b/src/Common/src/Certificates/ConfigureCertificateOptions.cs @@ -10,10 +10,9 @@ namespace Steeltoe.Common.Certificates; -internal sealed class ConfigureCertificateOptions : IConfigureNamedOptions +internal sealed partial class ConfigureCertificateOptions : IConfigureNamedOptions { - private static readonly Regex CertificateRegex = new("-+BEGIN CERTIFICATE-+.+?-+END CERTIFICATE-+", RegexOptions.Compiled | RegexOptions.Singleline, - TimeSpan.FromSeconds(1)); + private const int RegexMatchTimeoutInMilliseconds = 1_000; private readonly IConfiguration _configuration; @@ -24,6 +23,10 @@ public ConfigureCertificateOptions(IConfiguration configuration) _configuration = configuration; } + [GeneratedRegex("-+BEGIN CERTIFICATE-+.+?-+END CERTIFICATE-+", RegexOptions.Singleline | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture, + RegexMatchTimeoutInMilliseconds)] + private static partial Regex CertificateRegex(); + public void Configure(CertificateOptions options) { Configure(Options.DefaultName, options); @@ -47,7 +50,7 @@ public void Configure(string? name, CertificateOptions options) ? X509Certificate2.CreateFromPemFile(certificateFilePath, privateKeyFilePath) : new X509Certificate2(certificateFilePath); - X509Certificate2[] certificateChain = CertificateRegex.Matches(File.ReadAllText(certificateFilePath)) + X509Certificate2[] certificateChain = CertificateRegex().Matches(File.ReadAllText(certificateFilePath)) .Select(x => new X509Certificate2(Encoding.ASCII.GetBytes(x.Value))).ToArray(); #pragma warning restore SYSLIB0057 // Type or member is obsolete diff --git a/src/Common/src/Common/Configuration/ConfigurationKeyConverter.cs b/src/Common/src/Common/Configuration/ConfigurationKeyConverter.cs index 8b3b9cd4fd..97a88a65e9 100644 --- a/src/Common/src/Common/Configuration/ConfigurationKeyConverter.cs +++ b/src/Common/src/Common/Configuration/ConfigurationKeyConverter.cs @@ -8,15 +8,17 @@ namespace Steeltoe.Common.Configuration; -internal static class ConfigurationKeyConverter +internal static partial class ConfigurationKeyConverter { private const string DotDelimiterString = "."; private const char DotDelimiterChar = '.'; private const char UnderscoreDelimiterChar = '_'; private const char EscapeChar = '\\'; private const string EscapeString = "\\"; + private const int RegexMatchTimeoutInMilliseconds = 1_000; - private static readonly Regex ArrayRegex = new(@"\[(?\d+)\]", RegexOptions.Compiled | RegexOptions.Singleline, TimeSpan.FromSeconds(1)); + [GeneratedRegex(@"\[(?\d+)\]", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture, RegexMatchTimeoutInMilliseconds)] + private static partial Regex ArrayRegex(); public static string AsDotNetConfigurationKey(string key) { @@ -82,6 +84,6 @@ static string UnEscapeString(string src) private static string ConvertArrayKey(string key) { - return ArrayRegex.Replace(key, ":${digits}"); + return ArrayRegex().Replace(key, ":${digits}"); } } diff --git a/src/Common/src/Common/Net/InetUtils.cs b/src/Common/src/Common/Net/InetUtils.cs index e821256786..47bf5c0b79 100644 --- a/src/Common/src/Common/Net/InetUtils.cs +++ b/src/Common/src/Common/Net/InetUtils.cs @@ -13,8 +13,11 @@ namespace Steeltoe.Common.Net; // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global // Non-sealed because this type is mocked by tests. -internal class InetUtils +internal partial class InetUtils { + private const RegexOptions InetRegexOptions = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture; + private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(1); + private readonly IDomainNameResolver _domainNameResolver; private readonly IOptionsMonitor _optionsMonitor; private readonly ILogger _logger; @@ -62,7 +65,7 @@ public virtual HostInfo FindFirstNonLoopbackHostInfo() { if (networkInterface is { OperationalStatus: OperationalStatus.Up, IsReceiveOnly: false }) { - _logger.LogTrace("Testing interface: {Name}, {Id}", networkInterface.Name, networkInterface.Id); + LogTestingInterface(networkInterface.Name, networkInterface.Id); IPInterfaceProperties properties = networkInterface.GetIPProperties(); IPv4InterfaceProperties iPv4Properties = properties.GetIPv4Properties(); @@ -84,7 +87,7 @@ public virtual HostInfo FindFirstNonLoopbackHostInfo() if (IsInet4Address(address) && !IsLoopbackAddress(address) && IsPreferredAddress(address, inetOptions)) { - _logger.LogTrace("Found non-loopback interface: {Name}", networkInterface.Name); + LogNonLoopbackInterfaceFound(networkInterface.Name); result = address; } } @@ -94,7 +97,7 @@ public virtual HostInfo FindFirstNonLoopbackHostInfo() } catch (Exception exception) { - _logger.LogError(exception, "Cannot get first non-loopback address"); + LogCannotGetNonLoopbackAddress(exception); } if (result != null) @@ -123,7 +126,7 @@ internal bool IsPreferredAddress(IPAddress address, InetOptions inetOptions) if (!siteLocalAddress) { - _logger.LogTrace("Ignoring address: {Address} [UseOnlySiteLocalInterfaces=true, this address is not]", address); + LogIgnoringNonSiteLocalAddress(address); } return siteLocalAddress; @@ -139,7 +142,7 @@ internal bool IsPreferredAddress(IPAddress address, InetOptions inetOptions) foreach (string regex in preferredNetworks) { string hostAddress = address.ToString(); - var matcher = new Regex(regex, RegexOptions.None, TimeSpan.FromSeconds(1)); + var matcher = new Regex(regex, InetRegexOptions, RegexMatchTimeout); if (matcher.IsMatch(hostAddress) || hostAddress.StartsWith(regex, StringComparison.Ordinal)) { @@ -147,7 +150,7 @@ internal bool IsPreferredAddress(IPAddress address, InetOptions inetOptions) } } - _logger.LogTrace("Ignoring address: {Address}", address); + LogIgnoringAddress(address); return false; } @@ -160,11 +163,11 @@ internal bool IgnoreInterface(string interfaceName, InetOptions inetOptions) foreach (string regex in inetOptions.GetIgnoredInterfaces()) { - var matcher = new Regex(regex, RegexOptions.None, TimeSpan.FromSeconds(1)); + var matcher = new Regex(regex, InetRegexOptions, RegexMatchTimeout); if (matcher.IsMatch(interfaceName)) { - _logger.LogTrace("Ignoring interface: {Name}", interfaceName); + LogIgnoringInterface(interfaceName); return true; } } @@ -186,7 +189,7 @@ internal HostInfo ConvertAddress(IPAddress address, InetOptions inetOptions) } catch (Exception exception) { - _logger.LogInformation(exception, "Cannot determine local hostname."); + LogCannotDetermineHostname(exception); hostname = "localhost"; } } @@ -220,7 +223,7 @@ internal HostInfo ConvertAddress(IPAddress address, InetOptions inetOptions) } catch (Exception exception) { - _logger.LogWarning(exception, "Unable to resolve host address."); + LogUnableToResolveHostAddress(exception); } return result; @@ -234,7 +237,7 @@ internal HostInfo ConvertAddress(IPAddress address, InetOptions inetOptions) } catch (Exception exception) { - _logger.LogWarning(exception, "Unable to resolve hostname."); + LogUnableToResolveHostname(exception); return null; } } @@ -252,4 +255,32 @@ private static bool IsSiteLocalAddress(IPAddress address) return text.StartsWith("10.", StringComparison.Ordinal) || text.StartsWith("172.16.", StringComparison.Ordinal) || text.StartsWith("192.168.", StringComparison.Ordinal); } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Testing interface {Name} with ID {Id}.")] + private partial void LogTestingInterface(string name, string id); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Found non-loopback interface {Name}.")] + private partial void LogNonLoopbackInterfaceFound(string name); + + [LoggerMessage(Level = LogLevel.Error, Message = "Cannot get first non-loopback address.")] + private partial void LogCannotGetNonLoopbackAddress(Exception exception); + + [LoggerMessage(Level = LogLevel.Trace, + Message = "Ignoring address {Address} because UseOnlySiteLocalInterfaces is true and this address is not site-local.")] + private partial void LogIgnoringNonSiteLocalAddress(IPAddress address); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Ignoring address {Address}.")] + private partial void LogIgnoringAddress(IPAddress address); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Ignoring interface {Name}.")] + private partial void LogIgnoringInterface(string name); + + [LoggerMessage(Level = LogLevel.Information, Message = "Cannot determine local hostname.")] + private partial void LogCannotDetermineHostname(Exception exception); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Unable to resolve host address.")] + private partial void LogUnableToResolveHostAddress(Exception exception); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Unable to resolve hostname.")] + private partial void LogUnableToResolveHostname(Exception exception); } diff --git a/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs b/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs index 0215563c03..f7296f00d4 100644 --- a/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs +++ b/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs @@ -58,7 +58,11 @@ public IEnumerable GetChildKeys(IEnumerable earlierKeys, string? ArgumentNullException.ThrowIfNull(earlierKeysArray, nameof(earlierKeys)); #pragma warning restore S3236 // Caller information arguments should not be provided explicitly - LogGetChildKeys(GetType().Name, earlierKeysArray, parentPath); + if (_logger.IsEnabled(LogLevel.Trace)) + { + string earlierKeyNames = string.Join(", ", earlierKeysArray.Select(key => $"'{key}'")); + LogGetChildKeys(GetType().Name, earlierKeyNames, parentPath); + } IConfiguration? section = parentPath == null ? ConfigurationRoot : ConfigurationRoot?.GetSection(parentPath); @@ -125,8 +129,9 @@ protected virtual void Dispose(bool disposing) [LoggerMessage(Level = LogLevel.Trace, Message = "CreateConfigurationRoot from {Type} with {ProviderCount} providers.")] private partial void LogCreateConfigurationRoot(string type, int providerCount); - [LoggerMessage(Level = LogLevel.Trace, Message = "GetChildKeys from {Type} with earlierKeys [{EarlierKeys}] and parentPath '{ParentPath}'.")] - private partial void LogGetChildKeys(string type, string[] earlierKeys, string? parentPath); + [LoggerMessage(Level = LogLevel.Trace, SkipEnabledCheck = true, + Message = "GetChildKeys from {Type} with earlierKeys [{EarlierKeys}] and parentPath '{ParentPath}'.")] + private partial void LogGetChildKeys(string type, string earlierKeys, string? parentPath); [LoggerMessage(Level = LogLevel.Trace, Message = "TryGet from {Type} with key '{Key}'.")] private partial void LogTryGet(string type, string key); diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/CloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/CloudFoundryPostProcessor.cs index dbf1c2a71e..a54b06d45e 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/CloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/CloudFoundryPostProcessor.cs @@ -7,13 +7,17 @@ namespace Steeltoe.Configuration.CloudFoundry.ServiceBindings.PostProcessors; -internal abstract class CloudFoundryPostProcessor : IConfigurationPostProcessor +internal abstract partial class CloudFoundryPostProcessor : IConfigurationPostProcessor { - private static readonly Regex TagsConfigurationKeyRegex = - new("^vcap:services:[^:]+:[0-9]+:tags:[0-9]+", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); + private const int RegexMatchTimeoutInMilliseconds = 1_000; - private static readonly Regex LabelConfigurationKeyRegex = - new("^vcap:services:[^:]+:[0-9]+:label+", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); + [GeneratedRegex("^vcap:services:[^:]+:[0-9]+:tags:[0-9]+", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture, + RegexMatchTimeoutInMilliseconds)] + private static partial Regex TagsConfigurationKeyRegex(); + + [GeneratedRegex("^vcap:services:[^:]+:[0-9]+:label+", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture, + RegexMatchTimeoutInMilliseconds)] + private static partial Regex LabelConfigurationKeyRegex(); public abstract void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData); @@ -23,7 +27,7 @@ protected ICollection FilterKeys(IDictionary configurat foreach ((string key, string? value) in configurationData) { - if ((sources & KeyFilterSources.Tag) != 0 && TagsConfigurationKeyRegex.IsMatch(key) && + if ((sources & KeyFilterSources.Tag) != 0 && TagsConfigurationKeyRegex().IsMatch(key) && string.Equals(value, valueToFind, StringComparison.OrdinalIgnoreCase)) { string? parentKey = ConfigurationPath.GetParentPath(key); @@ -39,7 +43,7 @@ protected ICollection FilterKeys(IDictionary configurat } } - if ((sources & KeyFilterSources.Label) != 0 && LabelConfigurationKeyRegex.IsMatch(key) && + if ((sources & KeyFilterSources.Label) != 0 && LabelConfigurationKeyRegex().IsMatch(key) && string.Equals(value, valueToFind, StringComparison.OrdinalIgnoreCase)) { string? serviceBindingKey = ConfigurationPath.GetParentPath(key); diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/EurekaCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/EurekaCloudFoundryPostProcessor.cs index ae87402a15..c7713086af 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/EurekaCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/EurekaCloudFoundryPostProcessor.cs @@ -6,7 +6,7 @@ namespace Steeltoe.Configuration.CloudFoundry.ServiceBindings.PostProcessors; -internal sealed class EurekaCloudFoundryPostProcessor : CloudFoundryPostProcessor +internal sealed partial class EurekaCloudFoundryPostProcessor : CloudFoundryPostProcessor { internal const string BindingType = "eureka"; internal const string EurekaConfigurationKeyPrefix = "eureka:client"; @@ -27,7 +27,7 @@ public override void PostProcessConfiguration(PostProcessorConfigurationProvider { if (hasMapped) { - _logger.LogWarning("Multiple Eureka service bindings found, which is not supported. Using the first binding from VCAP_SERVICES."); + LogMultipleEurekaBindings(); break; } @@ -52,4 +52,8 @@ public override void PostProcessConfiguration(PostProcessorConfigurationProvider hasMapped = true; } } + + [LoggerMessage(Level = LogLevel.Warning, + Message = "Multiple Eureka service bindings found, which is not supported. Using the first binding from VCAP_SERVICES.")] + private partial void LogMultipleEurekaBindings(); } diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/IdentityCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/IdentityCloudFoundryPostProcessor.cs index 93a96da8e7..068ff27384 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/IdentityCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/IdentityCloudFoundryPostProcessor.cs @@ -6,7 +6,7 @@ namespace Steeltoe.Configuration.CloudFoundry.ServiceBindings.PostProcessors; -internal sealed class IdentityCloudFoundryPostProcessor : CloudFoundryPostProcessor +internal sealed partial class IdentityCloudFoundryPostProcessor : CloudFoundryPostProcessor { internal const string BindingType = "p-identity"; internal const string AuthenticationConfigurationKeyPrefix = "Authentication:Schemes"; @@ -34,7 +34,7 @@ public override void PostProcessConfiguration(PostProcessorConfigurationProvider { if (hasMapped) { - _logger.LogWarning("Multiple identity service bindings found, which is not supported. Using the first binding from VCAP_SERVICES."); + LogMultipleIdentityBindings(); break; } @@ -50,4 +50,8 @@ public override void PostProcessConfiguration(PostProcessorConfigurationProvider hasMapped = true; } } + + [LoggerMessage(Level = LogLevel.Warning, + Message = "Multiple identity service bindings found, which is not supported. Using the first binding from VCAP_SERVICES.")] + private partial void LogMultipleIdentityBindings(); } diff --git a/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs b/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs index 7963604378..36be91f368 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs @@ -25,7 +25,7 @@ namespace Steeltoe.Configuration.ConfigServer; /// /// A Spring Cloud Config Server based . /// -internal sealed class ConfigServerConfigurationProvider : ConfigurationProvider, IDisposable +internal sealed partial class ConfigServerConfigurationProvider : ConfigurationProvider, IDisposable { private const string VaultRenewPath = "vault/v1/auth/token/renew-self"; private const string VaultTokenHeader = "X-Vault-Token"; @@ -149,7 +149,7 @@ private void OnSettingsChanged() /// private async Task DoPolledLoadAsync() { - _logger.LogTrace("Entering timer cycle"); + LogEnteringTimerCycle(); bool lockTaken = false; try @@ -165,23 +165,23 @@ private async Task DoPolledLoadAsync() { if (lockTaken) { - _logger.LogTrace("Exclusive lock obtained"); + LogExclusiveLockObtained(); await DoLoadAsync(true, CancellationToken.None); } else { - _logger.LogTrace("Previous cycle is still running, or already disposed; skipping this cycle"); + LogSkippingCycle(); } } catch (Exception exception) { - _logger.LogWarning(exception, "Could not reload configuration during polling"); + LogCouldNotReloadDuringPolling(exception); } finally { if (lockTaken) { - _logger.LogTrace("Timer cycle completed, releasing exclusive lock"); + LogTimerCycleCompleted(); try { @@ -210,7 +210,7 @@ public override void Load() { if (!ClientOptions.Enabled) { - _logger.LogInformation("Config Server client disabled, did not fetch configuration!"); + LogConfigServerClientDisabled(); return null; } @@ -230,7 +230,7 @@ public override void Load() do { - _logger.LogDebug("Fetching configuration from server(s)."); + LogFetchingConfiguration(); try { @@ -238,7 +238,7 @@ public override void Load() } catch (ConfigServerException exception) { - _logger.LogWarning(exception, "Failed fetching configuration from server(s)."); + LogFailedFetchingConfiguration(exception); attempts++; if (attempts < ClientOptions.Retry.MaxAttempts) @@ -256,7 +256,7 @@ public override void Load() while (true); } - _logger.LogDebug("Fetching configuration from server(s)."); + LogFetchingConfiguration(); return await DoLoadAsync(updateDictionary, cancellationToken); } @@ -271,11 +271,11 @@ public override void Load() { foreach (string label in GetLabels()) { - _logger.LogTrace("Processing label '{Label}'", label); + LogProcessingLabel(label); if (uris.Count > 1) { - _logger.LogDebug("Multiple Config Server Uris listed."); + LogMultipleConfigServerUris(); } // Invoke Config Servers @@ -284,8 +284,7 @@ public override void Load() // Update configuration Data dictionary with any results if (env != null) { - _logger.LogDebug("Located environment name: {Name}, profiles: {Profiles}, labels: {Label}, version: {Version}, state: {State}", env.Name, - env.Profiles, env.Label, env.Version, env.State); + LogEnvironmentLocated(env.Name, string.Join(", ", env.Profiles.Select(p => $"'{p}'")), env.Label, env.Version, env.State); if (updateDictionary) { @@ -314,13 +313,13 @@ public override void Load() if (!AreDictionariesEqual(Data, data)) { - _logger.LogTrace("Data has changed, raising configuration reload"); + LogDataChanged(); Data = data; OnReload(); } else { - _logger.LogTrace("Data has not changed"); + LogDataNotChanged(); } } @@ -333,11 +332,11 @@ public override void Load() error = exception; } - _logger.LogWarning(error, "Could not locate PropertySource"); + LogCouldNotLocatePropertySource(error); if (ClientOptions.FailFast) { - _logger.LogTrace(error, "Failure with FailFast enabled, throwing ConfigServerException"); + LogFailFastEnabled(error); throw new ConfigServerException("Could not locate PropertySource, fail fast property is set, failing", error); } @@ -454,7 +453,7 @@ internal async Task GetRequestMessageAsync(Uri requestUri, C if (requestUri.TryGetUsernamePassword(out string? username, out string? password) && password.Length > 0) { - _logger.LogDebug("Adding credentials from '{RequestUri}' to Authorization header.", requestUri.ToMaskedString()); + LogAddingCredentials(requestUri.ToMaskedString()); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}"))); @@ -469,7 +468,7 @@ internal async Task GetRequestMessageAsync(Uri requestUri, C string accessToken = await httpClient.GetAccessTokenAsync(accessTokenUri, ClientOptions.ClientId, ClientOptions.ClientSecret, cancellationToken); - _logger.LogDebug("Fetched access token from '{AccessTokenUri}'.", accessTokenUri.ToMaskedString()); + LogAccessTokenFetched(accessTokenUri.ToMaskedString()); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); } } @@ -543,7 +542,7 @@ private void AddConfigServerClientOptions(Dictionary data) internal async Task RemoteLoadAsync(List requestUris, string? label, CancellationToken cancellationToken) { - _logger.LogTrace("Entered {Method}", nameof(RemoteLoadAsync)); + LogRemoteLoadEntered(nameof(RemoteLoadAsync)); // Get client if not already set using HttpClient httpClient = CreateHttpClient(ClientOptions); @@ -555,19 +554,19 @@ private void AddConfigServerClientOptions(Dictionary data) // Make Config Server URI from settings Uri uri = BuildConfigServerUri(requestUri, label); - _logger.LogDebug("Trying to connect to Config Server at {RequestUri}", uri.ToMaskedString()); + LogTryingToConnect(uri.ToMaskedString()); // Get the request message - _logger.LogTrace("Building HTTP request message"); + LogBuildingHttpRequest(); HttpRequestMessage request = await GetRequestMessageAsync(uri, cancellationToken); // Invoke Config Server try { - _logger.LogTrace("Sending HTTP request"); + LogSendingHttpRequest(); using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken); - _logger.LogDebug("Config Server returned status: {StatusCode} invoking path: {RequestUri}", response.StatusCode, uri.ToMaskedString()); + LogConfigServerReturnedStatus(response.StatusCode, uri.ToMaskedString()); if (response.StatusCode != HttpStatusCode.OK) { @@ -585,7 +584,7 @@ private void AddConfigServerClientOptions(Dictionary data) return null; } - _logger.LogTrace("Parsing JSON response"); + LogParsingJsonResponse(); return await response.Content.ReadFromJsonAsync(SerializerOptions, cancellationToken); } catch (Exception exception) when (!exception.IsCancellation()) @@ -594,7 +593,7 @@ private void AddConfigServerClientOptions(Dictionary data) if (IsSocketError(exception)) { - _logger.LogTrace(exception, "Socket error detected"); + LogSocketError(exception); continue; } @@ -689,7 +688,7 @@ private void AddPropertySource(PropertySource? source, Dictionary GetVaultRenewRequestMessageAsync(Uri requ string accessToken = await httpClient.GetAccessTokenAsync(accessTokenUri, ClientOptions.ClientId, ClientOptions.ClientSecret, cancellationToken); - _logger.LogDebug("Fetched access token from '{AccessTokenUri}'.", accessTokenUri.ToMaskedString()); + LogAccessTokenFetched(accessTokenUri.ToMaskedString()); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); } @@ -838,4 +837,89 @@ public void Dispose() _httpClientHandler = null; } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Entering timer cycle.")] + private partial void LogEnteringTimerCycle(); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Exclusive lock obtained.")] + private partial void LogExclusiveLockObtained(); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Previous cycle is still running, or already disposed; skipping this cycle.")] + private partial void LogSkippingCycle(); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Could not reload configuration during polling.")] + private partial void LogCouldNotReloadDuringPolling(Exception exception); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Timer cycle completed, releasing exclusive lock.")] + private partial void LogTimerCycleCompleted(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Config Server client disabled, not fetching configuration.")] + private partial void LogConfigServerClientDisabled(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Fetching configuration from server(s).")] + private partial void LogFetchingConfiguration(); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Failed fetching configuration from server(s).")] + private partial void LogFailedFetchingConfiguration(Exception exception); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Processing label '{Label}'.")] + private partial void LogProcessingLabel(string? label); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Multiple Config Server Uris listed.")] + private partial void LogMultipleConfigServerUris(); + + [LoggerMessage(Level = LogLevel.Debug, + Message = "Located environment with name {Name}, profiles {Profiles}, label {Label}, version {Version} and state {State}.")] + private partial void LogEnvironmentLocated(string? name, string profiles, string? label, string? version, string? state); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Data has changed, raising configuration reload.")] + private partial void LogDataChanged(); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Data has not changed.")] + private partial void LogDataNotChanged(); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Could not locate property source.")] + private partial void LogCouldNotLocatePropertySource(Exception? error); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Failure with FailFast enabled, throwing ConfigServerException.")] + private partial void LogFailFastEnabled(Exception? error); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Adding credentials from '{RequestUri}' to Authorization header.")] + private partial void LogAddingCredentials(string requestUri); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Fetched access token from {AccessTokenUri}.")] + private partial void LogAccessTokenFetched(string accessTokenUri); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Entered {Method}.")] + private partial void LogRemoteLoadEntered(string method); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Trying to connect to Config Server at {RequestUri}.")] + private partial void LogTryingToConnect(string requestUri); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Building HTTP request message.")] + private partial void LogBuildingHttpRequest(); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Sending HTTP request.")] + private partial void LogSendingHttpRequest(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Config Server returned status {StatusCode} for path {RequestUri}.")] + private partial void LogConfigServerReturnedStatus(HttpStatusCode statusCode, string requestUri); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Parsing JSON response.")] + private partial void LogParsingJsonResponse(); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Socket error detected.")] + private partial void LogSocketError(Exception exception); + + [LoggerMessage(Level = LogLevel.Error, Message = "Config Server exception for property {Key} of type {Type}.")] + private partial void LogConfigServerPropertyException(Exception exception, string key, Type type); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Renewing Vault token {Token} for {Ttl} milliseconds at Uri {Uri}.")] + private partial void LogRenewingVaultToken(string token, int ttl, string uri); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Renewing Vault token {Token} returned status {Status}.")] + private partial void LogVaultTokenRenewalStatus(string token, HttpStatusCode status); + + [LoggerMessage(Level = LogLevel.Error, Message = "Unable to renew Vault token {Token}. The token is likely invalid or has expired.")] + private partial void LogUnableToRenewVaultToken(Exception exception, string token); } diff --git a/src/Configuration/src/ConfigServer/ConfigServerDiscoveryService.cs b/src/Configuration/src/ConfigServer/ConfigServerDiscoveryService.cs index 5bc32ebaf1..a6b86ee428 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerDiscoveryService.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerDiscoveryService.cs @@ -15,7 +15,7 @@ namespace Steeltoe.Configuration.ConfigServer; -internal sealed class ConfigServerDiscoveryService +internal sealed partial class ConfigServerDiscoveryService { private static readonly AssemblyLoader AssemblyLoader = new(); private readonly IConfiguration _configuration; @@ -99,7 +99,7 @@ private IDiscoveryClient[] GetDiscoveryClientsFromServiceCollection(ServiceColle foreach (IDiscoveryClient discoveryClient in discoveryClients) { - _logger.LogDebug("Found discovery client of type {DiscoveryClientType}", discoveryClient.GetType()); + LogDiscoveryClientFound(discoveryClient.GetType()); } return discoveryClients; @@ -113,7 +113,7 @@ internal async Task> GetConfigServerInstancesAsync do { - _logger.LogDebug("Locating ConfigServer {ServiceId} via discovery", _options.Discovery.ServiceId); + LogLocatingConfigServer(_options.Discovery.ServiceId); if (_options.Discovery.ServiceId != null) { @@ -126,7 +126,7 @@ internal async Task> GetConfigServerInstancesAsync } catch (Exception exception) when (!exception.IsCancellation()) { - _logger.LogError(exception, "Failed to get instances during ConfigServer lookup from {DiscoveryClient}.", discoveryClient.GetType()); + LogFailedToGetInstances(exception, discoveryClient.GetType()); } } } @@ -158,7 +158,7 @@ internal async Task ProvideRuntimeReplacementsAsync(ICollection _logger; @@ -30,7 +30,7 @@ public ConfigServerHealthContributor(IConfiguration configuration, TimeProvider if (Provider == null) { - _logger.LogWarning("Unable to find ConfigServerConfigurationProvider, health check disabled"); + LogHealthCheckDisabled(); } } @@ -40,7 +40,7 @@ public ConfigServerHealthContributor(IConfiguration configuration, TimeProvider if (Provider == null) { - _logger.LogDebug("No Config Server provider found"); + LogNoProviderFound(); health.Status = HealthStatus.Unknown; health.Details.Add("error", "No Config Server provider found"); return health; @@ -55,7 +55,7 @@ public ConfigServerHealthContributor(IConfiguration configuration, TimeProvider if (sources == null || sources.Count == 0) { - _logger.LogDebug("No property sources found"); + LogNoPropertySourcesFound(); health.Status = HealthStatus.Unknown; health.Details.Add("error", "No property sources found"); return health; @@ -67,14 +67,14 @@ public ConfigServerHealthContributor(IConfiguration configuration, TimeProvider internal void UpdateHealth(HealthCheckResult health, IList sources) { - _logger.LogDebug("Config Server health check returning UP"); + LogHealthCheckReturningUp(); health.Status = HealthStatus.Up; List names = []; foreach (PropertySource source in sources) { - _logger.LogDebug("Returning property source: {PropertySource}", source.Name); + LogReturningPropertySource(source.Name); names.Add(source.Name); } @@ -88,7 +88,7 @@ internal void UpdateHealth(HealthCheckResult health, IList sourc if (IsCacheStale(currentTime)) { LastAccess = currentTime; - _logger.LogDebug("Cache stale, fetching config server health"); + LogCacheStale(); Cached = await provider.LoadInternalAsync(false, cancellationToken); } @@ -114,4 +114,22 @@ internal long GetTimeToLive() { return Provider != null ? Provider.ClientOptions.Health.TimeToLive : long.MaxValue; } + + [LoggerMessage(Level = LogLevel.Warning, Message = "No Config Server provider found, health check disabled.")] + private partial void LogHealthCheckDisabled(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "No Config Server provider found.")] + private partial void LogNoProviderFound(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "No property sources found.")] + private partial void LogNoPropertySourcesFound(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Config Server health check returning UP.")] + private partial void LogHealthCheckReturningUp(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Returning property source {PropertySource}.")] + private partial void LogReturningPropertySource(string? propertySource); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Cache stale, fetching config server health.")] + private partial void LogCacheStale(); } diff --git a/src/Configuration/src/Encryption/DecryptionConfigurationProvider.cs b/src/Configuration/src/Encryption/DecryptionConfigurationProvider.cs index 5caa46c875..0b8be46740 100644 --- a/src/Configuration/src/Encryption/DecryptionConfigurationProvider.cs +++ b/src/Configuration/src/Encryption/DecryptionConfigurationProvider.cs @@ -13,10 +13,13 @@ internal sealed partial class DecryptionConfigurationProvider( IList providers, ITextDecryptor? textDecryptor, ILoggerFactory loggerFactory) : CompositeConfigurationProvider(providers, loggerFactory) { + private const int RegexMatchTimeoutInMilliseconds = 1_000; + private readonly ILogger _logger = loggerFactory.CreateLogger(); private ITextDecryptor? _textDecryptor = textDecryptor; - [GeneratedRegex("^{cipher}({key:(?.*)})?(?.*)$", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture, 1000)] + [GeneratedRegex("^{cipher}({key:(?.*)})?(?.*)$", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture, + RegexMatchTimeoutInMilliseconds)] private static partial Regex CipherRegex(); public override bool TryGet(string key, out string? value) diff --git a/src/Configuration/src/Encryption/DecryptionConfigurationSource.cs b/src/Configuration/src/Encryption/DecryptionConfigurationSource.cs index a55cbc8aee..d703d9e247 100644 --- a/src/Configuration/src/Encryption/DecryptionConfigurationSource.cs +++ b/src/Configuration/src/Encryption/DecryptionConfigurationSource.cs @@ -35,5 +35,5 @@ public IConfigurationProvider Build(IConfigurationBuilder builder) } [LoggerMessage(Level = LogLevel.Trace, Message = "Build for {SourceCount} sources and {PropertyCount} properties.")] - private static partial void LogBuild(ILogger logger, int sourceCount, int propertyCount); + private static partial void LogBuild(ILogger logger, int sourceCount, int propertyCount); } diff --git a/src/Configuration/src/Placeholder/PlaceholderConfigurationSource.cs b/src/Configuration/src/Placeholder/PlaceholderConfigurationSource.cs index 0b6a35b2f9..29ecbb202a 100644 --- a/src/Configuration/src/Placeholder/PlaceholderConfigurationSource.cs +++ b/src/Configuration/src/Placeholder/PlaceholderConfigurationSource.cs @@ -32,5 +32,5 @@ public IConfigurationProvider Build(IConfigurationBuilder builder) } [LoggerMessage(Level = LogLevel.Trace, Message = "Build for {SourceCount} sources and {PropertyCount} properties.")] - private static partial void LogBuild(ILogger logger, int sourceCount, int propertyCount); + private static partial void LogBuild(ILogger logger, int sourceCount, int propertyCount); } diff --git a/src/Configuration/src/Placeholder/PropertyPlaceHolderHelper.cs b/src/Configuration/src/Placeholder/PropertyPlaceHolderHelper.cs index 4cca8c32f1..9753c42830 100644 --- a/src/Configuration/src/Placeholder/PropertyPlaceHolderHelper.cs +++ b/src/Configuration/src/Placeholder/PropertyPlaceHolderHelper.cs @@ -22,7 +22,7 @@ namespace Steeltoe.Configuration.Placeholder; /// . /// /// -internal sealed class PropertyPlaceholderHelper +internal sealed partial class PropertyPlaceholderHelper { private const string Prefix = "${"; private const string Suffix = "}"; @@ -110,7 +110,7 @@ public PropertyPlaceholderHelper(ILogger logger) propertyValue = ParseStringValue(propertyValue, configuration, visitedPlaceholders); Replace(result, startIndex, endIndex + Suffix.Length, propertyValue); - _logger.LogDebug("Resolved placeholder '{Placeholder}' to '{Value}'", innerPlaceholder, propertyValue); + LogPlaceholderResolved(innerPlaceholder, propertyValue); startIndex = IndexOf(result, Prefix, startIndex + propertyValue.Length); } else @@ -202,6 +202,9 @@ private static string Substring(StringBuilder builder, int start, int end) return builder.ToString()[start..end]; } + [LoggerMessage(Level = LogLevel.Debug, Message = "Resolved placeholder '{Placeholder}' to '{Value}'.")] + private partial void LogPlaceholderResolved(string placeholder, string value); + private readonly struct PlaceholderExpression(string key, string? defaultValue) : IEquatable { public string Key { get; } = key; diff --git a/src/Configuration/src/RandomValue/RandomValueProvider.cs b/src/Configuration/src/RandomValue/RandomValueProvider.cs index 8eed55e1c4..dbbebe0195 100644 --- a/src/Configuration/src/RandomValue/RandomValueProvider.cs +++ b/src/Configuration/src/RandomValue/RandomValueProvider.cs @@ -12,7 +12,7 @@ namespace Steeltoe.Configuration.RandomValue; /// /// Configuration provider that provides random values. Note: This code was inspired by the Spring Boot equivalent class. /// -internal sealed class RandomValueProvider : ConfigurationProvider +internal sealed partial class RandomValueProvider : ConfigurationProvider { private readonly ILogger _logger; private readonly string _prefix; @@ -60,7 +60,7 @@ public override bool TryGet(string key, out string? value) } value = GetRandomValue(key[_prefix.Length..]); - _logger.LogDebug("Generated random value {Value} for '{Key}'", value, key); + LogRandomValueGenerated(value, key); return true; } @@ -199,4 +199,7 @@ private string GetRandomBytes() Random.Shared.NextBytes(bytes); return Convert.ToHexString(bytes); } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Generated random value '{Value}' for '{Key}'.")] + private partial void LogRandomValueGenerated(string? value, string key); } diff --git a/src/Configuration/test/Placeholder.Test/TestConfigurationSource.cs b/src/Configuration/test/Placeholder.Test/TestConfigurationSource.cs index ca6ce53fb3..572dccca30 100644 --- a/src/Configuration/test/Placeholder.Test/TestConfigurationSource.cs +++ b/src/Configuration/test/Placeholder.Test/TestConfigurationSource.cs @@ -25,5 +25,5 @@ public IConfigurationProvider Build(IConfigurationBuilder builder) } [LoggerMessage(Level = LogLevel.Trace, Message = "Build ({Name}).")] - private static partial void LogBuild(ILogger logger, string name); + private static partial void LogBuild(ILogger logger, string name); } diff --git a/src/Connectors/src/Connectors/CosmosDb/CosmosDbHealthContributor.cs b/src/Connectors/src/Connectors/CosmosDb/CosmosDbHealthContributor.cs index e1b1993a33..ffdcf04cab 100644 --- a/src/Connectors/src/Connectors/CosmosDb/CosmosDbHealthContributor.cs +++ b/src/Connectors/src/Connectors/CosmosDb/CosmosDbHealthContributor.cs @@ -12,7 +12,7 @@ namespace Steeltoe.Connectors.CosmosDb; -internal sealed class CosmosDbHealthContributor : IHealthContributor, IDisposable +internal sealed partial class CosmosDbHealthContributor : IHealthContributor, IDisposable { private readonly CosmosClientShimFactory _clientFactory; private readonly ILogger _logger; @@ -40,7 +40,7 @@ public CosmosDbHealthContributor(string serviceName, IServiceProvider servicePro public async Task CheckHealthAsync(CancellationToken cancellationToken) { - _logger.LogTrace("Checking {DbConnection} health at {Host}", Id, Host); + LogCheckingHealth(Id, Host); var result = new HealthCheckResult { @@ -64,7 +64,7 @@ public CosmosDbHealthContributor(string serviceName, IServiceProvider servicePro result.Status = HealthStatus.Up; - _logger.LogTrace("{DbConnection} at {Host} is up!", Id, Host); + LogHealthUp(Id, Host); } catch (Exception exception) { @@ -75,7 +75,7 @@ public CosmosDbHealthContributor(string serviceName, IServiceProvider servicePro ExceptionDispatchInfo.Capture(exception).Throw(); } - _logger.LogError(exception, "{DbConnection} at {Host} is down!", Id, Host); + LogHealthDown(exception, Id, Host); result.Status = HealthStatus.Down; result.Description = $"{Id} health check failed"; @@ -91,6 +91,15 @@ public void Dispose() _cosmosClientShim = null; } + [LoggerMessage(Level = LogLevel.Trace, Message = "Checking {DbConnection} health at {Host}.")] + private partial void LogCheckingHealth(string dbConnection, string host); + + [LoggerMessage(Level = LogLevel.Trace, Message = "{DbConnection} at {Host} is up.")] + private partial void LogHealthUp(string dbConnection, string host); + + [LoggerMessage(Level = LogLevel.Error, Message = "{DbConnection} at {Host} is down.")] + private partial void LogHealthDown(Exception exception, string dbConnection, string host); + private sealed class CosmosClientShimFactory { private readonly ConnectorShim _connectorShim; diff --git a/src/Connectors/src/Connectors/MongoDb/MongoDbHealthContributor.cs b/src/Connectors/src/Connectors/MongoDb/MongoDbHealthContributor.cs index 311e6d0457..ed0b0f9f0a 100644 --- a/src/Connectors/src/Connectors/MongoDb/MongoDbHealthContributor.cs +++ b/src/Connectors/src/Connectors/MongoDb/MongoDbHealthContributor.cs @@ -11,7 +11,7 @@ namespace Steeltoe.Connectors.MongoDb; -internal sealed class MongoDbHealthContributor : IHealthContributor +internal sealed partial class MongoDbHealthContributor : IHealthContributor { private readonly MongoClientInterfaceShimFactory _clientFactory; private readonly ILogger _logger; @@ -36,7 +36,7 @@ public MongoDbHealthContributor(string serviceName, IServiceProvider serviceProv public async Task CheckHealthAsync(CancellationToken cancellationToken) { - _logger.LogTrace("Checking {DbConnection} health at {Host}", Id, Host); + LogCheckingHealth(Id, Host); var result = new HealthCheckResult { @@ -59,7 +59,7 @@ public MongoDbHealthContributor(string serviceName, IServiceProvider serviceProv result.Status = HealthStatus.Up; - _logger.LogTrace("{DbConnection} at {Host} is up!", Id, Host); + LogHealthUp(Id, Host); } catch (Exception exception) { @@ -70,7 +70,7 @@ public MongoDbHealthContributor(string serviceName, IServiceProvider serviceProv ExceptionDispatchInfo.Capture(exception).Throw(); } - _logger.LogError(exception, "{DbConnection} at {Host} is down!", Id, Host); + LogHealthDown(exception, Id, Host); result.Status = HealthStatus.Down; result.Description = $"{Id} health check failed"; @@ -80,6 +80,15 @@ public MongoDbHealthContributor(string serviceName, IServiceProvider serviceProv return result; } + [LoggerMessage(Level = LogLevel.Trace, Message = "Checking {DbConnection} health at {Host}.")] + private partial void LogCheckingHealth(string dbConnection, string host); + + [LoggerMessage(Level = LogLevel.Trace, Message = "{DbConnection} at {Host} is up.")] + private partial void LogHealthUp(string dbConnection, string host); + + [LoggerMessage(Level = LogLevel.Error, Message = "{DbConnection} at {Host} is down.")] + private partial void LogHealthDown(Exception exception, string dbConnection, string host); + private sealed class MongoClientInterfaceShimFactory { private readonly ConnectorShim _connectorShim; diff --git a/src/Connectors/src/Connectors/RabbitMQ/RabbitMQHealthContributor.cs b/src/Connectors/src/Connectors/RabbitMQ/RabbitMQHealthContributor.cs index b534510529..a324a4ca11 100644 --- a/src/Connectors/src/Connectors/RabbitMQ/RabbitMQHealthContributor.cs +++ b/src/Connectors/src/Connectors/RabbitMQ/RabbitMQHealthContributor.cs @@ -11,7 +11,7 @@ namespace Steeltoe.Connectors.RabbitMQ; -internal sealed class RabbitMQHealthContributor : IHealthContributor, IDisposable +internal sealed partial class RabbitMQHealthContributor : IHealthContributor, IDisposable { private readonly ILogger _logger; private readonly ConnectionFactoryInterfaceShim _connectionFactoryInterfaceShim; @@ -34,7 +34,7 @@ public RabbitMQHealthContributor(object connectionFactory, string host, ILogger< public async Task CheckHealthAsync(CancellationToken cancellationToken) { - _logger.LogTrace("Checking {DbConnection} health at {Host}", Id, Host); + LogCheckingHealth(Id, Host); var result = new HealthCheckResult { @@ -68,7 +68,7 @@ public RabbitMQHealthContributor(object connectionFactory, string host, ILogger< result.Status = HealthStatus.Up; - _logger.LogTrace("{DbConnection} at {Host} is up!", Id, Host); + LogHealthUp(Id, Host); } catch (Exception exception) { @@ -79,7 +79,7 @@ public RabbitMQHealthContributor(object connectionFactory, string host, ILogger< ExceptionDispatchInfo.Capture(exception).Throw(); } - _logger.LogError(exception, "{DbConnection} at {Host} is down!", Id, Host); + LogHealthDown(exception, Id, Host); result.Status = HealthStatus.Down; result.Description = $"{Id} health check failed"; @@ -94,4 +94,13 @@ public void Dispose() _connectionInterfaceShim?.Dispose(); _connectionInterfaceShim = null; } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Checking {DbConnection} health at {Host}.")] + private partial void LogCheckingHealth(string dbConnection, string host); + + [LoggerMessage(Level = LogLevel.Trace, Message = "{DbConnection} at {Host} is up.")] + private partial void LogHealthUp(string dbConnection, string host); + + [LoggerMessage(Level = LogLevel.Error, Message = "{DbConnection} at {Host} is down.")] + private partial void LogHealthDown(Exception exception, string dbConnection, string host); } diff --git a/src/Connectors/src/Connectors/Redis/RedisHealthContributor.cs b/src/Connectors/src/Connectors/Redis/RedisHealthContributor.cs index 9a2e0eb98c..efa9496da0 100644 --- a/src/Connectors/src/Connectors/Redis/RedisHealthContributor.cs +++ b/src/Connectors/src/Connectors/Redis/RedisHealthContributor.cs @@ -10,7 +10,7 @@ namespace Steeltoe.Connectors.Redis; -internal sealed class RedisHealthContributor : IHealthContributor, IDisposable +internal sealed partial class RedisHealthContributor : IHealthContributor, IDisposable { private readonly ILogger _logger; private readonly string? _connectionString; @@ -54,7 +54,7 @@ internal void SetConnectionMultiplexer(object connectionMultiplexer) public async Task CheckHealthAsync(CancellationToken cancellationToken) { - _logger.LogTrace("Checking {DbConnection} health at {Host}", Id, Host); + LogCheckingHealth(Id, Host); var result = new HealthCheckResult { @@ -80,7 +80,7 @@ internal void SetConnectionMultiplexer(object connectionMultiplexer) result.Status = HealthStatus.Up; result.Details.Add("ping", roundTripTime.TotalMilliseconds); - _logger.LogTrace("{DbConnection} at {Host} is up!", Id, Host); + LogHealthUp(Id, Host); } catch (Exception exception) { @@ -91,7 +91,7 @@ internal void SetConnectionMultiplexer(object connectionMultiplexer) ExceptionDispatchInfo.Capture(exception).Throw(); } - _logger.LogError(exception, "{DbConnection} at {Host} is down!", Id, Host); + LogHealthDown(exception, Id, Host); result.Status = HealthStatus.Down; result.Description = $"{Id} health check failed"; @@ -106,4 +106,13 @@ public void Dispose() _connectionMultiplexerInterfaceShim?.Dispose(); _connectionMultiplexerInterfaceShim = null; } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Checking {DbConnection} health at {Host}.")] + private partial void LogCheckingHealth(string dbConnection, string host); + + [LoggerMessage(Level = LogLevel.Trace, Message = "{DbConnection} at {Host} is up.")] + private partial void LogHealthUp(string dbConnection, string host); + + [LoggerMessage(Level = LogLevel.Error, Message = "{DbConnection} at {Host} is down.")] + private partial void LogHealthDown(Exception exception, string dbConnection, string host); } diff --git a/src/Connectors/src/Connectors/RelationalDatabaseHealthContributor.cs b/src/Connectors/src/Connectors/RelationalDatabaseHealthContributor.cs index 04b1773a41..a2fcfd0dc7 100644 --- a/src/Connectors/src/Connectors/RelationalDatabaseHealthContributor.cs +++ b/src/Connectors/src/Connectors/RelationalDatabaseHealthContributor.cs @@ -10,7 +10,7 @@ namespace Steeltoe.Connectors; -internal sealed class RelationalDatabaseHealthContributor : IHealthContributor, IDisposable +internal sealed partial class RelationalDatabaseHealthContributor : IHealthContributor, IDisposable { private readonly DbConnection _connection; private readonly ILogger _logger; @@ -32,7 +32,7 @@ public RelationalDatabaseHealthContributor(DbConnection connection, string? host public async Task CheckHealthAsync(CancellationToken cancellationToken) { - _logger.LogTrace("Checking {DbConnection} health at {Host}", Id, Host); + LogCheckingHealth(Id, Host); var result = new HealthCheckResult { @@ -56,7 +56,7 @@ public RelationalDatabaseHealthContributor(DbConnection connection, string? host result.Status = HealthStatus.Up; - _logger.LogTrace("{DbConnection} at {Host} is up!", Id, Host); + LogHealthUp(Id, Host); } catch (Exception exception) { @@ -67,7 +67,7 @@ public RelationalDatabaseHealthContributor(DbConnection connection, string? host ExceptionDispatchInfo.Capture(exception).Throw(); } - _logger.LogError(exception, "{DbConnection} at {Host} is down!", Id, Host); + LogHealthDown(exception, Id, Host); result.Status = HealthStatus.Down; result.Description = $"{Id} health check failed"; @@ -96,4 +96,13 @@ public void Dispose() { _connection.Dispose(); } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Checking {DbConnection} health at {Host}.")] + private partial void LogCheckingHealth(string dbConnection, string host); + + [LoggerMessage(Level = LogLevel.Trace, Message = "{DbConnection} at {Host} is up.")] + private partial void LogHealthUp(string dbConnection, string host); + + [LoggerMessage(Level = LogLevel.Error, Message = "{DbConnection} at {Host} is down.")] + private partial void LogHealthDown(Exception exception, string dbConnection, string host); } diff --git a/src/Connectors/src/EntityFrameworkCore/MigrateDbContextTask.cs b/src/Connectors/src/EntityFrameworkCore/MigrateDbContextTask.cs index c4b899b49e..c127054e38 100644 --- a/src/Connectors/src/EntityFrameworkCore/MigrateDbContextTask.cs +++ b/src/Connectors/src/EntityFrameworkCore/MigrateDbContextTask.cs @@ -18,7 +18,7 @@ namespace Steeltoe.Connectors.EntityFrameworkCore; /// /// The to run migrations from. /// -public sealed class MigrateDbContextTask : IApplicationTask +public sealed partial class MigrateDbContextTask : IApplicationTask where TDbContext : DbContext { public const string Name = "migrate"; @@ -50,7 +50,7 @@ public async Task RunAsync(CancellationToken cancellationToken) isNewDatabase = true; } - _logger.LogInformation("Starting database migration..."); + LogStartingMigration(); await _dbContext.Database.MigrateAsync(cancellationToken); if (isNewDatabase) @@ -60,12 +60,21 @@ public async Task RunAsync(CancellationToken cancellationToken) if (migrations.Length > 0) { - string migrationNames = string.Join(", ", migrations); - _logger.LogInformation("The following migrations have been successfully applied: {MigrationNames}.", migrationNames); + string migrationNames = string.Join(", ", migrations.Select(migration => $"'{migration}'")); + LogMigrationsApplied(migrationNames); } else { - _logger.LogInformation("Database is already up to date."); + LogAlreadyUpToDate(); } } + + [LoggerMessage(Level = LogLevel.Information, Message = "Starting database migration.")] + private partial void LogStartingMigration(); + + [LoggerMessage(Level = LogLevel.Information, Message = "The following migrations have been successfully applied: {MigrationNames}.")] + private partial void LogMigrationsApplied(string migrationNames); + + [LoggerMessage(Level = LogLevel.Information, Message = "Database is already up to date.")] + private partial void LogAlreadyUpToDate(); } diff --git a/src/Discovery/src/Consul/PeriodicHeartbeat.cs b/src/Discovery/src/Consul/PeriodicHeartbeat.cs index 66a6379932..95d4c3c398 100644 --- a/src/Discovery/src/Consul/PeriodicHeartbeat.cs +++ b/src/Discovery/src/Consul/PeriodicHeartbeat.cs @@ -5,10 +5,11 @@ using Consul; using Microsoft.Extensions.Logging; using Steeltoe.Common.Extensions; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Steeltoe.Discovery.Consul; -internal sealed class PeriodicHeartbeat : IAsyncDisposable +internal sealed partial class PeriodicHeartbeat : IAsyncDisposable { private readonly string _serviceId; private readonly PeriodicTimer _periodicTimer; @@ -37,11 +38,11 @@ private async Task TimerLoopAsync() { try { - _logger.LogDebug("Start sending periodic Consul heartbeats for '{ServiceId}' with interval {Interval}.", _serviceId, Interval); + LogStartSendingHeartbeats(_serviceId, Interval); while (await _periodicTimer.WaitForNextTickAsync(_cancellationTokenSource.Token)) { - _logger.LogDebug("Sending Consul heartbeat for '{ServiceId}'.", _serviceId); + LogSendingHeartbeat(_serviceId); try { @@ -49,16 +50,13 @@ private async Task TimerLoopAsync() } catch (Exception exception) when (!exception.IsCancellation()) { - _logger.LogError(exception, "Failed to send Consul heartbeat for '{ServiceId}'.", _serviceId); + LogFailedToSendHeartbeat(exception, _serviceId); } } } catch (OperationCanceledException) { -#pragma warning disable S6667 // Logging in a catch clause should pass the caught exception as a parameter. - // Justification: The exception contains no useful information. Logging it suggests something crashed, while this is expected behavior. - _logger.LogDebug("Stop sending periodic Consul heartbeats for '{ServiceId}'.", _serviceId); -#pragma warning restore S6667 // Logging in a catch clause should pass the caught exception as a parameter. + LogSendingHeartbeatsStopped(_serviceId); } } @@ -68,7 +66,7 @@ public void ChangeInterval(TimeSpan interval) { _periodicTimer.Period = interval; Interval = interval; - _logger.LogDebug("Periodic Consul heartbeat interval for '{ServiceId}' changed to {Interval}.", _serviceId, interval); + LogHeartbeatIntervalChanged(_serviceId, interval); } } @@ -80,4 +78,19 @@ public async ValueTask DisposeAsync() _task.Dispose(); _periodicTimer.Dispose(); } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Start sending periodic Consul heartbeats for '{ServiceId}' with interval {Interval}.")] + private partial void LogStartSendingHeartbeats(string serviceId, TimeSpan interval); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Sending Consul heartbeat for '{ServiceId}'.")] + private partial void LogSendingHeartbeat(string serviceId); + + [LoggerMessage(Level = LogLevel.Error, Message = "Failed to send Consul heartbeat for '{ServiceId}'.")] + private partial void LogFailedToSendHeartbeat(Exception exception, string serviceId); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Stopped sending periodic Consul heartbeats for '{ServiceId}'.")] + private partial void LogSendingHeartbeatsStopped(string serviceId); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Periodic Consul heartbeat interval for '{ServiceId}' changed to {Interval}.")] + private partial void LogHeartbeatIntervalChanged(string serviceId, TimeSpan interval); } diff --git a/src/Discovery/src/Consul/Registry/ConsulServiceRegistrar.cs b/src/Discovery/src/Consul/Registry/ConsulServiceRegistrar.cs index e7d264f9a2..ef0d9d392e 100644 --- a/src/Discovery/src/Consul/Registry/ConsulServiceRegistrar.cs +++ b/src/Discovery/src/Consul/Registry/ConsulServiceRegistrar.cs @@ -13,7 +13,7 @@ namespace Steeltoe.Discovery.Consul.Registry; /// /// A registrar used to register a service in a Consul server. /// -internal sealed class ConsulServiceRegistrar : IAsyncDisposable +internal sealed partial class ConsulServiceRegistrar : IAsyncDisposable { private const int NotRunning = 0; private const int Running = 1; @@ -73,7 +73,7 @@ public async Task StartAsync(CancellationToken cancellationToken) { if (!Options.Enabled) { - _logger.LogDebug("Consul discovery client is turned off."); + LogDiscoveryClientTurnedOff(); return; } @@ -94,7 +94,7 @@ private async Task RegisterAsync(CancellationToken cancellationToken) { if (!Options.Register) { - _logger.LogDebug("Consul registration is turned off."); + LogRegistrationTurnedOff(); return; } @@ -105,7 +105,7 @@ private async Task DeregisterAsync(CancellationToken cancellationToken) { if (!Options.Register || !Options.Deregister) { - _logger.LogDebug("Consul deregistration is turned off."); + LogDeregistrationTurnedOff(); return; } @@ -116,7 +116,7 @@ private async Task DoWithRetryAsync(Func retryable, Con { ArgumentNullException.ThrowIfNull(retryable); - _logger.LogDebug("Starting retryable action."); + LogStartingRetryableAction(); int attempts = 0; int backOff = options.InitialInterval; @@ -126,7 +126,7 @@ private async Task DoWithRetryAsync(Func retryable, Con try { await retryable(cancellationToken); - _logger.LogDebug("Finished retryable action."); + LogRetryableActionFinished(); return; } catch (Exception exception) when (!exception.IsCancellation()) @@ -135,14 +135,14 @@ private async Task DoWithRetryAsync(Func retryable, Con if (attempts < options.MaxAttempts) { - _logger.LogError(exception, "Exception during {Attempt} attempts of retryable action, will retry", attempts); + LogStartingRetry(exception, attempts); Thread.CurrentThread.Join(backOff); int nextBackOff = (int)(backOff * options.Multiplier); backOff = Math.Min(nextBackOff, options.MaxInterval); } else { - _logger.LogError(exception, "Exception during {Attempt} attempts of retryable action, done with retry", attempts); + LogRetryFailed(exception, attempts); throw; } } @@ -164,4 +164,25 @@ public async ValueTask DisposeAsync() _isDisposed = true; } } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Consul discovery client is turned off.")] + private partial void LogDiscoveryClientTurnedOff(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Consul registration is turned off.")] + private partial void LogRegistrationTurnedOff(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Consul deregistration is turned off.")] + private partial void LogDeregistrationTurnedOff(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Starting retryable action.")] + private partial void LogStartingRetryableAction(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Finished retryable action.")] + private partial void LogRetryableActionFinished(); + + [LoggerMessage(Level = LogLevel.Error, Message = "Exception during {Attempt} attempts of retryable action, will retry.")] + private partial void LogStartingRetry(Exception exception, int attempt); + + [LoggerMessage(Level = LogLevel.Error, Message = "Exception during {Attempt} attempts of retryable action, done with retries.")] + private partial void LogRetryFailed(Exception exception, int attempt); } diff --git a/src/Discovery/src/Consul/Registry/ConsulServiceRegistry.cs b/src/Discovery/src/Consul/Registry/ConsulServiceRegistry.cs index 2809e500ee..a18b270df8 100644 --- a/src/Discovery/src/Consul/Registry/ConsulServiceRegistry.cs +++ b/src/Discovery/src/Consul/Registry/ConsulServiceRegistry.cs @@ -8,13 +8,14 @@ using Microsoft.Extensions.Options; using Steeltoe.Common.Extensions; using Steeltoe.Discovery.Consul.Configuration; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Steeltoe.Discovery.Consul.Registry; /// /// A service registry that uses Consul. /// -internal sealed class ConsulServiceRegistry : IAsyncDisposable +internal sealed partial class ConsulServiceRegistry : IAsyncDisposable { private const string Up = "UP"; private const string OutOfService = "OUT_OF_SERVICE"; @@ -67,7 +68,7 @@ public async Task RegisterAsync(ConsulRegistration registration, CancellationTok { ArgumentNullException.ThrowIfNull(registration); - _logger.LogInformation("Registering service {ServiceId} with Consul.", registration.ServiceId); + LogRegistering(registration.ServiceId); try { @@ -82,11 +83,11 @@ public async Task RegisterAsync(ConsulRegistration registration, CancellationTok { if (Options.FailFast) { - _logger.LogError(exception, "Error registering service {ServiceId} with Consul.", registration.ServiceId); + LogRegisterFailed(exception, registration.ServiceId); throw; } - _logger.LogWarning(exception, "FailFast is false. Error registering service {ServiceId} with Consul.", registration.ServiceId); + LogWarnForRegisterFailed(exception, registration.ServiceId); } } @@ -110,12 +111,12 @@ public async Task DeregisterAsync(ConsulRegistration registration, CancellationT await _scheduler.RemoveAsync(registration.InstanceId); } - _logger.LogInformation("Deregistering service {InstanceId} with Consul.", registration.InstanceId); + LogDeregistering(registration.InstanceId); await _client.Agent.ServiceDeregister(registration.InstanceId, cancellationToken); } catch (Exception exception) when (!exception.IsCancellation()) { - _logger.LogError(exception, "Error deregistering service {ServiceId} with Consul.", registration.ServiceId); + LogDeregisterFailed(exception, registration.ServiceId); } } @@ -184,4 +185,19 @@ public async ValueTask DisposeAsync() await _scheduler.DisposeAsync(); } } + + [LoggerMessage(Level = LogLevel.Information, Message = "Registering service {ServiceId} with Consul.")] + private partial void LogRegistering(string serviceId); + + [LoggerMessage(Level = LogLevel.Error, Message = "Error registering service {ServiceId} with Consul.")] + private partial void LogRegisterFailed(Exception exception, string serviceId); + + [LoggerMessage(Level = LogLevel.Warning, Message = "FailFast is false. Error registering service {ServiceId} with Consul.")] + private partial void LogWarnForRegisterFailed(Exception exception, string serviceId); + + [LoggerMessage(Level = LogLevel.Information, Message = "Deregistering service {InstanceId} with Consul.")] + private partial void LogDeregistering(string instanceId); + + [LoggerMessage(Level = LogLevel.Error, Message = "Error deregistering service {ServiceId} with Consul.")] + private partial void LogDeregisterFailed(Exception exception, string serviceId); } diff --git a/src/Discovery/src/Consul/TtlScheduler.cs b/src/Discovery/src/Consul/TtlScheduler.cs index 972a06342f..792c5a3a0d 100644 --- a/src/Discovery/src/Consul/TtlScheduler.cs +++ b/src/Discovery/src/Consul/TtlScheduler.cs @@ -8,13 +8,14 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Steeltoe.Discovery.Consul.Configuration; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Steeltoe.Discovery.Consul; /// /// Scheduler used to issue TTL (time-to-live) requests to the Consul server. /// -internal sealed class TtlScheduler : IAsyncDisposable +internal sealed partial class TtlScheduler : IAsyncDisposable { private const string InstancePrefix = "service:"; @@ -80,7 +81,7 @@ public void Add(string instanceId) private void AddOrUpdate(string instanceId, ConsulHeartbeatOptions heartbeatOptions) { - _schedulerLogger.LogDebug("Adding/updating instance '{InstanceId}'.", instanceId); + LogAddingOrUpdatingInstance(_schedulerLogger, instanceId); TimeSpan interval = heartbeatOptions.ComputeHeartbeatInterval(); string checkId = instanceId; @@ -109,7 +110,7 @@ public async Task RemoveAsync(string instanceId) if (ServiceHeartbeats.TryRemove(instanceId, out PeriodicHeartbeat? heartbeat)) { - _schedulerLogger.LogDebug("Removing instance '{InstanceId}'.", instanceId); + LogRemovingInstance(_schedulerLogger, instanceId); await heartbeat.DisposeAsync(); } } @@ -130,4 +131,10 @@ public async ValueTask DisposeAsync() _isDisposed = true; } } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Adding/updating instance '{InstanceId}'.")] + private static partial void LogAddingOrUpdatingInstance(ILogger logger, string instanceId); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Removing instance '{InstanceId}'.")] + private static partial void LogRemovingInstance(ILogger logger, string instanceId); } diff --git a/src/Discovery/src/Eureka/AppInfo/ApplicationInfo.cs b/src/Discovery/src/Eureka/AppInfo/ApplicationInfo.cs index 8417407d59..df11be4347 100644 --- a/src/Discovery/src/Eureka/AppInfo/ApplicationInfo.cs +++ b/src/Discovery/src/Eureka/AppInfo/ApplicationInfo.cs @@ -17,7 +17,7 @@ public sealed class ApplicationInfo private readonly ConcurrentDictionary _instanceMap = new(); public string Name { get; } - public IReadOnlyList Instances => new List(_instanceMap.Values); + public IReadOnlyList Instances => GetInstancesSnapshot(); internal ApplicationInfo(string name) : this(name, Array.Empty()) @@ -38,6 +38,11 @@ internal ApplicationInfo(string name, ICollection instances) } } + private List GetInstancesSnapshot() + { + return [.. _instanceMap.Values]; + } + internal InstanceInfo? GetInstance(string instanceId) { ArgumentException.ThrowIfNullOrWhiteSpace(instanceId); diff --git a/src/Discovery/src/Eureka/Configuration/EurekaInstanceOptions.cs b/src/Discovery/src/Eureka/Configuration/EurekaInstanceOptions.cs index ef18b9fb65..08e0f3f51e 100644 --- a/src/Discovery/src/Eureka/Configuration/EurekaInstanceOptions.cs +++ b/src/Discovery/src/Eureka/Configuration/EurekaInstanceOptions.cs @@ -14,7 +14,7 @@ namespace Steeltoe.Discovery.Eureka.Configuration; -public sealed class EurekaInstanceOptions +public sealed partial class EurekaInstanceOptions { internal const string ConfigurationPrefix = "eureka:instance"; internal const string DefaultStatusPageUrlPath = "/info"; @@ -257,14 +257,13 @@ internal void SetPortsFromListenAddresses(IEnumerable listenOnAddresses, { if (listenHttpPort != null) { - // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression if (nonSecurePort == null) { - logger.LogDebug("Activating non-secure port {NonSecurePort} from {Source}.", listenHttpPort, source); + LogActivatingNonSecurePort(logger, listenHttpPort, source); } else { - logger.LogDebug("Changing non-secure port to {NonSecurePort} from {Source}.", listenHttpPort, source); + LogChangingNonSecurePort(logger, listenHttpPort, source); } NonSecurePort = listenHttpPort.Value; @@ -272,7 +271,7 @@ internal void SetPortsFromListenAddresses(IEnumerable listenOnAddresses, } else if (nonSecurePort != null) { - logger.LogDebug("Deactivating non-secure port from {Source}.", source); + LogDeactivatingNonSecurePort(logger, source); IsNonSecurePortEnabled = false; } } @@ -281,14 +280,13 @@ internal void SetPortsFromListenAddresses(IEnumerable listenOnAddresses, { if (listenHttpsPort != null) { - // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression if (securePort == null) { - logger.LogDebug("Activating secure port {SecurePort} from {Source}.", listenHttpsPort, source); + LogActivatingSecurePort(logger, listenHttpsPort, source); } else { - logger.LogDebug("Changing secure port to {SecurePort} from {Source}.", listenHttpsPort, source); + LogChangingSecurePort(logger, listenHttpsPort, source); } SecurePort = listenHttpsPort.Value; @@ -296,10 +294,28 @@ internal void SetPortsFromListenAddresses(IEnumerable listenOnAddresses, } else if (securePort != null) { - logger.LogDebug("Deactivating secure port from {Source}.", source); + LogDeactivatingSecurePort(logger, source); IsSecurePortEnabled = false; } } } } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Activating non-secure port {NonSecurePort} from {Source}.")] + private static partial void LogActivatingNonSecurePort(ILogger logger, int? nonSecurePort, string source); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Changing non-secure port to {NonSecurePort} from {Source}.")] + private static partial void LogChangingNonSecurePort(ILogger logger, int? nonSecurePort, string source); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Deactivating non-secure port from {Source}.")] + private static partial void LogDeactivatingNonSecurePort(ILogger logger, string source); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Activating secure port {SecurePort} from {Source}.")] + private static partial void LogActivatingSecurePort(ILogger logger, int? securePort, string source); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Changing secure port to {SecurePort} from {Source}.")] + private static partial void LogChangingSecurePort(ILogger logger, int? securePort, string source); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Deactivating secure port from {Source}.")] + private static partial void LogDeactivatingSecurePort(ILogger logger, string source); } diff --git a/src/Discovery/src/Eureka/EurekaApplicationInfoManager.cs b/src/Discovery/src/Eureka/EurekaApplicationInfoManager.cs index d76ae11a4b..3bf7b05b3c 100644 --- a/src/Discovery/src/Eureka/EurekaApplicationInfoManager.cs +++ b/src/Discovery/src/Eureka/EurekaApplicationInfoManager.cs @@ -19,7 +19,7 @@ namespace Steeltoe.Discovery.Eureka; /// /// Provides access to the Eureka instance that represents the currently running application. /// -public sealed class EurekaApplicationInfoManager : IDisposable +public sealed partial class EurekaApplicationInfoManager : IDisposable { private readonly IOptionsMonitor _clientOptionsMonitor; private readonly IOptionsMonitor _instanceOptionsMonitor; @@ -69,7 +69,7 @@ public EurekaApplicationInfoManager(IOptionsMonitor clientO private void HandleInstanceOptionsChanged(EurekaInstanceOptions instanceOptions) { - _logger.LogDebug("Responding to changed configuration."); + LogRespondingToChangedConfiguration(); try { @@ -77,7 +77,7 @@ private void HandleInstanceOptionsChanged(EurekaInstanceOptions instanceOptions) } catch (Exception exception) { - _logger.LogError(exception, "Failed to update Eureka instance from changed configuration."); + LogFailedToUpdateInstance(exception); } } @@ -121,7 +121,7 @@ private void InnerUpdateInstance(EurekaInstanceOptions newInstanceOptions, bool } catch (Exception exception) { - _logger.LogError(exception, "Failed to adapt to configuration changes. Discarding updated configuration."); + LogFailedToAdaptConfiguration(exception); newInstance = previousInstance; } @@ -143,13 +143,13 @@ private void InnerUpdateInstance(EurekaInstanceOptions newInstanceOptions, bool if (newInstance.IsDirty) { - _logger.LogDebug("Instance has changed."); + LogInstanceHasChanged(); _instance = newInstance; eventArgs = new InstanceChangedEventArgs(newInstance, previousInstance); } else { - _logger.LogDebug("Instance has not changed."); + LogInstanceHasNotChanged(); } } @@ -164,14 +164,14 @@ private InstanceInfo MergeInstanceWithConfiguration(EurekaInstanceOptions instan if (instanceOptions.InstanceId != previousInstance.InstanceId) { // A change of InstanceId would require unregister, then re-register. - _logger.LogWarning("Discarding change of InstanceId, which is not supported."); + LogDiscardingInstanceIdChange(); instanceOptions.InstanceId = previousInstance.InstanceId; } if (!string.Equals(instanceOptions.AppName, previousInstance.AppName, StringComparison.OrdinalIgnoreCase)) { // A change of AppName would require unregister, then re-register. - _logger.LogWarning("Discarding change of AppName, which is not supported."); + LogDiscardingAppNameChange(); instanceOptions.AppName = previousInstance.AppName; } @@ -198,4 +198,25 @@ public void Dispose() { _instanceOptionsChangeToken?.Dispose(); } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Responding to changed configuration.")] + private partial void LogRespondingToChangedConfiguration(); + + [LoggerMessage(Level = LogLevel.Error, Message = "Failed to update Eureka instance from changed configuration.")] + private partial void LogFailedToUpdateInstance(Exception exception); + + [LoggerMessage(Level = LogLevel.Error, Message = "Failed to adapt to configuration changes. Discarding updated configuration.")] + private partial void LogFailedToAdaptConfiguration(Exception exception); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Instance has changed.")] + private partial void LogInstanceHasChanged(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Instance has not changed.")] + private partial void LogInstanceHasNotChanged(); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Discarding change of InstanceId, which is not supported.")] + private partial void LogDiscardingInstanceIdChange(); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Discarding change of AppName, which is not supported.")] + private partial void LogDiscardingAppNameChange(); } diff --git a/src/Discovery/src/Eureka/EurekaClient.cs b/src/Discovery/src/Eureka/EurekaClient.cs index 77d73754b5..47fa8b7de6 100644 --- a/src/Discovery/src/Eureka/EurekaClient.cs +++ b/src/Discovery/src/Eureka/EurekaClient.cs @@ -25,7 +25,7 @@ namespace Steeltoe.Discovery.Eureka; /// /// Sends HTTP requests to Eureka servers. /// -public sealed class EurekaClient +public sealed partial class EurekaClient { // HTTP endpoints are described at: https://github.com/Netflix/eureka/wiki/Eureka-REST-operations // Self preservation is described at: https://www.baeldung.com/eureka-self-preservation-renewal @@ -92,8 +92,7 @@ public async Task RegisterAsync(InstanceInfo instance, CancellationToken cancell if ((Platform.IsContainerized || Platform.IsCloudHosted) && string.Equals(instance.HostName, "localhost", StringComparison.OrdinalIgnoreCase)) { - _logger.LogWarning("Registering with hostname 'localhost' in containerized or cloud environments may not be valid. " + - "Please configure Eureka:Instance:HostName with a non-localhost address."); + LogHostNamePotentiallyInvalid(); } string requestBody = JsonSerializer.Serialize(new JsonInstanceInfoRoot @@ -257,20 +256,18 @@ private async Task ExecuteRequestAsync(HttpMethod method, stri if (!string.IsNullOrEmpty(requestBody)) { - _logger.LogDebug("Sending {RequestMethod} request to '{RequestUri}' with body: {RequestBody}.", request.Method, requestUri.ToMaskedString(), - requestBody); + LogSendingRequestWithBody(request.Method, requestUri.ToMaskedString(), requestBody); } else { - _logger.LogDebug("Sending {RequestMethod} request to '{RequestUri}' without request body.", request.Method, requestUri.ToMaskedString()); + LogSendingRequestWithoutBody(request.Method, requestUri.ToMaskedString()); } try { using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken); - _logger.LogDebug("HTTP {RequestMethod} request to '{RequestUri}' returned status {StatusCode} in attempt {Attempt}.", request.Method, - requestUri.ToMaskedString(), (int)response.StatusCode, attempt); + LogRequestReturnedStatus(request.Method, requestUri.ToMaskedString(), (int)response.StatusCode, attempt); if (response.IsSuccessStatusCode) { @@ -282,22 +279,19 @@ private async Task ExecuteRequestAsync(HttpMethod method, stri } catch (JsonException exception) when (!exception.IsCancellation()) { - _logger.LogDebug(exception, "Failed to deserialize HTTP response from {RequestMethod} '{RequestUri}'.", request.Method, - requestUri.ToMaskedString()); + LogFailedToDeserializeResponse(exception, request.Method, requestUri.ToMaskedString()); } } else { string responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - _logger.LogInformation("HTTP {RequestMethod} request to '{RequestUri}' failed with status {StatusCode}: {ResponseBody}", request.Method, - requestUri.ToMaskedString(), (int)response.StatusCode, responseBody); + LogRequestFailed(request.Method, requestUri.ToMaskedString(), (int)response.StatusCode, responseBody); } } catch (Exception exception) when (!exception.IsCancellation()) { - _logger.LogWarning(exception, "Failed to execute HTTP {RequestMethod} request to '{RequestUri}' in attempt {Attempt}.", request.Method, - requestUri.ToMaskedString(), attempt); + LogAttemptFailed(exception, request.Method, requestUri.ToMaskedString(), attempt); } _eurekaServiceUriStateManager.MarkFailingServiceUri(serviceUri); @@ -335,7 +329,7 @@ private async Task GetRequestMessageAsync(HttpMethod method, if (requestUri.TryGetUsernamePassword(out string? username, out string? password) && password.Length > 0) { - _logger.LogDebug("Adding credentials from '{RequestUri}' to Authorization header.", requestUri.ToMaskedString()); + LogAddingCredentials(requestUri.ToMaskedString()); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}"))); @@ -352,7 +346,7 @@ private async Task GetRequestMessageAsync(HttpMethod method, string accessToken = await httpClient.GetAccessTokenAsync(accessTokenUri, clientOptions.ClientId, clientOptions.ClientSecret, cancellationToken); - _logger.LogDebug("Fetched access token from '{AccessTokenUri}'.", accessTokenUri.ToMaskedString()); + LogAccessTokenFetched(accessTokenUri.ToMaskedString()); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); } } @@ -364,4 +358,33 @@ private async Task GetRequestMessageAsync(HttpMethod method, return requestMessage; } + + [LoggerMessage(Level = LogLevel.Warning, + Message = "Registering with hostname 'localhost' in containerized or cloud environments may not be valid. " + + "Please configure Eureka:Instance:HostName with a non-localhost address.")] + private partial void LogHostNamePotentiallyInvalid(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Sending {RequestMethod} request to '{RequestUri}' with body: '{RequestBody}'.")] + private partial void LogSendingRequestWithBody(HttpMethod requestMethod, string requestUri, string? requestBody); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Sending {RequestMethod} request to '{RequestUri}' without request body.")] + private partial void LogSendingRequestWithoutBody(HttpMethod requestMethod, string requestUri); + + [LoggerMessage(Level = LogLevel.Debug, Message = "HTTP {RequestMethod} request to '{RequestUri}' returned status {StatusCode} in attempt {Attempt}.")] + private partial void LogRequestReturnedStatus(HttpMethod requestMethod, string requestUri, int statusCode, int attempt); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Failed to deserialize HTTP response from {RequestMethod} '{RequestUri}'.")] + private partial void LogFailedToDeserializeResponse(Exception exception, HttpMethod requestMethod, string requestUri); + + [LoggerMessage(Level = LogLevel.Information, Message = "HTTP {RequestMethod} request to '{RequestUri}' failed with status {StatusCode}: '{ResponseBody}'.")] + private partial void LogRequestFailed(HttpMethod requestMethod, string requestUri, int statusCode, string responseBody); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Failed to execute HTTP {RequestMethod} request to '{RequestUri}' in attempt {Attempt}.")] + private partial void LogAttemptFailed(Exception exception, HttpMethod requestMethod, string requestUri, int attempt); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Adding credentials from '{RequestUri}' to Authorization header.")] + private partial void LogAddingCredentials(string requestUri); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Fetched access token from '{AccessTokenUri}'.")] + private partial void LogAccessTokenFetched(string accessTokenUri); } diff --git a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs index dbb50b9480..ac6852233d 100644 --- a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs +++ b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs @@ -23,7 +23,7 @@ namespace Steeltoe.Discovery.Eureka; /// /// . /// -public sealed class EurekaDiscoveryClient : IDiscoveryClient +public sealed partial class EurekaDiscoveryClient : IDiscoveryClient { private readonly EurekaApplicationInfoManager _appInfoManager; private readonly EurekaClient _eurekaClient; @@ -115,10 +115,10 @@ public EurekaDiscoveryClient(EurekaApplicationInfoManager appInfoManager, Eureka } catch (Exception exception) { - _logger.LogInformation(exception, "Initial registration failed."); + LogInitialRegistrationFailed(exception); } - _logger.LogInformation("Starting heartbeat timer."); + LogStartingHeartbeatTimer(); _heartbeatTimer = StartTimer(leaseRenewalInterval.Value, HeartbeatAsyncTask); _appInfoManager.InstanceChanged += AppInfoManagerOnInstanceChanged; @@ -136,10 +136,10 @@ public EurekaDiscoveryClient(EurekaApplicationInfoManager appInfoManager, Eureka } catch (Exception exception) { - _logger.LogInformation(exception, "Initial fetch registry failed."); + LogInitialFetchRegistryFailed(exception); } - _logger.LogInformation("Starting applications cache refresh timer."); + LogStartingCacheRefreshTimer(); _cacheRefreshTimer = StartTimer(clientOptions.RegistryFetchInterval, CacheRefreshAsyncTask); _clientOptionsChangeToken = _clientOptionsMonitor.OnChange(options => @@ -198,7 +198,7 @@ public async Task ShutdownAsync(CancellationToken cancellationToken) } catch (Exception exception) when (!exception.IsCancellation()) { - _logger.LogWarning(exception, "Deregister failed during shutdown."); + LogDeregisterFailedDuringShutdown(exception); } _appInfoManager.Dispose(); @@ -216,7 +216,7 @@ private async void AppInfoManagerOnInstanceChanged(object? sender, InstanceChang try { - _logger.LogDebug("Instance changed event handler: New={NewInstance}, Previous={PreviousInstance}", args.NewInstance, args.PreviousInstance); + LogInstanceChangedEvent(args.NewInstance, args.PreviousInstance); if (args.NewInstance.LeaseInfo?.RenewalInterval != args.PreviousInstance.LeaseInfo?.RenewalInterval) { @@ -230,7 +230,7 @@ private async void AppInfoManagerOnInstanceChanged(object? sender, InstanceChang { if (!exception.IsCancellation()) { - _logger.LogError(exception, "Failed to handle {EventName} event.", nameof(EurekaApplicationInfoManager.InstanceChanged)); + LogFailedToHandleEvent(exception, nameof(EurekaApplicationInfoManager.InstanceChanged)); } } } @@ -259,9 +259,9 @@ internal async Task RegisterAsync(bool requireDirtyInstance, CancellationToken c if (!requireDirtyInstance || snapshot.IsDirty) { - _logger.LogDebug("Registering {Application}/{Instance}.", snapshot.AppName, snapshot.InstanceId); + LogRegistering(snapshot.AppName, snapshot.InstanceId); await _eurekaClient.RegisterAsync(snapshot, cancellationToken); - _logger.LogDebug("Register {Application}/{Instance} succeeded.", snapshot.AppName, snapshot.InstanceId); + LogRegistrationSucceeded(snapshot.AppName, snapshot.InstanceId); snapshot.IsDirty = false; } @@ -280,9 +280,9 @@ internal async Task DeregisterAsync(CancellationToken cancellationToken) { InstanceInfo snapshot = _appInfoManager.Instance; - _logger.LogDebug("Deregistering {Application}/{Instance}.", snapshot.AppName, snapshot.InstanceId); + LogDeregistering(snapshot.AppName, snapshot.InstanceId); await _eurekaClient.DeregisterAsync(snapshot.AppName, snapshot.InstanceId, cancellationToken); - _logger.LogDebug("Deregister {Application}/{Instance} succeeded.", snapshot.AppName, snapshot.InstanceId); + LogDeregistrationSucceeded(snapshot.AppName, snapshot.InstanceId); } finally { @@ -299,16 +299,15 @@ internal async Task RenewAsync(CancellationToken cancellationToken) { InstanceInfo snapshot = _appInfoManager.Instance; - _logger.LogDebug("Sending heartbeat for {Application}/{Instance}.", snapshot.AppName, snapshot.InstanceId); + LogSendingHeartbeat(snapshot.AppName, snapshot.InstanceId); await _eurekaClient.HeartbeatAsync(snapshot.AppName, snapshot.InstanceId, snapshot.LastDirtyTimeUtc, cancellationToken); - _logger.LogDebug("Heartbeat for {Application}/{Instance} succeeded.", snapshot.AppName, snapshot.InstanceId); + LogHeartbeatSucceeded(snapshot.AppName, snapshot.InstanceId); _lastGoodHeartbeatTimeUtc = new NullableValueWrapper(_timeProvider.GetUtcNow().UtcDateTime); } catch (EurekaTransportException exception) { - _logger.LogWarning(exception, - "Eureka heartbeat failed. This could happen if Eureka was offline during app startup. Attempting to (re)register now."); + LogHeartbeatFailed(exception); await RegisterAsync(false, cancellationToken); } @@ -368,7 +367,7 @@ private void OnApplicationsFetched(ApplicationsFetchedEventArgs? args) } catch (Exception exception) { - _logger.LogError(exception, "Failed to handle {EventName} event.", nameof(ApplicationsFetched)); + LogFailedToHandleEvent(exception, nameof(ApplicationsFetched)); } }); } @@ -378,13 +377,13 @@ internal async Task FetchFullRegistryAsync(Cancellati { EurekaClientOptions clientOptions = _clientOptionsMonitor.CurrentValue; - _logger.LogDebug("Sending request to fetch applications."); + LogFetchingApplications(); ApplicationInfoCollection applications = string.IsNullOrWhiteSpace(clientOptions.RegistryRefreshSingleVipAddress) ? await _eurekaClient.GetApplicationsAsync(cancellationToken) : await _eurekaClient.GetByVipAsync(clientOptions.RegistryRefreshSingleVipAddress, cancellationToken); - _logger.LogDebug("Full registry fetch succeeded with {Count} applications.", applications.Count); + LogFullRegistryFetchSucceeded(applications.Count); return applications; } @@ -394,29 +393,28 @@ internal async Task FetchRegistryDeltaAsync(Cancellat try { - _logger.LogDebug("Sending request to fetch applications delta."); + LogFetchingApplicationsDelta(); delta = await _eurekaClient.GetDeltaAsync(cancellationToken); } catch (EurekaTransportException exception) { - _logger.LogDebug(exception, "Failed to fetch registry delta. Trying full fetch."); + LogFailedToFetchDelta(exception); return await FetchFullRegistryAsync(cancellationToken); } - _logger.LogDebug("Registry delta fetched, updating local cache."); + LogRegistryDeltaFetched(); _remoteApps.UpdateFromDelta(delta); string hashCode = _remoteApps.ComputeHashCode(); if (hashCode != delta.AppsHashCode) { - _logger.LogWarning("Discarding fetched registry delta due to hash codes mismatch (Local={HashLocal}, Remote={HashRemote}). Trying full fetch.", - hashCode, delta.AppsHashCode); + LogDeltaHashCodeMismatch(hashCode, delta.AppsHashCode); return await FetchFullRegistryAsync(cancellationToken); } - _logger.LogDebug("Registry delta fetch succeeded with {Count} changes.", delta.Count); + LogDeltaFetchSucceeded(delta.Count); _remoteApps.AppsHashCode = delta.AppsHashCode; return _remoteApps; } @@ -427,26 +425,26 @@ internal async Task RunHealthChecksAsync(CancellationToken cancellationToken) { if (_appInfoManager.Instance.Status == InstanceStatus.Starting) { - _logger.LogDebug("Skipping health check handler in starting state."); + LogSkippingHealthCheck(); return; } try { InstanceStatus aggregatedStatus = await HealthCheckHandler.GetStatusAsync(_hasFirstHeartbeatCompleted, cancellationToken); - _logger.LogDebug("Health check handler returned status {Status}.", aggregatedStatus); + LogHealthCheckStatus(aggregatedStatus); InstanceInfo snapshot = _appInfoManager.Instance; if (aggregatedStatus != snapshot.Status) { - _logger.LogDebug("Changing instance status from {LocalStatus} to {RemoteStatus}.", snapshot.Status, aggregatedStatus); + LogChangingInstanceStatus(snapshot.Status, aggregatedStatus); _appInfoManager.UpdateStatusWithoutRaisingEvent(aggregatedStatus); } } catch (Exception exception) when (!exception.IsCancellation()) { - _logger.LogError(exception, "Failed to determine health status."); + LogFailedToDetermineHealthStatus(exception); } } } @@ -461,8 +459,7 @@ private void UpdateLastRemoteInstanceStatusFromCache() { if (remoteInstance.EffectiveStatus != snapshot.EffectiveStatus) { - _logger.LogWarning("Remote instance status {RemoteStatus} differs from local status {LocalStatus}.", remoteInstance.EffectiveStatus, - snapshot.EffectiveStatus); + LogRemoteStatusDiffers(remoteInstance.EffectiveStatus, snapshot.EffectiveStatus); } // We have ownership of the local instance, so don't take the remote status. @@ -486,7 +483,7 @@ private async void HeartbeatAsyncTask() { if (!exception.IsCancellation()) { - _logger.LogError(exception, "Periodic renew failed."); + LogPeriodicRenewFailed(exception); } } } @@ -507,7 +504,7 @@ private async void CacheRefreshAsyncTask() { if (!exception.IsCancellation()) { - _logger.LogError(exception, "Periodic fetch of applications failed."); + LogPeriodicFetchFailed(exception); } } } @@ -539,7 +536,7 @@ public Task> GetInstancesAsync(string serviceId, Cancell if (_logger.IsEnabled(LogLevel.Debug)) { string instanceNames = string.Join(", ", serviceInstances.Select(FormatServiceInstance)); - _logger.LogDebug("Returning {Count} service instances for '{ServiceId}': {ServiceInstances}", serviceInstances.Length, serviceId, instanceNames); + LogReturningServiceInstances(serviceInstances.Length, serviceId, instanceNames); } return Task.FromResult>(serviceInstances); @@ -572,4 +569,94 @@ public IServiceInstance GetLocalServiceInstance() { return new EurekaServiceInstance(_appInfoManager.Instance); } + + [LoggerMessage(Level = LogLevel.Information, Message = "Initial registration failed.")] + private partial void LogInitialRegistrationFailed(Exception exception); + + [LoggerMessage(Level = LogLevel.Information, Message = "Starting heartbeat timer.")] + private partial void LogStartingHeartbeatTimer(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Initial fetch registry failed.")] + private partial void LogInitialFetchRegistryFailed(Exception exception); + + [LoggerMessage(Level = LogLevel.Information, Message = "Starting applications cache refresh timer.")] + private partial void LogStartingCacheRefreshTimer(); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Deregister failed during shutdown.")] + private partial void LogDeregisterFailedDuringShutdown(Exception exception); + + [LoggerMessage(Level = LogLevel.Debug, + Message = "Instance changed event handler invoked with new instance {NewInstance} and previous instance {PreviousInstance}.")] + private partial void LogInstanceChangedEvent(InstanceInfo newInstance, InstanceInfo previousInstance); + + [LoggerMessage(Level = LogLevel.Error, Message = "Failed to handle {EventName} event.")] + private partial void LogFailedToHandleEvent(Exception exception, string eventName); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Registering {Application}/{Instance}.")] + private partial void LogRegistering(string application, string instance); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Register {Application}/{Instance} succeeded.")] + private partial void LogRegistrationSucceeded(string application, string instance); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Deregistering {Application}/{Instance}.")] + private partial void LogDeregistering(string application, string instance); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Deregister {Application}/{Instance} succeeded.")] + private partial void LogDeregistrationSucceeded(string application, string instance); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Sending heartbeat for {Application}/{Instance}.")] + private partial void LogSendingHeartbeat(string application, string instance); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Heartbeat for {Application}/{Instance} succeeded.")] + private partial void LogHeartbeatSucceeded(string application, string instance); + + [LoggerMessage(Level = LogLevel.Warning, + Message = "Eureka heartbeat failed. This could happen if Eureka was offline during app startup. Attempting to (re)register now.")] + private partial void LogHeartbeatFailed(Exception exception); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Sending request to fetch applications.")] + private partial void LogFetchingApplications(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Full registry fetch succeeded with {Count} applications.")] + private partial void LogFullRegistryFetchSucceeded(int count); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Sending request to fetch applications delta.")] + private partial void LogFetchingApplicationsDelta(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Failed to fetch registry delta. Trying full fetch.")] + private partial void LogFailedToFetchDelta(Exception exception); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Registry delta fetched, updating local cache.")] + private partial void LogRegistryDeltaFetched(); + + [LoggerMessage(Level = LogLevel.Warning, + Message = "Discarding fetched registry delta due to hash code mismatch between local {HashLocal} and remote {HashRemote}. Trying full fetch.")] + private partial void LogDeltaHashCodeMismatch(string hashLocal, string? hashRemote); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Registry delta fetch succeeded with {Count} changes.")] + private partial void LogDeltaFetchSucceeded(int count); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Skipping health check handler in starting state.")] + private partial void LogSkippingHealthCheck(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Health check handler returned status {Status}.")] + private partial void LogHealthCheckStatus(InstanceStatus status); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Changing instance status from {LocalStatus} to {RemoteStatus}.")] + private partial void LogChangingInstanceStatus(InstanceStatus? localStatus, InstanceStatus remoteStatus); + + [LoggerMessage(Level = LogLevel.Error, Message = "Failed to determine health status.")] + private partial void LogFailedToDetermineHealthStatus(Exception exception); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Remote instance status {RemoteStatus} differs from local status {LocalStatus}.")] + private partial void LogRemoteStatusDiffers(InstanceStatus remoteStatus, InstanceStatus localStatus); + + [LoggerMessage(Level = LogLevel.Error, Message = "Periodic renew failed.")] + private partial void LogPeriodicRenewFailed(Exception exception); + + [LoggerMessage(Level = LogLevel.Error, Message = "Periodic fetch of applications failed.")] + private partial void LogPeriodicFetchFailed(Exception exception); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Returning {Count} service instances for '{ServiceId}': {ServiceInstances}.")] + private partial void LogReturningServiceInstances(int count, string serviceId, string serviceInstances); } diff --git a/src/Discovery/src/Eureka/EurekaServiceUriStateManager.cs b/src/Discovery/src/Eureka/EurekaServiceUriStateManager.cs index 85b1113930..d1dfaeae0a 100644 --- a/src/Discovery/src/Eureka/EurekaServiceUriStateManager.cs +++ b/src/Discovery/src/Eureka/EurekaServiceUriStateManager.cs @@ -19,7 +19,7 @@ namespace Steeltoe.Discovery.Eureka; /// /// Keeps track of working and broken Eureka service URIs that are configured, with stickiness to the last working server. /// -public sealed class EurekaServiceUriStateManager +public sealed partial class EurekaServiceUriStateManager { private readonly IOptionsMonitor _optionsMonitor; private readonly ILogger _logger; @@ -62,7 +62,7 @@ private Uri[] GetAvailableServiceUris() if (_failedServiceUris.Count > 0 && _failedServiceUris.Count >= threshold) { - _logger.LogDebug("Clearing quarantined list of size {Count}.", _failedServiceUris.Count); + LogClearingQuarantinedList(_failedServiceUris.Count); _failedServiceUris.Clear(); } @@ -134,6 +134,9 @@ internal void MarkFailingServiceUri(Uri serviceUri) } } + [LoggerMessage(Level = LogLevel.Debug, Message = "Clearing quarantined list of size {Count}.")] + private partial void LogClearingQuarantinedList(int count); + /// /// Provides a method to sequentially try all available Eureka servers. /// diff --git a/src/Discovery/src/HttpClients/LoadBalancers/RandomLoadBalancer.cs b/src/Discovery/src/HttpClients/LoadBalancers/RandomLoadBalancer.cs index 82c6477d3b..5f9ca42822 100644 --- a/src/Discovery/src/HttpClients/LoadBalancers/RandomLoadBalancer.cs +++ b/src/Discovery/src/HttpClients/LoadBalancers/RandomLoadBalancer.cs @@ -11,7 +11,7 @@ namespace Steeltoe.Discovery.HttpClients.LoadBalancers; /// /// Returns random service instances. /// -public sealed class RandomLoadBalancer : ILoadBalancer +public sealed partial class RandomLoadBalancer : ILoadBalancer { private readonly ServiceInstancesResolver _serviceInstancesResolver; private readonly ILogger _logger; @@ -40,13 +40,13 @@ public async Task ResolveServiceInstanceAsync(Uri requestUri, CancellationT ArgumentNullException.ThrowIfNull(requestUri); string serviceName = requestUri.Host; - _logger.LogTrace("Resolving service instance for '{ServiceName}'.", serviceName); + LogResolvingServiceInstance(serviceName); IList availableServiceInstances = await _serviceInstancesResolver.ResolveInstancesAsync(serviceName, cancellationToken); if (availableServiceInstances.Count == 0) { - _logger.LogWarning("No service instances are available for '{ServiceName}'.", serviceName); + LogNoServiceInstances(serviceName); return requestUri; } @@ -54,7 +54,7 @@ public async Task ResolveServiceInstanceAsync(Uri requestUri, CancellationT int index = Random.Shared.Next(availableServiceInstances.Count); IServiceInstance serviceInstance = availableServiceInstances[index]; - _logger.LogDebug("Resolved '{ServiceName}' to '{ServiceInstance}'.", serviceName, serviceInstance.Uri); + LogServiceInstanceResolved(serviceName, serviceInstance.Uri); return new Uri(serviceInstance.Uri, requestUri.PathAndQuery); } @@ -64,4 +64,13 @@ public Task UpdateStatisticsAsync(Uri requestUri, Uri serviceInstanceUri, TimeSp cancellationToken.ThrowIfCancellationRequested(); return Task.CompletedTask; } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Resolving service instance for '{ServiceName}'.")] + private partial void LogResolvingServiceInstance(string serviceName); + + [LoggerMessage(Level = LogLevel.Warning, Message = "No service instances are available for '{ServiceName}'.")] + private partial void LogNoServiceInstances(string serviceName); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Resolved '{ServiceName}' to '{ServiceInstance}'.")] + private partial void LogServiceInstanceResolved(string serviceName, Uri serviceInstance); } diff --git a/src/Discovery/src/HttpClients/LoadBalancers/RoundRobinLoadBalancer.cs b/src/Discovery/src/HttpClients/LoadBalancers/RoundRobinLoadBalancer.cs index e0a913d367..4b459df508 100644 --- a/src/Discovery/src/HttpClients/LoadBalancers/RoundRobinLoadBalancer.cs +++ b/src/Discovery/src/HttpClients/LoadBalancers/RoundRobinLoadBalancer.cs @@ -13,7 +13,7 @@ namespace Steeltoe.Discovery.HttpClients.LoadBalancers; /// /// Returns service instances in round-robin fashion, optionally using distributed caching for determining the next instance. /// -public sealed class RoundRobinLoadBalancer : ILoadBalancer +public sealed partial class RoundRobinLoadBalancer : ILoadBalancer { private const string CacheKeyPrefix = "Steeltoe-LoadBalancerIndex-"; private readonly ServiceInstancesResolver _serviceInstancesResolver; @@ -69,20 +69,20 @@ public async Task ResolveServiceInstanceAsync(Uri requestUri, CancellationT ArgumentNullException.ThrowIfNull(requestUri); string serviceName = requestUri.Host; - _logger.LogTrace("Resolving service instance for '{ServiceName}'.", serviceName); + LogResolvingServiceInstance(serviceName); IList availableServiceInstances = await _serviceInstancesResolver.ResolveInstancesAsync(serviceName, cancellationToken); if (availableServiceInstances.Count == 0) { - _logger.LogWarning("No service instances are available for '{ServiceName}'.", serviceName); + LogNoServiceInstances(serviceName); return requestUri; } int instanceIndex = await GetNextInstanceIndexAsync(serviceName, availableServiceInstances.Count, cancellationToken); IServiceInstance serviceInstance = availableServiceInstances[instanceIndex]; - _logger.LogDebug("Resolved '{ServiceName}' to '{ServiceInstance}'.", serviceName, serviceInstance.Uri); + LogServiceInstanceResolved(serviceName, serviceInstance.Uri); return new Uri(serviceInstance.Uri, requestUri.PathAndQuery); } @@ -128,4 +128,13 @@ public Task UpdateStatisticsAsync(Uri requestUri, Uri serviceInstanceUri, TimeSp cancellationToken.ThrowIfCancellationRequested(); return Task.CompletedTask; } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Resolving service instance for '{ServiceName}'.")] + private partial void LogResolvingServiceInstance(string serviceName); + + [LoggerMessage(Level = LogLevel.Warning, Message = "No service instances are available for '{ServiceName}'.")] + private partial void LogNoServiceInstances(string serviceName); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Resolved '{ServiceName}' to '{ServiceInstance}'.")] + private partial void LogServiceInstanceResolved(string serviceName, Uri serviceInstance); } diff --git a/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs b/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs index c6e6be70c6..6a13c24690 100644 --- a/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs +++ b/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs @@ -14,7 +14,7 @@ namespace Steeltoe.Discovery.HttpClients.LoadBalancers; /// /// Queries all discovery clients for service instances, optionally caching the results using . /// -public sealed class ServiceInstancesResolver +public sealed partial class ServiceInstancesResolver { private readonly IDiscoveryClient[] _discoveryClients; private readonly IDistributedCache? _distributedCache; @@ -66,7 +66,7 @@ public ServiceInstancesResolver(IEnumerable discoveryClients, if (_discoveryClients.Length == 0) { - _logger.LogWarning("No discovery clients are registered."); + LogNoDiscoveryClients(); } } @@ -83,7 +83,7 @@ public async Task> ResolveInstancesAsync(string serviceI if (instancesFromCache != null) { - _logger.LogDebug("Returning {Count} instances from cache.", instancesFromCache.Count); + LogReturningInstancesFromCache(instancesFromCache.Count); return instancesFromCache; } } @@ -99,7 +99,7 @@ public async Task> ResolveInstancesAsync(string serviceI } catch (Exception exception) { - _logger.LogError(exception, "Failed to get instances from {DiscoveryClient}.", discoveryClient.GetType()); + LogFailedToGetInstances(exception, discoveryClient.GetType()); } } @@ -133,6 +133,15 @@ private static byte[] ToCacheValue(IEnumerable instances) return JsonSerializer.SerializeToUtf8Bytes(serializableInstances); } + [LoggerMessage(Level = LogLevel.Warning, Message = "No discovery clients are registered.")] + private partial void LogNoDiscoveryClients(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Returning {Count} instances from cache.")] + private partial void LogReturningInstancesFromCache(int count); + + [LoggerMessage(Level = LogLevel.Error, Message = "Failed to get instances from {DiscoveryClient}.")] + private partial void LogFailedToGetInstances(Exception exception, Type discoveryClient); + private sealed class JsonSerializableServiceInstance : IServiceInstance { // Trust that deserialized instances meet the IServiceInstance contract, so suppress nullability warnings. diff --git a/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs b/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs index b653071dea..9e5958cacf 100644 --- a/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs +++ b/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs @@ -128,6 +128,35 @@ public sealed class EurekaClientTest } """; + private static readonly string ExpectedJsonRequestBody = """ + { + "instance": { + "instanceId": "some", + "app": "FOOBAR", + "ipAddr": "127.0.0.1", + "port": { + "@enabled": "true", + "$": 8080 + }, + "securePort": { + "@enabled": "false", + "$": 9090 + }, + "dataCenterInfo": { + "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", + "name": "MyOwn" + }, + "hostName": "localhost", + "overriddenstatus": "UNKNOWN", + "metadata": { + "@class": "java.util.Collections$EmptyMap" + }, + "lastUpdatedTimestamp": "1708427732823", + "lastDirtyTimestamp": "1708427732823" + } + } + """.ReplaceLineEndings(string.Empty).Replace(" ", string.Empty, StringComparison.Ordinal); + [Fact] public async Task RegisterAsync_ThrowsOnUnreachableServer() { @@ -161,8 +190,7 @@ public async Task RegisterAsync_ThrowsOnUnreachableServer() IList logMessages = capturingLoggerProvider.GetAll(); logMessages.Should().BeEquivalentTo( - $"DBUG {typeof(EurekaClient)}: Sending POST request to 'http://host-that-does-not-exist.net:9999/apps/FOOBAR' with body: " + - """{"instance":{"instanceId":"some","app":"FOOBAR","ipAddr":"127.0.0.1","port":{"@enabled":"true","$":8080},"securePort":{"@enabled":"false","$":9090},"dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name":"MyOwn"},"hostName":"localhost","overriddenstatus":"UNKNOWN","metadata":{"@class":"java.util.Collections$EmptyMap"},"lastUpdatedTimestamp":"1708427732823","lastDirtyTimestamp":"1708427732823"}}.""", + $"DBUG {typeof(EurekaClient)}: Sending POST request to 'http://host-that-does-not-exist.net:9999/apps/FOOBAR' with body: '{ExpectedJsonRequestBody}'.", $"WARN {typeof(EurekaClient)}: Failed to execute HTTP POST request to 'http://host-that-does-not-exist.net:9999/apps/FOOBAR' in attempt 1."); } @@ -208,10 +236,9 @@ public async Task RegisterAsync_ThrowsOnErrorResponse() logMessages.Should().BeEquivalentTo( [ - $"DBUG {typeof(EurekaClient)}: Sending POST request to 'http://localhost:8761/eureka/apps/FOOBAR' with body: " + - """{"instance":{"instanceId":"some","app":"FOOBAR","ipAddr":"127.0.0.1","port":{"@enabled":"true","$":8080},"securePort":{"@enabled":"false","$":9090},"dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name":"MyOwn"},"hostName":"localhost","overriddenstatus":"UNKNOWN","metadata":{"@class":"java.util.Collections$EmptyMap"},"lastUpdatedTimestamp":"1708427732823","lastDirtyTimestamp":"1708427732823"}}.""", + $"DBUG {typeof(EurekaClient)}: Sending POST request to 'http://localhost:8761/eureka/apps/FOOBAR' with body: '{ExpectedJsonRequestBody}'.", $"DBUG {typeof(EurekaClient)}: HTTP POST request to 'http://localhost:8761/eureka/apps/FOOBAR' returned status 404 in attempt 1.", - $"INFO {typeof(EurekaClient)}: HTTP POST request to 'http://localhost:8761/eureka/apps/FOOBAR' failed with status 404: Sorry!" + $"INFO {typeof(EurekaClient)}: HTTP POST request to 'http://localhost:8761/eureka/apps/FOOBAR' failed with status 404: 'Sorry!'." ], options => options.WithStrictOrdering()); } @@ -254,10 +281,9 @@ public async Task RegisterAsync_ThrowsOnRetryLimitReached() logMessages.Should().BeEquivalentTo( [ - $"DBUG {typeof(EurekaClient)}: Sending POST request to 'http://localhost:8761/eureka/apps/FOOBAR' with body: " + - """{"instance":{"instanceId":"some","app":"FOOBAR","ipAddr":"127.0.0.1","port":{"@enabled":"true","$":8080},"securePort":{"@enabled":"false","$":9090},"dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name":"MyOwn"},"hostName":"localhost","overriddenstatus":"UNKNOWN","metadata":{"@class":"java.util.Collections$EmptyMap"},"lastUpdatedTimestamp":"1708427732823","lastDirtyTimestamp":"1708427732823"}}.""", + $"DBUG {typeof(EurekaClient)}: Sending POST request to 'http://localhost:8761/eureka/apps/FOOBAR' with body: '{ExpectedJsonRequestBody}'.", $"DBUG {typeof(EurekaClient)}: HTTP POST request to 'http://localhost:8761/eureka/apps/FOOBAR' returned status 404 in attempt 1.", - $"INFO {typeof(EurekaClient)}: HTTP POST request to 'http://localhost:8761/eureka/apps/FOOBAR' failed with status 404: " + $"INFO {typeof(EurekaClient)}: HTTP POST request to 'http://localhost:8761/eureka/apps/FOOBAR' failed with status 404: ''." ], options => options.WithStrictOrdering()); } @@ -364,8 +390,7 @@ public async Task RegisterAsync_SendsRequestToServer() logMessages.Should().BeEquivalentTo( [ - $"DBUG {typeof(EurekaClient)}: Sending POST request to 'http://localhost:8761/eureka/apps/FOOBAR' with body: " + - """{"instance":{"instanceId":"some","app":"FOOBAR","ipAddr":"127.0.0.1","port":{"@enabled":"true","$":8080},"securePort":{"@enabled":"false","$":9090},"dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name":"MyOwn"},"hostName":"localhost","overriddenstatus":"UNKNOWN","metadata":{"@class":"java.util.Collections$EmptyMap"},"lastUpdatedTimestamp":"1708427732823","lastDirtyTimestamp":"1708427732823"}}.""", + $"DBUG {typeof(EurekaClient)}: Sending POST request to 'http://localhost:8761/eureka/apps/FOOBAR' with body: '{ExpectedJsonRequestBody}'.", $"DBUG {typeof(EurekaClient)}: HTTP POST request to 'http://localhost:8761/eureka/apps/FOOBAR' returned status 204 in attempt 1." ], options => options.WithStrictOrdering()); } @@ -408,12 +433,10 @@ public async Task RegisterAsync_TriesSecondServerIfFirstOneFails() logMessages.Should().BeEquivalentTo( [ - $"DBUG {typeof(EurekaClient)}: Sending POST request to 'http://server1:8761/apps/FOOBAR' with body: " + - """{"instance":{"instanceId":"some","app":"FOOBAR","ipAddr":"127.0.0.1","port":{"@enabled":"true","$":8080},"securePort":{"@enabled":"false","$":9090},"dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name":"MyOwn"},"hostName":"localhost","overriddenstatus":"UNKNOWN","metadata":{"@class":"java.util.Collections$EmptyMap"},"lastUpdatedTimestamp":"1708427732823","lastDirtyTimestamp":"1708427732823"}}.""", + $"DBUG {typeof(EurekaClient)}: Sending POST request to 'http://server1:8761/apps/FOOBAR' with body: '{ExpectedJsonRequestBody}'.", $"DBUG {typeof(EurekaClient)}: HTTP POST request to 'http://server1:8761/apps/FOOBAR' returned status 404 in attempt 1.", - $"INFO {typeof(EurekaClient)}: HTTP POST request to 'http://server1:8761/apps/FOOBAR' failed with status 404: ", - $"DBUG {typeof(EurekaClient)}: Sending POST request to 'http://server2:8761/apps/FOOBAR' with body: " + - """{"instance":{"instanceId":"some","app":"FOOBAR","ipAddr":"127.0.0.1","port":{"@enabled":"true","$":8080},"securePort":{"@enabled":"false","$":9090},"dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name":"MyOwn"},"hostName":"localhost","overriddenstatus":"UNKNOWN","metadata":{"@class":"java.util.Collections$EmptyMap"},"lastUpdatedTimestamp":"1708427732823","lastDirtyTimestamp":"1708427732823"}}.""", + $"INFO {typeof(EurekaClient)}: HTTP POST request to 'http://server1:8761/apps/FOOBAR' failed with status 404: ''.", + $"DBUG {typeof(EurekaClient)}: Sending POST request to 'http://server2:8761/apps/FOOBAR' with body: '{ExpectedJsonRequestBody}'.", $"DBUG {typeof(EurekaClient)}: HTTP POST request to 'http://server2:8761/apps/FOOBAR' returned status 204 in attempt 2." ], options => options.WithStrictOrdering()); } diff --git a/src/Management/src/Endpoint/ActuatorMapper.cs b/src/Management/src/Endpoint/ActuatorMapper.cs index 6f65dabf8c..12dc2242ed 100644 --- a/src/Management/src/Endpoint/ActuatorMapper.cs +++ b/src/Management/src/Endpoint/ActuatorMapper.cs @@ -12,7 +12,7 @@ namespace Steeltoe.Management.Endpoint; -internal abstract class ActuatorMapper +internal abstract partial class ActuatorMapper { private readonly IOptionsMonitor _managementOptionsMonitor; private readonly ILogger _logger; @@ -47,9 +47,8 @@ protected ActuatorMapper(IEnumerable middlewares, IOptionsM { if (managementOptions is { IsCloudFoundryEnabled: true, HasCloudFoundrySecurity: false }) { - _logger.LogWarning( - $"Actuators at the {ConfigureManagementOptions.DefaultCloudFoundryPath} endpoint are disabled because the Cloud Foundry security middleware is not active. " + - $"Call {nameof(EndpointApplicationBuilderExtensions.UseCloudFoundrySecurity)}() from your custom middleware pipeline to enable them."); + LogCloudFoundryActuatorsDisabled(ConfigureManagementOptions.DefaultCloudFoundryPath, + nameof(EndpointApplicationBuilderExtensions.UseCloudFoundrySecurity)); } foreach (IEndpointMiddleware middleware in _middlewares.Where(middleware => middleware is not HypermediaEndpointMiddleware)) @@ -62,7 +61,15 @@ protected ActuatorMapper(IEnumerable middlewares, IOptionsM protected void LogErrorForDuplicateRoute(string routePattern, IEndpointMiddleware existingMiddleware, IEndpointMiddleware duplicateMiddleware) { - _logger.LogError("Skipping over duplicate route '{Route}' from {DuplicateMiddlewareType}, which was already added by {ExistingMiddlewareType}", - routePattern, duplicateMiddleware.GetType(), existingMiddleware.GetType()); + LogSkippingDuplicateRoute(routePattern, duplicateMiddleware.GetType(), existingMiddleware.GetType()); } + + [LoggerMessage(Level = LogLevel.Warning, + Message = "Actuators at the {CloudFoundryPath} endpoint are disabled because the Cloud Foundry security middleware is not active. " + + "Call {MethodName}() from your custom middleware pipeline to enable them.")] + private partial void LogCloudFoundryActuatorsDisabled(string cloudFoundryPath, string methodName); + + [LoggerMessage(Level = LogLevel.Error, + Message = "Skipping over duplicate route '{Route}' from {DuplicateMiddlewareType}, which was already added by {ExistingMiddlewareType}.")] + private partial void LogSkippingDuplicateRoute(string route, Type duplicateMiddlewareType, Type existingMiddlewareType); } diff --git a/src/Management/src/Endpoint/Actuators/CloudFoundry/CloudFoundrySecurityMiddleware.cs b/src/Management/src/Endpoint/Actuators/CloudFoundry/CloudFoundrySecurityMiddleware.cs index 629ea0a4a1..c8734d6ed0 100644 --- a/src/Management/src/Endpoint/Actuators/CloudFoundry/CloudFoundrySecurityMiddleware.cs +++ b/src/Management/src/Endpoint/Actuators/CloudFoundry/CloudFoundrySecurityMiddleware.cs @@ -18,7 +18,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.CloudFoundry; -internal sealed class CloudFoundrySecurityMiddleware +internal sealed partial class CloudFoundrySecurityMiddleware { private const string BearerTokenPrefix = "Bearer "; private readonly IOptionsMonitor _managementOptionsMonitor; @@ -53,7 +53,7 @@ public async Task InvokeAsync(HttpContext context) { ArgumentNullException.ThrowIfNull(context); - _logger.LogDebug("InvokeAsync({RequestPath})", context.Request.Path.Value); + LogEntering(context.Request.Path.Value); ManagementOptions managementOptions = _managementOptionsMonitor.CurrentValue; if (Platform.IsCloudFoundry && managementOptions.IsCloudFoundryEnabled && PermissionsProvider.IsCloudFoundryRequest(context.Request.Path)) @@ -62,8 +62,7 @@ public async Task InvokeAsync(HttpContext context) if (string.IsNullOrEmpty(endpointOptions.ApplicationId)) { - _logger.LogError( - "The Application Id could not be found. Make sure the Cloud Foundry Configuration Provider has been added to the application configuration."); + LogApplicationIdMissing(); await ReturnErrorAsync(context, new SecurityResult(HttpStatusCode.ServiceUnavailable, PermissionsProvider.Messages.ApplicationIdMissing)); return; @@ -161,7 +160,7 @@ internal async Task GetPermissionsAsync(HttpContext context) private async Task ReturnErrorAsync(HttpContext context, SecurityResult error) { - _logger.LogError("Actuator Security Error: {Code} - {Message}", error.Code, error.Message); + LogSecurityError(error.Code, error.Message); context.Response.Headers.Append("Content-Type", "application/json;charset=UTF-8"); // UseStatusCodeFromResponse was added to prevent IIS/HWC from blocking the response body on 500-level errors. @@ -174,4 +173,14 @@ private async Task ReturnErrorAsync(HttpContext context, SecurityResult error) await JsonSerializer.SerializeAsync(context.Response.Body, error, cancellationToken: context.RequestAborted); } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Entering Cloud Foundry Security middleware at path {RequestPath}.")] + private partial void LogEntering(string? requestPath); + + [LoggerMessage(Level = LogLevel.Error, + Message = "The Application Id could not be found. Make sure the Cloud Foundry Configuration Provider has been added to the application configuration.")] + private partial void LogApplicationIdMissing(); + + [LoggerMessage(Level = LogLevel.Error, Message = "Actuator security error with status {Code}: '{Message}'.")] + private partial void LogSecurityError(HttpStatusCode code, string? message); } diff --git a/src/Management/src/Endpoint/Actuators/CloudFoundry/PermissionsProvider.cs b/src/Management/src/Endpoint/Actuators/CloudFoundry/PermissionsProvider.cs index 7b9d77c571..c44bfd35e9 100644 --- a/src/Management/src/Endpoint/Actuators/CloudFoundry/PermissionsProvider.cs +++ b/src/Management/src/Endpoint/Actuators/CloudFoundry/PermissionsProvider.cs @@ -17,7 +17,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.CloudFoundry; -internal sealed class PermissionsProvider +internal sealed partial class PermissionsProvider { private const string ReadSensitiveDataJsonPropertyName = "read_sensitive_data"; public const string HttpClientName = "CloudFoundrySecurity"; @@ -59,14 +59,13 @@ public async Task GetPermissionsAsync(string accessToken, Cancel try { - _logger.LogDebug("GetPermissionsAsync({Uri})", checkPermissionsUri); + LogGetPermissions(checkPermissionsUri); using HttpClient httpClient = CreateHttpClient(); using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken); if (response.StatusCode != HttpStatusCode.OK) { - _logger.LogInformation("Cloud Foundry returned status: {HttpStatus} while obtaining permissions from: {PermissionsUri}", response.StatusCode, - checkPermissionsUri); + LogResponseStatus(response.StatusCode, checkPermissionsUri); if (response.StatusCode is HttpStatusCode.Forbidden) { @@ -103,7 +102,7 @@ public async Task ParsePermissionsResponseAsync(HttpRespons { json = await response.Content.ReadAsStringAsync(cancellationToken); - _logger.LogDebug("GetPermissionsAsync returned json: {Json}", SecurityUtilities.SanitizeInput(json)); + LogResponseJson(SecurityUtilities.SanitizeInput(json)); var result = JsonSerializer.Deserialize>(json); @@ -118,7 +117,7 @@ public async Task ParsePermissionsResponseAsync(HttpRespons throw new SecurityException($"Exception extracting permissions from json: {SecurityUtilities.SanitizeInput(json)}", exception); } - _logger.LogDebug("GetPermissionsAsync returning: {Permissions}", permissions); + LogPermissions(permissions); return permissions; } @@ -129,6 +128,18 @@ private HttpClient CreateHttpClient() return httpClient; } + [LoggerMessage(Level = LogLevel.Debug, Message = "Fetching permissions from {PermissionsUri}.")] + private partial void LogGetPermissions(string permissionsUri); + + [LoggerMessage(Level = LogLevel.Information, Message = "Cloud Foundry returned status {HttpStatus} while obtaining permissions from {PermissionsUri}.")] + private partial void LogResponseStatus(HttpStatusCode httpStatus, string permissionsUri); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Permissions response returned JSON: {Json}")] + private partial void LogResponseJson(string json); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Resolved permissions to {Permissions}.")] + private partial void LogPermissions(EndpointPermissions permissions); + internal static class Messages { public const string AccessDenied = "Access denied"; diff --git a/src/Management/src/Endpoint/Actuators/DbMigrations/DbMigrationsEndpointHandler.cs b/src/Management/src/Endpoint/Actuators/DbMigrations/DbMigrationsEndpointHandler.cs index 073aea1ee1..8352763605 100644 --- a/src/Management/src/Endpoint/Actuators/DbMigrations/DbMigrationsEndpointHandler.cs +++ b/src/Management/src/Endpoint/Actuators/DbMigrations/DbMigrationsEndpointHandler.cs @@ -11,7 +11,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.DbMigrations; -internal sealed class DbMigrationsEndpointHandler : IDbMigrationsEndpointHandler +internal sealed partial class DbMigrationsEndpointHandler : IDbMigrationsEndpointHandler { private readonly IOptionsMonitor _optionsMonitor; private readonly IServiceProvider _serviceProvider; @@ -41,7 +41,7 @@ public async Task> InvokeAsync(object if (dbContextType is null) { - _logger.LogError("The Microsoft.EntityFrameworkCore.DbContext type is unavailable. Are you missing a package reference?"); + LogDbContextTypeUnavailable(); } else { @@ -83,7 +83,7 @@ public async Task> InvokeAsync(object } catch (DbException exception) when (exception.Message.Contains("exist", StringComparison.Ordinal)) { - _logger.LogWarning(exception, "Failed to load pending/applied migrations, returning all migrations."); + LogFailedToLoadMigrations(exception); AddRange(descriptor.PendingMigrations, _scanner.GetMigrations(dbContext)); } } @@ -106,4 +106,10 @@ private static void AddRange(IList source, IEnumerable items) } } } + + [LoggerMessage(Level = LogLevel.Error, Message = "The Microsoft.EntityFrameworkCore.DbContext type is unavailable. Are you missing a package reference?")] + private partial void LogDbContextTypeUnavailable(); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Failed to load pending/applied migrations, returning all migrations.")] + private partial void LogFailedToLoadMigrations(Exception exception); } diff --git a/src/Management/src/Endpoint/Actuators/Environment/EnvironmentEndpointHandler.cs b/src/Management/src/Endpoint/Actuators/Environment/EnvironmentEndpointHandler.cs index 7a72857137..5dfca27f23 100644 --- a/src/Management/src/Endpoint/Actuators/Environment/EnvironmentEndpointHandler.cs +++ b/src/Management/src/Endpoint/Actuators/Environment/EnvironmentEndpointHandler.cs @@ -11,7 +11,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.Environment; -internal sealed class EnvironmentEndpointHandler : IEnvironmentEndpointHandler +internal sealed partial class EnvironmentEndpointHandler : IEnvironmentEndpointHandler { private readonly IOptionsMonitor _optionsMonitor; private readonly IConfiguration _configuration; @@ -36,7 +36,7 @@ public EnvironmentEndpointHandler(IOptionsMonitor op public Task InvokeAsync(object? argument, CancellationToken cancellationToken) { - _logger.LogTrace("Fetching property sources."); + LogFetchingPropertySources(); List activeProfiles = [_environment.EnvironmentName]; IList propertySources = GetPropertySources(); @@ -114,4 +114,7 @@ private static HashSet GetFullKeyNames(IConfigurationProvider provider, return initialKeys; } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Fetching property sources.")] + private partial void LogFetchingPropertySources(); } diff --git a/src/Management/src/Endpoint/Actuators/Health/Availability/ApplicationAvailability.cs b/src/Management/src/Endpoint/Actuators/Health/Availability/ApplicationAvailability.cs index 63c1cee1c4..eb48e6fcc0 100644 --- a/src/Management/src/Endpoint/Actuators/Health/Availability/ApplicationAvailability.cs +++ b/src/Management/src/Endpoint/Actuators/Health/Availability/ApplicationAvailability.cs @@ -6,7 +6,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.Health.Availability; -public sealed class ApplicationAvailability +public sealed partial class ApplicationAvailability { public const string LivenessKey = "Liveness"; public const string ReadinessKey = "Readiness"; @@ -58,7 +58,7 @@ public void SetAvailabilityState(string availabilityType, AvailabilityState newS throw new InvalidOperationException($"{availabilityType} state can only be of type {availabilityType}State"); } - _logger.LogTrace("{StateKey} availability has been set to {NewState} by {Caller}", availabilityType, newState, caller ?? "unspecified"); + LogAvailabilityStateChanged(availabilityType, newState, caller ?? "unspecified"); _availabilityStates[availabilityType] = newState; if (availabilityType == LivenessKey) @@ -71,4 +71,7 @@ public void SetAvailabilityState(string availabilityType, AvailabilityState newS ReadinessChanged?.Invoke(this, new AvailabilityEventArgs(newState)); } } + + [LoggerMessage(Level = LogLevel.Trace, Message = "{StateKey} availability has been set to {NewState} by {Caller}.")] + private partial void LogAvailabilityStateChanged(string stateKey, AvailabilityState newState, string caller); } diff --git a/src/Management/src/Endpoint/Actuators/Health/Contributors/AvailabilityStateHealthContributor.cs b/src/Management/src/Endpoint/Actuators/Health/Contributors/AvailabilityStateHealthContributor.cs index 6552652017..ec7e703b6b 100644 --- a/src/Management/src/Endpoint/Actuators/Health/Contributors/AvailabilityStateHealthContributor.cs +++ b/src/Management/src/Endpoint/Actuators/Health/Contributors/AvailabilityStateHealthContributor.cs @@ -8,7 +8,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.Health.Contributors; -internal abstract class AvailabilityStateHealthContributor : IHealthContributor +internal abstract partial class AvailabilityStateHealthContributor : IHealthContributor { private readonly IDictionary _stateMappings; private readonly ILogger _logger; @@ -39,18 +39,18 @@ private HealthCheckResult Health() if (currentHealth == null) { - _logger.LogError("Failed to get current availability state"); + LogFailedToGetState(); health.Description = "Failed to get current availability state"; } else { - try + if (_stateMappings.TryGetValue(currentHealth, out HealthStatus status)) { - health.Status = _stateMappings[currentHealth]; + health.Status = status; } - catch (Exception exception) + else { - _logger.LogError(exception, "Failed to map current availability state"); + LogFailedToMapState(currentHealth); health.Description = "Failed to map current availability state"; } } @@ -59,4 +59,10 @@ private HealthCheckResult Health() } protected abstract AvailabilityState? GetState(); + + [LoggerMessage(Level = LogLevel.Error, Message = "Failed to get current availability state.")] + private partial void LogFailedToGetState(); + + [LoggerMessage(Level = LogLevel.Error, Message = "Failed to map availability state {State}.")] + private partial void LogFailedToMapState(AvailabilityState state); } diff --git a/src/Management/src/Endpoint/Actuators/Health/HealthEndpointHandler.cs b/src/Management/src/Endpoint/Actuators/Health/HealthEndpointHandler.cs index e7b3cf8770..426c39ade0 100644 --- a/src/Management/src/Endpoint/Actuators/Health/HealthEndpointHandler.cs +++ b/src/Management/src/Endpoint/Actuators/Health/HealthEndpointHandler.cs @@ -12,7 +12,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.Health; -internal sealed class HealthEndpointHandler : IHealthEndpointHandler +internal sealed partial class HealthEndpointHandler : IHealthEndpointHandler { private readonly IOptionsMonitor _endpointOptionsMonitor; private readonly IHealthAggregator _healthAggregator; @@ -115,8 +115,7 @@ private void CleanResponse(HealthEndpointResponse response, HealthEndpointOption if (ShouldClear(showComponents, healthRequest)) { - _logger.LogTrace("Clearing health check components. ShowComponents={ShowComponents}, HasClaim={HasClaimForHealth}.", showComponents, - healthRequest.HasClaim); + LogClearingComponents(showComponents, healthRequest.HasClaim); response.Components.Clear(); } @@ -126,8 +125,7 @@ private void CleanResponse(HealthEndpointResponse response, HealthEndpointOption if (ShouldClear(showDetails, healthRequest)) { - _logger.LogTrace("Clearing health check component details. ShowDetails={ShowDetails}, HasClaim={HasClaimForHealth}.", showDetails, - healthRequest.HasClaim); + LogClearingDetails(showDetails, healthRequest.HasClaim); foreach (HealthCheckResult component in response.Components.Values) { @@ -141,4 +139,12 @@ private static bool ShouldClear(ShowValues showValues, HealthEndpointRequest hea { return showValues == ShowValues.Never || (showValues == ShowValues.WhenAuthorized && !healthRequest.HasClaim); } + + [LoggerMessage(Level = LogLevel.Trace, + Message = "Clearing health check components because ShowComponents is {ShowComponents} and HasClaim is {HasClaimForHealth}.")] + private partial void LogClearingComponents(ShowValues showComponents, bool hasClaimForHealth); + + [LoggerMessage(Level = LogLevel.Trace, + Message = "Clearing health check component details because ShowDetails is {ShowDetails} and HasClaim is {HasClaimForHealth}.")] + private partial void LogClearingDetails(ShowValues showDetails, bool hasClaimForHealth); } diff --git a/src/Management/src/Endpoint/Actuators/Health/HealthEndpointMiddleware.cs b/src/Management/src/Endpoint/Actuators/Health/HealthEndpointMiddleware.cs index 657e861364..7737c50b1a 100644 --- a/src/Management/src/Endpoint/Actuators/Health/HealthEndpointMiddleware.cs +++ b/src/Management/src/Endpoint/Actuators/Health/HealthEndpointMiddleware.cs @@ -13,7 +13,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.Health; -internal sealed class HealthEndpointMiddleware : EndpointMiddleware +internal sealed partial class HealthEndpointMiddleware : EndpointMiddleware { private readonly IOptionsMonitor _endpointOptionsMonitor; private readonly ILogger _logger; @@ -59,11 +59,11 @@ private string GetRequestedHealthGroup(PathString requestPath, HealthEndpointOpt if (requestComponents.Length > 0 && requestComponents[^1] != endpointOptions.Id) { - _logger.LogTrace("Found group '{HealthGroup}' in the request path.", requestComponents[^1]); + LogGroupFound(requestComponents[^1]); return requestComponents[^1]; } - _logger.LogTrace("Did not find a health group in the request path."); + LogNoGroupFound(); return string.Empty; } @@ -127,4 +127,10 @@ private static int GetStatusCode(HealthEndpointResponse response) { return response.Status is HealthStatus.Down or HealthStatus.OutOfService ? 503 : 200; } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Found group '{HealthGroup}' in the request path.")] + private partial void LogGroupFound(string healthGroup); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Did not find a health group in the request path.")] + private partial void LogNoGroupFound(); } diff --git a/src/Management/src/Endpoint/Actuators/HeapDump/HeapDumpEndpointHandler.cs b/src/Management/src/Endpoint/Actuators/HeapDump/HeapDumpEndpointHandler.cs index d6e8d19cad..1c8b1a70e8 100644 --- a/src/Management/src/Endpoint/Actuators/HeapDump/HeapDumpEndpointHandler.cs +++ b/src/Management/src/Endpoint/Actuators/HeapDump/HeapDumpEndpointHandler.cs @@ -8,7 +8,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.HeapDump; -internal sealed class HeapDumpEndpointHandler : IHeapDumpEndpointHandler +internal sealed partial class HeapDumpEndpointHandler : IHeapDumpEndpointHandler { private readonly IOptionsMonitor _optionsMonitor; private readonly IHeapDumper _heapDumper; @@ -29,8 +29,11 @@ public HeapDumpEndpointHandler(IOptionsMonitor optionsM public Task InvokeAsync(object? argument, CancellationToken cancellationToken) { - _logger.LogTrace("Invoking the heap dumper"); + LogInvokingHeapDumper(); string filePath = _heapDumper.DumpHeapToFile(cancellationToken); return Task.FromResult(filePath); } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Invoking the heap dumper.")] + private partial void LogInvokingHeapDumper(); } diff --git a/src/Management/src/Endpoint/Actuators/HeapDump/HeapDumpEndpointMiddleware.cs b/src/Management/src/Endpoint/Actuators/HeapDump/HeapDumpEndpointMiddleware.cs index c03d1b552e..9fe78539db 100644 --- a/src/Management/src/Endpoint/Actuators/HeapDump/HeapDumpEndpointMiddleware.cs +++ b/src/Management/src/Endpoint/Actuators/HeapDump/HeapDumpEndpointMiddleware.cs @@ -11,7 +11,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.HeapDump; -internal sealed class HeapDumpEndpointMiddleware( +internal sealed partial class HeapDumpEndpointMiddleware( IHeapDumpEndpointHandler endpointHandler, IOptionsMonitor managementOptionsMonitor, ILoggerFactory loggerFactory) : EndpointMiddleware(endpointHandler, managementOptionsMonitor, loggerFactory) { @@ -28,7 +28,7 @@ protected override async Task WriteResponseAsync(string? fileName, HttpContext h { ArgumentNullException.ThrowIfNull(httpContext); - _logger.LogDebug("Returning: {FileName}", fileName); + LogReturning(fileName); if (!File.Exists(fileName)) { @@ -51,4 +51,7 @@ protected override async Task WriteResponseAsync(string? fileName, HttpContext h File.Delete(fileName); } } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Returning heap dump file '{FileName}'.")] + private partial void LogReturning(string? fileName); } diff --git a/src/Management/src/Endpoint/Actuators/HeapDump/HeapDumper.cs b/src/Management/src/Endpoint/Actuators/HeapDump/HeapDumper.cs index 06bbd0c286..6e21c41e2d 100644 --- a/src/Management/src/Endpoint/Actuators/HeapDump/HeapDumper.cs +++ b/src/Management/src/Endpoint/Actuators/HeapDump/HeapDumper.cs @@ -11,7 +11,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.HeapDump; -internal sealed class HeapDumper : IHeapDumper +internal sealed partial class HeapDumper : IHeapDumper { private readonly IOptionsMonitor _optionsMonitor; private readonly TimeProvider _timeProvider; @@ -43,7 +43,7 @@ public string DumpHeapToFile(CancellationToken cancellationToken) _ => "full dump" }; - _logger.LogInformation("Attempting to create a {DumpType}.", dumpDescription); + LogStart(dumpDescription); try { @@ -64,7 +64,7 @@ public string DumpHeapToFile(CancellationToken cancellationToken) throw; } - _logger.LogInformation("Successfully created a {DumpType}.", dumpDescription); + LogSucceeded(dumpDescription); return outputPath; } @@ -159,7 +159,7 @@ internal void CaptureLogOutput(Func action, string dumpDescrip throw new InvalidOperationException($"Failed to create a {dumpDescription}. Captured log:{System.Environment.NewLine}{logOutput}", error); } - _logger.LogTrace("Captured log from {DumpType}:{LineBreak}{DumpLog}", dumpDescription, System.Environment.NewLine, logOutput); + LogDumpLogCaptured(dumpDescription, System.Environment.NewLine, logOutput); } private static void SafeDelete(string? outputPath) @@ -179,4 +179,13 @@ private static void SafeDelete(string? outputPath) } } } + + [LoggerMessage(Level = LogLevel.Information, Message = "Attempting to create a {DumpType}.")] + private partial void LogStart(string dumpType); + + [LoggerMessage(Level = LogLevel.Information, Message = "Successfully created a {DumpType}.")] + private partial void LogSucceeded(string dumpType); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Captured log from {DumpType}:{LineBreak}{DumpLog}")] + private partial void LogDumpLogCaptured(string dumpType, string lineBreak, string dumpLog); } diff --git a/src/Management/src/Endpoint/Actuators/HttpExchanges/Diagnostics/DiagnosticObserver.cs b/src/Management/src/Endpoint/Actuators/HttpExchanges/Diagnostics/DiagnosticObserver.cs index e6cdf32e39..5a1ff13705 100644 --- a/src/Management/src/Endpoint/Actuators/HttpExchanges/Diagnostics/DiagnosticObserver.cs +++ b/src/Management/src/Endpoint/Actuators/HttpExchanges/Diagnostics/DiagnosticObserver.cs @@ -7,7 +7,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges.Diagnostics; -internal abstract class DiagnosticObserver : IObserver>, IDisposable +internal abstract partial class DiagnosticObserver : IObserver>, IDisposable { private readonly string _observerName; private readonly string _listenerName; @@ -38,7 +38,7 @@ protected virtual void Dispose(bool disposing) _subscription?.Dispose(); _subscription = null; - _logger.LogTrace("DiagnosticObserver {Observer} disposed", _observerName); + LogObserverDisposed(_observerName); } } @@ -54,7 +54,7 @@ public void Subscribe(DiagnosticListener listener) } _subscription = listener.Subscribe(this); - _logger.LogTrace("DiagnosticObserver {Observer} subscribed to {Listener}", _observerName, listener.Name); + LogObserverSubscribed(_observerName, listener.Name); } } @@ -76,9 +76,18 @@ public virtual void OnNext(KeyValuePair value) } catch (Exception exception) { - _logger.LogError(exception, "Failed to process event {Id}", value.Key); + LogFailedToProcessEvent(exception, value.Key); } } public abstract void ProcessEvent(string eventName, object? value); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Diagnostic observer {Observer} disposed.")] + private partial void LogObserverDisposed(string observer); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Diagnostic observer {Observer} subscribed to {Listener}.")] + private partial void LogObserverSubscribed(string observer, string listener); + + [LoggerMessage(Level = LogLevel.Error, Message = "Failed to process event {Id}.")] + private partial void LogFailedToProcessEvent(Exception exception, string id); } diff --git a/src/Management/src/Endpoint/Actuators/HttpExchanges/Diagnostics/DiagnosticsManager.cs b/src/Management/src/Endpoint/Actuators/HttpExchanges/Diagnostics/DiagnosticsManager.cs index 7be3cc71a7..1b92b98b3c 100644 --- a/src/Management/src/Endpoint/Actuators/HttpExchanges/Diagnostics/DiagnosticsManager.cs +++ b/src/Management/src/Endpoint/Actuators/HttpExchanges/Diagnostics/DiagnosticsManager.cs @@ -7,7 +7,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges.Diagnostics; -internal sealed class DiagnosticsManager : IObserver, IDisposable +internal sealed partial class DiagnosticsManager : IObserver, IDisposable { private readonly ILogger _logger; private readonly List _observers; @@ -57,7 +57,7 @@ public void Start() if (_listenersSubscription != null) { - _logger.LogTrace("Subscribed to Diagnostic Listener"); + LogSubscribed(); } } } @@ -82,4 +82,7 @@ public void Dispose() _isDisposed = true; } } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Subscribed to diagnostic listener.")] + private partial void LogSubscribed(); } diff --git a/src/Management/src/Endpoint/Actuators/HttpExchanges/Diagnostics/DiagnosticsService.cs b/src/Management/src/Endpoint/Actuators/HttpExchanges/Diagnostics/DiagnosticsService.cs index 275d92dc4d..1e834e8bfa 100644 --- a/src/Management/src/Endpoint/Actuators/HttpExchanges/Diagnostics/DiagnosticsService.cs +++ b/src/Management/src/Endpoint/Actuators/HttpExchanges/Diagnostics/DiagnosticsService.cs @@ -7,7 +7,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges.Diagnostics; -internal sealed class DiagnosticsService : IHostedService +internal sealed partial class DiagnosticsService : IHostedService { private readonly ILogger _logger; private readonly DiagnosticsManager _observerManager; @@ -23,15 +23,21 @@ public DiagnosticsService(DiagnosticsManager observerManager, ILogger _optionsMonitor; private readonly HttpExchangesRepository _httpExchangesRepository; @@ -30,8 +30,11 @@ public HttpExchangesEndpointHandler(IOptionsMonitor InvokeAsync(object? argument, CancellationToken cancellationToken) { - _logger.LogTrace("Fetching Http Exchanges"); + LogFetchingHttpExchanges(); HttpExchangesResult result = _httpExchangesRepository.GetHttpExchanges(); return Task.FromResult(result); } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Fetching HTTP exchanges.")] + private partial void LogFetchingHttpExchanges(); } diff --git a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangesRepository.cs b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangesRepository.cs index 994ab4272e..63107c0121 100644 --- a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangesRepository.cs +++ b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangesRepository.cs @@ -9,7 +9,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges; -internal sealed class HttpExchangesRepository +internal sealed partial class HttpExchangesRepository { private const string RedactedText = "******"; @@ -34,7 +34,7 @@ private void OnRecord(HttpExchange exchange) { ArgumentNullException.ThrowIfNull(exchange); - _logger.LogDebug("Incoming exchange for {Url}.", exchange.Request.Uri); + LogIncomingExchange(exchange.Request.Uri); _queue.Enqueue(exchange); if (_queue.Count > _optionsMonitor.CurrentValue.Capacity) @@ -104,4 +104,7 @@ private static bool HeaderShouldBeRedacted(string currentHeader, HashSet { return !options.IncludeTimeTaken ? null : timeTaken; } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Incoming exchange for {Url}.")] + private partial void LogIncomingExchange(Uri url); } diff --git a/src/Management/src/Endpoint/Actuators/Hypermedia/HypermediaService.cs b/src/Management/src/Endpoint/Actuators/Hypermedia/HypermediaService.cs index ba0b3c7890..6e1df986b2 100644 --- a/src/Management/src/Endpoint/Actuators/Hypermedia/HypermediaService.cs +++ b/src/Management/src/Endpoint/Actuators/Hypermedia/HypermediaService.cs @@ -12,7 +12,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.Hypermedia; -internal sealed class HypermediaService +internal sealed partial class HypermediaService { private readonly IOptionsMonitor _managementOptionsMonitor; private readonly EndpointOptions _endpointOptions; @@ -70,7 +70,7 @@ public Links Invoke(Uri baseUrl) return links; } - _logger.LogTrace("Processing hypermedia for {ManagementOptions}", managementOptions); + LogProcessingHypermedia(); Link? selfLink = null; bool skipExposureCheck = PermissionsProvider.IsCloudFoundryRequest(baseUrl.PathAndQuery); @@ -101,7 +101,7 @@ public Links Invoke(Uri baseUrl) { if (links.Entries.ContainsKey(endpointOptions.Id)) { - _logger.LogWarning("Duplicate endpoint with ID '{DuplicateEndpointId}' detected.", endpointOptions.Id); + LogDuplicateEndpoint(endpointOptions.Id); } else { @@ -129,4 +129,10 @@ private static Link CreateLink(Uri baseUrl, string? basePath, EndpointOptions en string href = builder.Uri.ToString(); return new Link(href, false); } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Processing hypermedia.")] + private partial void LogProcessingHypermedia(); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Duplicate endpoint with ID '{DuplicateEndpointId}' detected.")] + private partial void LogDuplicateEndpoint(string? duplicateEndpointId); } diff --git a/src/Management/src/Endpoint/Actuators/Info/Contributors/GitInfoContributor.cs b/src/Management/src/Endpoint/Actuators/Info/Contributors/GitInfoContributor.cs index 75c62dcfad..71e1a065de 100644 --- a/src/Management/src/Endpoint/Actuators/Info/Contributors/GitInfoContributor.cs +++ b/src/Management/src/Endpoint/Actuators/Info/Contributors/GitInfoContributor.cs @@ -8,7 +8,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.Info.Contributors; -internal sealed class GitInfoContributor : ConfigurationContributor, IInfoContributor +internal sealed partial class GitInfoContributor : ConfigurationContributor, IInfoContributor { private const string GitSettingsPrefix = "git"; private const string GitPropertiesFileName = "git.properties"; @@ -78,7 +78,7 @@ public async Task ContributeAsync(InfoBuilder builder, CancellationToken cancell } else { - _logger.LogWarning("File '{Path}' does not exist.", _propertiesPath); + LogFileNotFound(_propertiesPath); } return null; @@ -99,4 +99,7 @@ protected override void AddKeyValue(IDictionary dictionary, str dictionary[key] = valueToInsert; } + + [LoggerMessage(Level = LogLevel.Warning, Message = "File '{Path}' does not exist.")] + private partial void LogFileNotFound(string path); } diff --git a/src/Management/src/Endpoint/Actuators/Info/InfoEndpointHandler.cs b/src/Management/src/Endpoint/Actuators/Info/InfoEndpointHandler.cs index 8c1a65223b..38a942e86d 100644 --- a/src/Management/src/Endpoint/Actuators/Info/InfoEndpointHandler.cs +++ b/src/Management/src/Endpoint/Actuators/Info/InfoEndpointHandler.cs @@ -10,7 +10,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.Info; -internal sealed class InfoEndpointHandler : IInfoEndpointHandler +internal sealed partial class InfoEndpointHandler : IInfoEndpointHandler { private readonly IOptionsMonitor _optionsMonitor; private readonly IInfoContributor[] _contributors; @@ -44,11 +44,13 @@ public InfoEndpointHandler(IOptionsMonitor optionsMonitor, } catch (Exception exception) when (!exception.IsCancellation()) { - _logger.LogWarning(exception, "Exception thrown by contributor '{ContributorTypeName}' while contributing to info endpoint.", - contributor.GetType()); + LogContributorError(exception, contributor.GetType()); } } return builder.Build(); } + + [LoggerMessage(Level = LogLevel.Warning, Message = "Exception thrown by contributor '{ContributorTypeName}' while contributing to info endpoint.")] + private partial void LogContributorError(Exception exception, Type contributorTypeName); } diff --git a/src/Management/src/Endpoint/Actuators/Loggers/LoggersEndpointHandler.cs b/src/Management/src/Endpoint/Actuators/Loggers/LoggersEndpointHandler.cs index d0412ee6d2..c0afe42906 100644 --- a/src/Management/src/Endpoint/Actuators/Loggers/LoggersEndpointHandler.cs +++ b/src/Management/src/Endpoint/Actuators/Loggers/LoggersEndpointHandler.cs @@ -11,7 +11,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.Loggers; -internal sealed class LoggersEndpointHandler : ILoggersEndpointHandler +internal sealed partial class LoggersEndpointHandler : ILoggersEndpointHandler { private const string SpringDefaultCategoryName = "Default"; @@ -48,7 +48,7 @@ public LoggersEndpointHandler(IOptionsMonitor optionsMon { ArgumentNullException.ThrowIfNull(request); - _logger.LogDebug("Invoke({Request})", SecurityUtilities.SanitizeInput(request.ToString())); + LogEntering(SecurityUtilities.SanitizeInput(request.ToString())); LoggersResponse? response; @@ -72,7 +72,7 @@ private LoggersResponse GetLogLevels() foreach (DynamicLoggerState loggerState in loggerStates.OrderBy(entry => entry.CategoryName)) { - _logger.LogTrace("Adding {LoggerState}", loggerState); + LogAddingLoggerState(loggerState); string categoryName = loggerState.CategoryName.Length == 0 ? SpringDefaultCategoryName : loggerState.CategoryName; var levels = new LoggerLevels(loggerState.BackupMinLevel, loggerState.EffectiveMinLevel); @@ -89,4 +89,10 @@ private void SetLogLevel(string name, string? level) _dynamicLoggerProvider.SetLogLevel(categoryName, logLevel); } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Invoking loggers endpoint handler with request {Request}.")] + private partial void LogEntering(string request); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Adding state {LoggerState}.")] + private partial void LogAddingLoggerState(DynamicLoggerState loggerState); } diff --git a/src/Management/src/Endpoint/Actuators/Loggers/LoggersEndpointMiddleware.cs b/src/Management/src/Endpoint/Actuators/Loggers/LoggersEndpointMiddleware.cs index a29774a5ae..30427ca776 100644 --- a/src/Management/src/Endpoint/Actuators/Loggers/LoggersEndpointMiddleware.cs +++ b/src/Management/src/Endpoint/Actuators/Loggers/LoggersEndpointMiddleware.cs @@ -13,7 +13,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.Loggers; -internal sealed class LoggersEndpointMiddleware( +internal sealed partial class LoggersEndpointMiddleware( ILoggersEndpointHandler endpointHandler, IOptionsMonitor managementOptionsMonitor, ILoggerFactory loggerFactory) : EndpointMiddleware(endpointHandler, managementOptionsMonitor, loggerFactory) { @@ -37,13 +37,13 @@ internal sealed class LoggersEndpointMiddleware( change.TryGetValue("configuredLevel", out string? level); - _logger.LogDebug("Change Request: {Name}, {Level}", loggerName, level ?? "RESET"); + LogChangeRequest(loggerName, level ?? "RESET"); if (!string.IsNullOrEmpty(loggerName)) { if (!string.IsNullOrEmpty(level) && LoggerLevels.StringToLogLevel(level) == null) { - _logger.LogDebug("Invalid LogLevel specified: {Level}", level); + LogInvalidLevel(level); return null; } @@ -68,7 +68,7 @@ private async Task> DeserializeRequestAsync(Stream st } catch (Exception exception) when (!exception.IsCancellation()) { - _logger.LogError(exception, "Exception deserializing loggers endpoint request."); + LogDeserializationFailed(exception); } return []; @@ -99,4 +99,13 @@ protected override async Task WriteResponseAsync(LoggersResponse? response, Http await JsonSerializer.SerializeAsync(httpContext.Response.Body, response, options, cancellationToken); } } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Received request to change logger '{Name}' to level {Level}.")] + private partial void LogChangeRequest(string name, string level); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Invalid log level {Level} specified.")] + private partial void LogInvalidLevel(string level); + + [LoggerMessage(Level = LogLevel.Error, Message = "Failed to deserialize loggers endpoint request.")] + private partial void LogDeserializationFailed(Exception exception); } diff --git a/src/Management/src/Endpoint/Actuators/Refresh/RefreshEndpointHandler.cs b/src/Management/src/Endpoint/Actuators/Refresh/RefreshEndpointHandler.cs index 47b6fe4517..3b6a909399 100644 --- a/src/Management/src/Endpoint/Actuators/Refresh/RefreshEndpointHandler.cs +++ b/src/Management/src/Endpoint/Actuators/Refresh/RefreshEndpointHandler.cs @@ -9,7 +9,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.Refresh; -internal sealed class RefreshEndpointHandler : IRefreshEndpointHandler +internal sealed partial class RefreshEndpointHandler : IRefreshEndpointHandler { private readonly IOptionsMonitor _optionsMonitor; private readonly IConfiguration _configuration; @@ -30,7 +30,7 @@ public RefreshEndpointHandler(IOptionsMonitor optionsMon public Task> InvokeAsync(object? argument, CancellationToken cancellationToken) { - _logger.LogInformation("Refreshing Configuration"); + LogRefreshingConfiguration(); if (_configuration is not IConfigurationRoot root) { @@ -51,4 +51,7 @@ public Task> InvokeAsync(object? argument, CancellationToken cance return Task.FromResult>(keys.ToList()); } + + [LoggerMessage(Level = LogLevel.Information, Message = "Refreshing configuration.")] + private partial void LogRefreshingConfiguration(); } diff --git a/src/Management/src/Endpoint/Actuators/RouteMappings/AspNetEndpointProvider.cs b/src/Management/src/Endpoint/Actuators/RouteMappings/AspNetEndpointProvider.cs index 39c995b7a9..018ac42330 100644 --- a/src/Management/src/Endpoint/Actuators/RouteMappings/AspNetEndpointProvider.cs +++ b/src/Management/src/Endpoint/Actuators/RouteMappings/AspNetEndpointProvider.cs @@ -28,7 +28,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.RouteMappings; /// /// Gathers endpoints in an ASP.NET Core application by combining information from various sources. /// -internal sealed class AspNetEndpointProvider +internal sealed partial class AspNetEndpointProvider { private static readonly MethodInfo? ProducesContentTypesPropertyGetter = typeof(ProducesResponseTypeAttribute).GetProperty("ContentTypes", BindingFlags.Instance | BindingFlags.NonPublic)?.GetMethod; @@ -60,7 +60,7 @@ public IList GetEndpoints(bool includeActuators) { if (!_mvcOptionsMonitor.CurrentValue.EnableEndpointRouting) { - _logger.LogWarning("Conventional routing is not supported."); + LogConventionalRoutingNotSupported(); return []; } @@ -436,4 +436,7 @@ private static IEnumerable ExtractProducedContentTypes(EndpointMetadataC yield return contentType; } } + + [LoggerMessage(Level = LogLevel.Warning, Message = "Conventional routing is not supported.")] + private partial void LogConventionalRoutingNotSupported(); } diff --git a/src/Management/src/Endpoint/Actuators/ThreadDump/EventPipeThreadDumper.cs b/src/Management/src/Endpoint/Actuators/ThreadDump/EventPipeThreadDumper.cs index 5163eef70d..80e71fdc00 100644 --- a/src/Management/src/Endpoint/Actuators/ThreadDump/EventPipeThreadDumper.cs +++ b/src/Management/src/Endpoint/Actuators/ThreadDump/EventPipeThreadDumper.cs @@ -18,7 +18,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.ThreadDump; /// /// Thread dumper that uses the EventPipe to acquire the call stacks of all the running threads. /// -internal sealed class EventPipeThreadDumper : IThreadDumper +internal sealed partial class EventPipeThreadDumper : IThreadDumper { private const string ThreadIdTemplate = "Thread ("; @@ -63,7 +63,7 @@ public async Task> DumpThreadsAsync(CancellationToken cancella { try { - _logger.LogInformation("Attempting to create a thread dump."); + LogStart(); var client = new DiagnosticsClient(System.Environment.ProcessId); List providers = [new("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational)]; @@ -72,16 +72,16 @@ public async Task> DumpThreadsAsync(CancellationToken cancella using EventPipeSession session = client.StartEventPipeSession(providers); List threads = await GetThreadsFromEventPipeSessionAsync(session, logWriter, cancellationToken); - _logger.LogInformation("Successfully created a thread dump."); + LogSucceeded(); return threads; } finally { #pragma warning disable S1215 // "GC.Collect" should not be called - long totalMemory = GC.GetTotalMemory(true); + long memoryInBytes = GC.GetTotalMemory(true); #pragma warning restore S1215 // "GC.Collect" should not be called - _logger.LogDebug("Total memory: {Memory}.", totalMemory); + LogTotalMemory(memoryInBytes); } }, cancellationToken); } @@ -128,7 +128,7 @@ internal async Task CaptureLogOutputAsync(Func> GetThreadsFromEventPipeSessionAsync(EventPi List results = ReadStackSource(stackSource, symbolReader, logWriter).ToList(); - _logger.LogTrace("Finished thread walk, found {Count} results.", results.Count); + LogThreadWalkFinished(results.Count); return results; } finally @@ -183,9 +183,7 @@ private async Task CreateTraceFileAsync(EventPipeSession session, Cancel } catch (TimeoutException) when (!cancellationToken.IsCancellationRequested) { -#pragma warning disable S6667 // Logging in a catch clause should pass the caught exception as a parameter. - _logger.LogInformation("Sufficiently large applications can cause this command to take non-trivial amounts of time."); -#pragma warning restore S6667 // Logging in a catch clause should pass the caught exception as a parameter. + LogPossiblySlow(); throw; } @@ -367,6 +365,24 @@ private static void SetThreadState(ThreadInfo threadInfo) : State.Runnable; } + [LoggerMessage(Level = LogLevel.Information, Message = "Attempting to create a thread dump.")] + private partial void LogStart(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Successfully created a thread dump.")] + private partial void LogSucceeded(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Total memory is {MemoryInBytes} bytes.")] + private partial void LogTotalMemory(long memoryInBytes); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Captured log from thread dump:{LineBreak}{DumpLog}")] + private partial void LogDumpLogCaptured(string lineBreak, string? dumpLog); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Finished thread walk, found {Count} results.")] + private partial void LogThreadWalkFinished(int count); + + [LoggerMessage(Level = LogLevel.Information, Message = "Sufficiently large applications can cause this command to take non-trivial amounts of time.")] + private partial void LogPossiblySlow(); + private sealed record StackFrameSymbol(string AssemblyName, string TypeName, string MemberName, string Parameters) { public static bool TryParse(string frameName, [NotNullWhen(true)] out StackFrameSymbol? symbol) diff --git a/src/Management/src/Endpoint/Actuators/ThreadDump/ThreadDumpEndpointHandler.cs b/src/Management/src/Endpoint/Actuators/ThreadDump/ThreadDumpEndpointHandler.cs index 597f545f9b..87bde9475f 100644 --- a/src/Management/src/Endpoint/Actuators/ThreadDump/ThreadDumpEndpointHandler.cs +++ b/src/Management/src/Endpoint/Actuators/ThreadDump/ThreadDumpEndpointHandler.cs @@ -8,7 +8,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.ThreadDump; -internal sealed class ThreadDumpEndpointHandler : IThreadDumpEndpointHandler +internal sealed partial class ThreadDumpEndpointHandler : IThreadDumpEndpointHandler { private readonly IOptionsMonitor _optionsMonitor; private readonly IThreadDumper _threadDumper; @@ -29,7 +29,10 @@ public ThreadDumpEndpointHandler(IOptionsMonitor opti public async Task> InvokeAsync(object? argument, CancellationToken cancellationToken) { - _logger.LogTrace("Invoking ThreadDumper"); + LogInvokingThreadDumper(); return await _threadDumper.DumpThreadsAsync(cancellationToken); } + + [LoggerMessage(Level = LogLevel.Trace, Message = "Invoking thread dumper.")] + private partial void LogInvokingThreadDumper(); } diff --git a/src/Management/src/Endpoint/ManagementPort/ManagementPortMiddleware.cs b/src/Management/src/Endpoint/ManagementPort/ManagementPortMiddleware.cs index 761727907f..6ba88fb103 100644 --- a/src/Management/src/Endpoint/ManagementPort/ManagementPortMiddleware.cs +++ b/src/Management/src/Endpoint/ManagementPort/ManagementPortMiddleware.cs @@ -14,7 +14,7 @@ namespace Steeltoe.Management.Endpoint.ManagementPort; /// /// Blocks access to actuator endpoints on ports other than the management port. Blocks access to non-actuator endpoints on the management port. /// -internal sealed class ManagementPortMiddleware +internal sealed partial class ManagementPortMiddleware { private readonly IOptionsMonitor _managementOptionsMonitor; private readonly RequestDelegate? _next; @@ -36,7 +36,7 @@ public async Task InvokeAsync(HttpContext context) ArgumentNullException.ThrowIfNull(context); ManagementOptions managementOptions = _managementOptionsMonitor.CurrentValue; - _logger.LogDebug("InvokeAsync({RequestPath}), OptionsPath: {OptionsPath}", context.Request.Path.Value, managementOptions.Path); + LogEntering(context.Request.Path.Value, managementOptions.Path); bool allowRequest = IsRequestAllowed(context.Request, managementOptions); @@ -81,12 +81,7 @@ private bool HasMappedInstancePort(int managementPort, int? requestPort) if (portMapping != null) { - if (_logger.IsEnabled(LogLevel.Trace)) - { - _logger.LogTrace( - "Request received on port {RequestPort}. Allowed by CF_INSTANCE_PORTS mapping: [ Internal: {InternalPort}, ExternalTlsProxy: {ExternalTlsProxy}, InternalTlsProxy: {InternalTlsProxy} ]", - requestPort, portMapping.Internal, portMapping.ExternalTlsProxy, portMapping.InternalTlsProxy); - } + LogPortMappingAllowed(requestPort, portMapping.Internal, portMapping.ExternalTlsProxy, portMapping.InternalTlsProxy); return true; } @@ -104,12 +99,23 @@ private void SetResponseError(HttpContext context, int managementPort) defaultPort = context.Request.Scheme == "http" ? 80 : 443; } - _logger.LogWarning("Access to {Path} on port {Port} denied because 'Management:Endpoints:Port' is set to {ManagementPort}.", context.Request.Path, - defaultPort ?? context.Request.Host.Port, managementPort); + LogAccessDenied(context.Request.Path, defaultPort ?? context.Request.Host.Port, managementPort); context.Response.StatusCode = StatusCodes.Status404NotFound; } + [LoggerMessage(Level = LogLevel.Debug, Message = "Handling request at path {RequestPath} with options path {OptionsPath}.")] + private partial void LogEntering(string? requestPath, string? optionsPath); + + [LoggerMessage(Level = LogLevel.Trace, + Message = + "Request received on port {RequestPort}, allowed by CF_INSTANCE_PORTS mapping with internal port {InternalPort}, external TLS proxy {ExternalTlsProxy} and internal TLS proxy {InternalTlsProxy}.")] + private partial void LogPortMappingAllowed(int? requestPort, int? internalPort, int? externalTlsProxy, int? internalTlsProxy); + + [LoggerMessage(Level = LogLevel.Warning, + Message = "Access to {Path} on port {Port} denied because 'Management:Endpoints:Port' is set to {ManagementPort}.")] + private partial void LogAccessDenied(PathString path, int? port, int managementPort); + private sealed record PortMapping { [JsonPropertyName("internal")] diff --git a/src/Management/src/Endpoint/Middleware/EndpointMiddleware.cs b/src/Management/src/Endpoint/Middleware/EndpointMiddleware.cs index 462fd375a5..6a1c54177c 100644 --- a/src/Management/src/Endpoint/Middleware/EndpointMiddleware.cs +++ b/src/Management/src/Endpoint/Middleware/EndpointMiddleware.cs @@ -13,7 +13,7 @@ namespace Steeltoe.Management.Endpoint.Middleware; -public abstract class EndpointMiddleware : IEndpointMiddleware +public abstract partial class EndpointMiddleware : IEndpointMiddleware { private readonly ILogger _logger; protected IOptionsMonitor ManagementOptionsMonitor { get; } @@ -57,25 +57,24 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate? next) { if (!allowedVerbs.Contains(context.Request.Method)) { - _logger.LogTrace("{Method} method is unavailable at path {Path}.", context.Request.Method, context.Request.Path.Value); + LogUnavailableHttpMethod(context.Request.Method, context.Request.Path.Value); context.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; } else if (!IsValidContentType(context.Request)) { - _logger.LogDebug("Content-Type header '{RequestContentType}' is not supported for this request.", context.Request.ContentType); + LogUnsupportedContentType(context.Request.ContentType); context.Response.StatusCode = (int)HttpStatusCode.UnsupportedMediaType; await context.Response.WriteAsync($"Only the '{ContentType}' content type is supported.", context.RequestAborted); } else if (!IsCompatibleAcceptHeader(context.Request)) { - _logger.LogDebug("Accept header '{AcceptType}' is not supported for this request.", context.Request.Headers.Accept.ToString()); + LogUnsupportedAcceptHeader(context.Request.Headers.Accept.ToString()); context.Response.StatusCode = (int)HttpStatusCode.NotAcceptable; await context.Response.WriteAsync($"Only the '{ContentType}' content type is supported.", context.RequestAborted); } else { - _logger.LogDebug("Reading {Method} request at path {Path} using {MiddlewareType}.", context.Request.Method, context.Request.Path.Value, - GetType()); + LogReadingRequest(context.Request.Method, context.Request.Path.Value, GetType()); TRequest? request = await ParseRequestAsync(context, context.RequestAborted); TResponse response = await InvokeEndpointHandlerAsync(request, context.RequestAborted); @@ -87,7 +86,7 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate? next) } else { - _logger.LogTrace("CanInvoke returned false for {Method} request at path {Path}.", context.Request.Method, context.Request.Path.Value); + LogInvokeDenied(context.Request.Method, context.Request.Path.Value); } context.Response.StatusCode = (int)HttpStatusCode.NotFound; @@ -147,4 +146,19 @@ protected virtual async Task WriteResponseAsync(TResponse response, HttpContext JsonSerializerOptions options = ManagementOptionsMonitor.CurrentValue.SerializerOptions; await JsonSerializer.SerializeAsync(httpContext.Response.Body, response, options, cancellationToken); } + + [LoggerMessage(Level = LogLevel.Trace, Message = "{Method} method is unavailable at path {Path}.")] + private partial void LogUnavailableHttpMethod(string method, string? path); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Content-Type header '{RequestContentType}' is not supported for this request.")] + private partial void LogUnsupportedContentType(string? requestContentType); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Accept header '{AcceptType}' is not supported for this request.")] + private partial void LogUnsupportedAcceptHeader(string acceptType); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Reading {Method} request at path {Path} using {MiddlewareType}.")] + private partial void LogReadingRequest(string method, string? path, Type middlewareType); + + [LoggerMessage(Level = LogLevel.Trace, Message = "CanInvoke returned false for {Method} request at path {Path}.")] + private partial void LogInvokeDenied(string method, string? path); } diff --git a/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminPeriodicRefresh.cs b/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminPeriodicRefresh.cs index 40139c7c9b..6426993f12 100644 --- a/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminPeriodicRefresh.cs +++ b/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminPeriodicRefresh.cs @@ -8,7 +8,7 @@ namespace Steeltoe.Management.Endpoint.SpringBootAdminClient; -internal sealed class SpringBootAdminPeriodicRefresh : IAsyncDisposable +internal sealed partial class SpringBootAdminPeriodicRefresh : IAsyncDisposable { private readonly SpringBootAdminRefreshRunner _runner; private readonly ILogger _logger; @@ -39,12 +39,12 @@ private async Task TimerLoopAsync(TimeSpan interval) { try { - _logger.LogDebug("Starting periodic refresh loop with interval {Interval}.", interval); + LogStartingPeriodicRefreshLoop(interval); bool isFirstTime = true; do { - _logger.LogDebug("Starting refresh cycle."); + LogStartingRefreshCycle(); try { @@ -52,7 +52,7 @@ private async Task TimerLoopAsync(TimeSpan interval) } catch (Exception exception) when (!exception.IsCancellation()) { - _logger.LogWarning(exception, "Refresh cycle failed."); + LogRefreshCycleFailed(exception); } isFirstTime = false; @@ -61,10 +61,7 @@ private async Task TimerLoopAsync(TimeSpan interval) } catch (OperationCanceledException) { -#pragma warning disable S6667 // Logging in a catch clause should pass the caught exception as a parameter. - // Justification: The exception contains no useful information. Logging it suggests something crashed, while this is expected behavior. - _logger.LogDebug("Stopped periodic refresh loop."); -#pragma warning restore S6667 // Logging in a catch clause should pass the caught exception as a parameter. + LogPeriodicRefreshLoopStopped(); } } @@ -75,7 +72,7 @@ private void ChangeInterval(TimeSpan interval) if (safeInterval != _periodicTimer.Period) { _periodicTimer.Period = safeInterval; - _logger.LogDebug("Refresh interval changed to {Interval}.", safeInterval); + LogRefreshIntervalChanged(safeInterval); } } @@ -87,10 +84,10 @@ private static TimeSpan InfiniteWhenZero(TimeSpan interval) public async Task StopAsync(CancellationToken cancellationToken) { - _logger.LogDebug("Signaling to stop periodic refresh loop."); + LogSignalingStop(); await DisposeAsync(); - _logger.LogDebug("Starting cleanup."); + LogStartingCleanup(); await _runner.CleanupAsync(cancellationToken); } @@ -107,4 +104,25 @@ public async ValueTask DisposeAsync() _periodicTimer.Dispose(); } } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Starting periodic refresh loop with interval {Interval}.")] + private partial void LogStartingPeriodicRefreshLoop(TimeSpan interval); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Starting refresh cycle.")] + private partial void LogStartingRefreshCycle(); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Refresh cycle failed.")] + private partial void LogRefreshCycleFailed(Exception exception); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Stopped periodic refresh loop.")] + private partial void LogPeriodicRefreshLoopStopped(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Refresh interval changed to {Interval}.")] + private partial void LogRefreshIntervalChanged(TimeSpan interval); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Signaling to stop periodic refresh loop.")] + private partial void LogSignalingStop(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Starting cleanup.")] + private partial void LogStartingCleanup(); } diff --git a/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminRefreshRunner.cs b/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminRefreshRunner.cs index eaf7784216..fa5d7ba2d0 100644 --- a/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminRefreshRunner.cs +++ b/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminRefreshRunner.cs @@ -13,7 +13,7 @@ namespace Steeltoe.Management.Endpoint.SpringBootAdminClient; -internal sealed class SpringBootAdminRefreshRunner +internal sealed partial class SpringBootAdminRefreshRunner { private readonly AppUrlCalculator _appUrlCalculator; private readonly SpringBootAdminApiClient _springBootAdminApiClient; @@ -56,13 +56,13 @@ public SpringBootAdminRefreshRunner(AppUrlCalculator appUrlCalculator, SpringBoo public async Task RunAsync(bool isFirstTime, CancellationToken cancellationToken) { - _logger.LogDebug("Validating options."); + LogValidatingOptions(); SpringBootAdminClientOptions clientOptions = _clientOptionsMonitor.CurrentValue; ValidateAndSetOptions(clientOptions); if (_lastGoodOptions?.Url != null && !string.Equals(_lastGoodOptions.Url, clientOptions.Url, StringComparison.OrdinalIgnoreCase)) { - _logger.LogDebug("Spring Boot Admin Server URL changed from {LastUrl} to {NewUrl}, unregistering first.", _lastGoodOptions.Url, clientOptions.Url); + LogUrlChanged(_lastGoodOptions.Url, clientOptions.Url); await SafeUnregisterAsync(_lastGoodOptions, cancellationToken); } @@ -130,11 +130,11 @@ private async Task RegisterAsync(SpringBootAdminClientOptions clientOptions, boo if (isFirstTime) { - _logger.LogInformation("Registering with Spring Boot Admin Server at {Url}.", clientOptions.Url); + LogRegisteringFirstTime(clientOptions.Url); } else { - _logger.LogDebug("Registering with Spring Boot Admin Server at {Url}.", clientOptions.Url); + LogRegisteringNotFirstTime(clientOptions.Url); } _lastRegistrationId = await _springBootAdminApiClient.RegisterAsync(app, clientOptions, cancellationToken); @@ -183,7 +183,7 @@ private async Task SafeUnregisterAsync(SpringBootAdminClientOptions clientOption { try { - _logger.LogDebug("Unregistering from Spring Boot Admin Server at {Url}.", clientOptions.Url); + LogUnregistering(clientOptions.Url); await _springBootAdminApiClient.UnregisterAsync(_lastRegistrationId, clientOptions, cancellationToken); _lastRegistrationId = null; } @@ -191,9 +191,27 @@ private async Task SafeUnregisterAsync(SpringBootAdminClientOptions clientOption { if (!exception.IsCancellation()) { - _logger.LogWarning(exception, "Failed to unregister from Spring Boot Admin server at {Url}.", clientOptions.Url); + LogUnregisterFailed(exception, clientOptions.Url); } } } } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Validating options.")] + private partial void LogValidatingOptions(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Spring Boot Admin Server URL changed from {LastUrl} to {NewUrl}, unregistering first.")] + private partial void LogUrlChanged(string? lastUrl, string? newUrl); + + [LoggerMessage(Level = LogLevel.Information, Message = "Registering with Spring Boot Admin Server at {Url}.")] + private partial void LogRegisteringFirstTime(string? url); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Registering with Spring Boot Admin Server at {Url}.")] + private partial void LogRegisteringNotFirstTime(string? url); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Unregistering from Spring Boot Admin Server at {Url}.")] + private partial void LogUnregistering(string? url); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Failed to unregister from Spring Boot Admin server at {Url}.")] + private partial void LogUnregisterFailed(Exception exception, string? url); } diff --git a/src/Management/src/Prometheus/PrometheusExtensions.cs b/src/Management/src/Prometheus/PrometheusExtensions.cs index 7d59c42236..de9520efa4 100644 --- a/src/Management/src/Prometheus/PrometheusExtensions.cs +++ b/src/Management/src/Prometheus/PrometheusExtensions.cs @@ -17,7 +17,7 @@ namespace Steeltoe.Management.Prometheus; -public static class PrometheusExtensions +public static partial class PrometheusExtensions { /// /// Adds the services used by the Steeltoe-configured OpenTelemetry Prometheus exporter and configures the ASP.NET Core middleware pipeline. @@ -140,7 +140,7 @@ public static IApplicationBuilder UsePrometheusActuator(this IApplicationBuilder if (permissionsProvider is null) { - logger.LogWarning("The Cloud Foundry Actuator is required in order to run the Prometheus exporter under the Cloud Foundry context."); + LogCloudFoundryActuatorRequired(logger); } else { @@ -154,13 +154,12 @@ public static IApplicationBuilder UsePrometheusActuator(this IApplicationBuilder if (applyActuatorConventions && !isEndpointRoutingEnabled) { - logger.LogWarning("Customizing endpoints is only supported when using endpoint routing."); + LogEndpointRoutingRequired(logger); } if (managementOptions.Port == 0 && !applyActuatorConventions && configurePrometheusPipeline is null) { - logger.LogWarning( - "The Prometheus endpoint may not be configured securely. Consider using a dedicated management port, adding actuator conventions or configuring the Prometheus middleware pipeline."); + LogPrometheusEndpointNotSecure(logger); } builder.UseOpenTelemetryPrometheusScrapingEndpoint(null, null, endpointPath, ConfigureBranchedPipeline, null); @@ -195,4 +194,16 @@ void ConfigureBranchedPipeline(IApplicationBuilder branchedApplicationBuilder) } } } + + [LoggerMessage(Level = LogLevel.Warning, + Message = "The Cloud Foundry Actuator is required in order to run the Prometheus exporter under the Cloud Foundry context.")] + private static partial void LogCloudFoundryActuatorRequired(ILogger logger); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Customizing endpoints is only supported when using endpoint routing.")] + private static partial void LogEndpointRoutingRequired(ILogger logger); + + [LoggerMessage(Level = LogLevel.Warning, + Message = "The Prometheus endpoint may not be configured securely. " + + "Consider using a dedicated management port, adding actuator conventions or configuring the Prometheus middleware pipeline.")] + private static partial void LogPrometheusEndpointNotSecure(ILogger logger); } diff --git a/src/Management/src/Tasks/TaskHostExtensions.cs b/src/Management/src/Tasks/TaskHostExtensions.cs index 766f2e033e..c52dee9702 100644 --- a/src/Management/src/Tasks/TaskHostExtensions.cs +++ b/src/Management/src/Tasks/TaskHostExtensions.cs @@ -11,7 +11,7 @@ namespace Steeltoe.Management.Tasks; -public static class TaskHostExtensions +public static partial class TaskHostExtensions { /// /// Indicates whether will run an application task, instead of the regular application. @@ -132,7 +132,7 @@ private static async Task RunTaskAsync(string taskName, IServiceProvider service { var loggerFactory = serviceProvider.GetRequiredService(); ILogger logger = loggerFactory.CreateLogger($"{typeof(TaskHostExtensions).Namespace}.CloudFoundryTasks"); - logger.LogError("No task with name '{TaskName}' is registered in the service container.", taskName); + LogTaskNotFound(logger, taskName); } } @@ -147,4 +147,7 @@ private static async Task DisposeHostAsync(IDisposable host) host.Dispose(); } } + + [LoggerMessage(Level = LogLevel.Error, Message = "No task with name '{TaskName}' is registered in the service container.")] + private static partial void LogTaskNotFound(ILogger logger, string taskName); } diff --git a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTestScenarios.cs b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTestScenarios.cs index 133ee605a7..7b7419395a 100644 --- a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTestScenarios.cs +++ b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTestScenarios.cs @@ -13,78 +13,81 @@ internal sealed class CloudFoundrySecurityMiddlewareTestScenarios : TheoryData[0-9a-f-]+),\sOU=organization:(?[0-9a-f-]+)\s\+\sOU=space:(?[0-9a-f-]+)\s\+\sOU=app:(?[0-9a-f-]+)$", - RegexOptions.Compiled | RegexOptions.Singleline, TimeSpan.FromSeconds(1)); - - // This pattern is found on certificates created by Steeltoe - private static readonly Regex SteeltoeInstanceCertificateSubjectRegex = - new(@"^CN=(?[0-9a-f-]+),\sOU=app:(?[0-9a-f-]+)\s\+\sOU=space:(?[0-9a-f-]+)\s\+\sOU=organization:(?[0-9a-f-]+)$", - RegexOptions.Compiled | RegexOptions.Singleline, TimeSpan.FromSeconds(1)); + private const int RegexMatchTimeoutInMilliseconds = 1_000; public string OrgId { get; } public string SpaceId { get; } @@ -32,16 +24,26 @@ private ApplicationInstanceCertificate(string orgId, string spaceId, string appl InstanceId = instanceId; } + // This pattern is found on certificates issued by Diego. + [GeneratedRegex(@"^CN=(?[0-9a-f-]+),\sOU=organization:(?[0-9a-f-]+)\s\+\sOU=space:(?[0-9a-f-]+)\s\+\sOU=app:(?[0-9a-f-]+)$", + RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture, RegexMatchTimeoutInMilliseconds)] + private static partial Regex CloudFoundryInstanceCertificateSubjectRegex(); + + // This pattern is found on certificates created by Steeltoe. + [GeneratedRegex(@"^CN=(?[0-9a-f-]+),\sOU=app:(?[0-9a-f-]+)\s\+\sOU=space:(?[0-9a-f-]+)\s\+\sOU=organization:(?[0-9a-f-]+)$", + RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture, RegexMatchTimeoutInMilliseconds)] + private static partial Regex SteeltoeInstanceCertificateSubjectRegex(); + public static bool TryParse(string certificateSubject, [NotNullWhen(true)] out ApplicationInstanceCertificate? instanceCertificate) { instanceCertificate = null; certificateSubject = certificateSubject.Replace("\"", string.Empty, StringComparison.OrdinalIgnoreCase); - Match instanceMatch = CloudFoundryInstanceCertificateSubjectRegex.Match(certificateSubject); + Match instanceMatch = CloudFoundryInstanceCertificateSubjectRegex().Match(certificateSubject); if (!instanceMatch.Success) { - instanceMatch = SteeltoeInstanceCertificateSubjectRegex.Match(certificateSubject); + instanceMatch = SteeltoeInstanceCertificateSubjectRegex().Match(certificateSubject); } if (instanceMatch.Success) diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs index 7e1fb81fea..b3c251f44b 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs @@ -10,7 +10,7 @@ namespace Steeltoe.Security.Authorization.Certificate; -internal sealed class CertificateAuthorizationHandler : IAuthorizationHandler +internal sealed partial class CertificateAuthorizationHandler : IAuthorizationHandler { private readonly ILogger _logger; private ApplicationInstanceCertificate? _applicationInstanceCertificate; @@ -48,8 +48,7 @@ private void OnCertificateRefresh(CertificateOptions certificateOptions) } else { - _logger.LogError("Identity certificate did not match an expected pattern. Subject was: {CertificateSubject}", - certificateOptions.Certificate.Subject); + LogIdentityCertificateMismatch(certificateOptions.Certificate.Subject); } } @@ -75,8 +74,14 @@ private void HandleCertificateAuthorizationRequirement(AuthorizationHandlerCo } else { - _logger.LogDebug("User has the required claim, but the value doesn't match. Expected {ExpectedClaimValue} but got {ActualClaimValue}", claimValue, - context.User.FindFirstValue(claimType)); + LogClaimValueMismatch(claimValue, context.User.FindFirstValue(claimType)); } } + + [LoggerMessage(Level = LogLevel.Error, Message = "Identity certificate did not match an expected pattern. Subject was '{CertificateSubject}'.")] + private partial void LogIdentityCertificateMismatch(string certificateSubject); + + [LoggerMessage(Level = LogLevel.Debug, + Message = "User has the required claim, but the value doesn't match. Expected '{ExpectedClaimValue}' but got '{ActualClaimValue}'.")] + private partial void LogClaimValueMismatch(string expectedClaimValue, string? actualClaimValue); } diff --git a/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs index d9b497519b..ba2f19982f 100644 --- a/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs @@ -10,7 +10,7 @@ namespace Steeltoe.Security.Authorization.Certificate; -public static class CertificateHttpClientBuilderExtensions +public static partial class CertificateHttpClientBuilderExtensions { /// /// Binds certificate paths in configuration to representing the application instance and attaches the certificate to @@ -94,18 +94,24 @@ public static IHttpClientBuilder AddClientCertificate(this IHttpClientBuilder bu if (certificate != null) { - logger.LogTrace("Adding certificate with subject {CertificateSubject} to outbound requests in header {CertificateHeaderName}", - certificate.Subject, certificateHeaderName); + LogAddingCertificate(logger, certificate.Subject, certificateHeaderName); string b64 = Convert.ToBase64String(certificate.Export(X509ContentType.Cert)); client.DefaultRequestHeaders.Add(certificateHeaderName, b64); } else { - logger.LogError("Failed to find a certificate under the name {CertificateOptionsName}", certificateName); + LogCertificateNotFound(logger, certificateName); } }); return builder; } + + [LoggerMessage(Level = LogLevel.Trace, + Message = "Adding certificate with subject '{CertificateSubject}' to outbound requests in header {CertificateHeaderName}.")] + private static partial void LogAddingCertificate(ILogger logger, string certificateSubject, string certificateHeaderName); + + [LoggerMessage(Level = LogLevel.Error, Message = "Failed to find a certificate under the name {CertificateOptionsName}.")] + private static partial void LogCertificateNotFound(ILogger logger, string certificateOptionsName); } diff --git a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs index 9540636576..2085265d8c 100644 --- a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs +++ b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs @@ -11,7 +11,7 @@ namespace Steeltoe.Security.Authorization.Certificate; -internal sealed class PostConfigureCertificateAuthenticationOptions : IPostConfigureOptions +internal sealed partial class PostConfigureCertificateAuthenticationOptions : IPostConfigureOptions { private readonly IOptionsMonitor _certificateOptionsMonitor; private readonly ILogger _logger; @@ -77,8 +77,7 @@ public void PostConfigure(string? name, CertificateAuthenticationOptions options } else { - _logger.LogError("Identity certificate did not match an expected pattern. Subject was: {CertificateSubject}", - context.ClientCertificate.Subject); + LogIdentityCertificateMismatch(context.ClientCertificate.Subject); } var identity = new ClaimsIdentity(claims, CertificateAuthenticationDefaults.AuthenticationScheme); @@ -89,4 +88,7 @@ public void PostConfigure(string? name, CertificateAuthenticationOptions options } }; } + + [LoggerMessage(Level = LogLevel.Error, Message = "Identity certificate did not match an expected pattern. Subject was '{CertificateSubject}'.")] + private partial void LogIdentityCertificateMismatch(string certificateSubject); } diff --git a/src/Steeltoe.All.sln.DotSettings b/src/Steeltoe.All.sln.DotSettings index 112d0c37d2..767688ffbd 100644 --- a/src/Steeltoe.All.sln.DotSettings +++ b/src/Steeltoe.All.sln.DotSettings @@ -58,6 +58,7 @@ WARNING SUGGESTION DO_NOT_SHOW + DO_NOT_SHOW WARNING DO_NOT_SHOW HINT diff --git a/versions.props b/versions.props index 7281d21072..123a774861 100644 --- a/versions.props +++ b/versions.props @@ -21,7 +21,7 @@ 7.2.* 4.0.* 8.4.* - 10.18.0.131500 + 10.20.0.135146 1.2.0-beta.556 2.0.* 8.15.* From c99e7aa38665a2fded727ca7a1ada83c38f3fc50 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Tue, 3 Mar 2026 16:21:53 +0100 Subject: [PATCH 37/81] Fix broken documentation in Steeltoe 4.x (#1655) --- .../Configuration/ConfigurationServiceCollectionExtensions.cs | 4 ++-- .../Endpoint/Actuators/Health/Availability/LivenessState.cs | 3 ++- .../Endpoint/Actuators/Health/Availability/ReadinessState.cs | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Discovery/src/Configuration/ConfigurationServiceCollectionExtensions.cs b/src/Discovery/src/Configuration/ConfigurationServiceCollectionExtensions.cs index 03ad09ee9d..4d5bd83405 100644 --- a/src/Discovery/src/Configuration/ConfigurationServiceCollectionExtensions.cs +++ b/src/Discovery/src/Configuration/ConfigurationServiceCollectionExtensions.cs @@ -19,7 +19,7 @@ public static class ConfigurationServiceCollectionExtensions /// Build your list of service instances under the configuration prefix discovery:services. /// /// Example configuration in appsettings.json: - /// + /// ]]> /// /// /// diff --git a/src/Management/src/Endpoint/Actuators/Health/Availability/LivenessState.cs b/src/Management/src/Endpoint/Actuators/Health/Availability/LivenessState.cs index e19bbbb7eb..e0e609983d 100644 --- a/src/Management/src/Endpoint/Actuators/Health/Availability/LivenessState.cs +++ b/src/Management/src/Endpoint/Actuators/Health/Availability/LivenessState.cs @@ -6,9 +6,10 @@ namespace Steeltoe.Management.Endpoint.Actuators.Health.Availability; /// /// "Liveness" state of the application. -/// +/// /// An application is considered live when it's running with a correct internal state. "Liveness" failure means that the internal state of the /// application is broken, and we cannot recover from it. As a result, the platform should restart the application. +/// /// public sealed class LivenessState : AvailabilityState { diff --git a/src/Management/src/Endpoint/Actuators/Health/Availability/ReadinessState.cs b/src/Management/src/Endpoint/Actuators/Health/Availability/ReadinessState.cs index b4371f9898..4169a69ae7 100644 --- a/src/Management/src/Endpoint/Actuators/Health/Availability/ReadinessState.cs +++ b/src/Management/src/Endpoint/Actuators/Health/Availability/ReadinessState.cs @@ -6,9 +6,10 @@ namespace Steeltoe.Management.Endpoint.Actuators.Health.Availability; /// /// The Readiness state of the application. -/// +/// /// An application is considered ready when it's and willing to accept traffic. "Readiness" failure means that the /// application is not able to accept traffic and that the infrastructure should not route requests to it. +/// /// public sealed class ReadinessState : AvailabilityState { From 407af878501d78ac0990dde3febd0c83f67c1d83 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 11 Mar 2026 12:33:13 +0100 Subject: [PATCH 38/81] Use json source generator (#1656) * Eureka: Add unit test for ToString on JSON objects * Eureka: Remove unused type * Eureka: Improve existing serialization tests * Eureka: Use JSON source generator * Eureka: Use source generator for ToString debug methods * HttpClients: Use JSON source generator * Replace JsonSerializer usage from certificate tests with simple string replacement * SpringBootAdmin client: Use JSON source generator * Cloud foundry middleware: gracefully handle failure to parse permissions response; don't assume that read_basic_data is always true * Switch to v3 of cloud controller API for permission checks * CloudFoundry actuator middleware: Use JSON source generator * Actuator port middleware: Use JSON source generator * Loggers actuator fixes: Respect serializer options when reading request body, correct nullability, correct invalid tests (don't assume RESET command if unable to parse request body) * Add missing [JsonPropertyName] to JSON-serialized types because 1) this guards against wire-level breaking changes when renaming properties and 2) the names are dictated by Spring, so configuring a global casing policy must not change them * Revert "Switch to v3 of cloud controller API for permission checks" This reverts commit 34914d0bf79e97cbffb150da4ca18672fd8545b2. * Fix broken log level reset in Spring Boot Admin The proper way to RESET is sending an empty JSON object, see https://docs.spring.io/spring-boot/api/rest/actuator/loggers.html. * Revert "SpringBootAdmin client: Use JSON source generator" This reverts commit ad5898f9f757d625e88509306774a1c539788390. * Add test for loggers reset from Apps Manager --- .../Common/HealthChecks/HealthCheckResult.cs | 3 + .../ConfigureCertificateOptionsTest.cs | 27 ++-- .../src/Eureka/AppInfo/ApplicationInfo.cs | 2 +- .../AppInfo/ApplicationInfoCollection.cs | 2 +- .../src/Eureka/AppInfo/DataCenterName.cs | 3 + .../src/Eureka/AppInfo/InstanceInfo.cs | 2 +- src/Discovery/src/Eureka/AppInfo/LeaseInfo.cs | 2 +- src/Discovery/src/Eureka/EurekaClient.cs | 20 +-- .../Transport/DebugJsonSerializerContext.cs | 12 ++ .../Transport/DebugSerializerOptions.cs | 20 --- .../Transport/EurekaJsonSerializerContext.cs | 14 ++ .../Transport/JsonApplicationConverter.cs | 4 +- .../Transport/JsonInstanceInfoConverter.cs | 4 +- .../ServiceInstancesJsonSerializerContext.cs | 11 ++ .../LoadBalancers/ServiceInstancesResolver.cs | 9 +- .../AppInfo/ApplicationInfoCollectionTest.cs | 126 ++++++++++++++++++ .../Eureka.Test/Transport/EurekaClientTest.cs | 8 +- .../Transport/JsonApplicationRootTest.cs | 72 ---------- .../Transport/JsonApplicationTest.cs | 70 ++++------ .../Transport/JsonApplicationsRootTest.cs | 58 +------- .../Transport/JsonApplicationsTest.cs | 79 +++++------ .../Transport/JsonInstanceInfoRootTest.cs | 76 +++-------- .../Transport/JsonInstanceInfoTest.cs | 107 ++++++++++++++- .../Eureka.Test/Transport/JsonLeaseTest.cs | 48 +++++-- .../Transport/JsonSerializationTest.cs | 2 +- .../Configuration/EndpointPermissions.cs | 2 +- .../CloudFoundryJsonSerializerContext.cs | 12 ++ .../CloudFoundrySecurityMiddleware.cs | 2 +- .../CloudFoundry/PermissionsProvider.cs | 47 ++++--- .../DbMigrations/DbMigrationsDescriptor.cs | 5 + .../Actuators/Health/HealthEndpointRequest.cs | 5 + .../Health/HealthEndpointResponse.cs | 4 + .../Actuators/HttpExchanges/HttpExchange.cs | 9 ++ .../HttpExchanges/HttpExchangePrincipal.cs | 3 + .../HttpExchanges/HttpExchangeRequest.cs | 6 + .../HttpExchanges/HttpExchangeResponse.cs | 3 + .../HttpExchanges/HttpExchangeSession.cs | 3 + .../HttpExchanges/HttpExchangesResult.cs | 2 + .../src/Endpoint/Actuators/Hypermedia/Link.cs | 1 + .../Endpoint/Actuators/Hypermedia/Links.cs | 1 + .../Loggers/LoggersEndpointMiddleware.cs | 12 +- .../ManagementPortMiddleware.cs | 4 +- .../PortMappingJsonSerializerContext.cs} | 10 +- .../CloudControllerPermissionsMock.cs | 12 +- .../CloudFoundry/CloudFoundryActuatorTest.cs | 2 +- .../CloudFoundrySecurityMiddlewareTest.cs | 24 ++-- ...dFoundrySecurityMiddlewareTestScenarios.cs | 21 ++- .../CloudFoundry/PermissionsProviderTest.cs | 14 +- .../ComplexDetailsContributor.cs | 12 ++ .../Loggers/LoggersActuatorSerilogTest.cs | 7 +- .../Actuators/Loggers/LoggersActuatorTest.cs | 113 +++++++++++++++- .../Endpoint.Test/ContentNegotiationTest.cs | 3 + .../test/Endpoint.Test/CorsPolicyTest.cs | 1 + 53 files changed, 698 insertions(+), 423 deletions(-) create mode 100644 src/Discovery/src/Eureka/Transport/DebugJsonSerializerContext.cs delete mode 100644 src/Discovery/src/Eureka/Transport/DebugSerializerOptions.cs create mode 100644 src/Discovery/src/Eureka/Transport/EurekaJsonSerializerContext.cs create mode 100644 src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesJsonSerializerContext.cs delete mode 100644 src/Discovery/test/Eureka.Test/Transport/JsonApplicationRootTest.cs create mode 100644 src/Management/src/Endpoint/Actuators/CloudFoundry/CloudFoundryJsonSerializerContext.cs rename src/{Discovery/src/Eureka/Transport/JsonApplicationRoot.cs => Management/src/Endpoint/ManagementPort/PortMappingJsonSerializerContext.cs} (50%) diff --git a/src/Common/src/Common/HealthChecks/HealthCheckResult.cs b/src/Common/src/Common/HealthChecks/HealthCheckResult.cs index 473c5e9936..8d967097e6 100644 --- a/src/Common/src/Common/HealthChecks/HealthCheckResult.cs +++ b/src/Common/src/Common/HealthChecks/HealthCheckResult.cs @@ -19,6 +19,7 @@ public sealed class HealthCheckResult /// /// Used by the health middleware to determine the HTTP Status code. /// + [JsonPropertyName("status")] [JsonConverter(typeof(SnakeCaseAllCapsEnumMemberJsonConverter))] public HealthStatus Status { get; set; } = HealthStatus.Unknown; @@ -28,11 +29,13 @@ public sealed class HealthCheckResult /// /// Currently only used on check failures. /// + [JsonPropertyName("description")] public string? Description { get; set; } /// /// Gets details of the health check. /// + [JsonPropertyName("details")] [JsonIgnoreEmptyCollection] public IDictionary Details { get; } = new Dictionary(); } diff --git a/src/Common/test/Certificates.Test/ConfigureCertificateOptionsTest.cs b/src/Common/test/Certificates.Test/ConfigureCertificateOptionsTest.cs index a2a64797d2..2a93ec848b 100644 --- a/src/Common/test/Certificates.Test/ConfigureCertificateOptionsTest.cs +++ b/src/Common/test/Certificates.Test/ConfigureCertificateOptionsTest.cs @@ -5,7 +5,6 @@ using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Text.Json; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -212,19 +211,25 @@ public async Task CertificateOptions_update_on_changed_path(string certificateNa private static string BuildAppSettingsJson(string certificateName, string certificatePath, string keyPath) { - string certificateBlock = $""" - "CertificateFilePath": {JsonSerializer.Serialize(certificatePath)}, - "PrivateKeyFilePath": {JsonSerializer.Serialize(keyPath)} - """; - - string namedCertificateSection = string.IsNullOrEmpty(certificateName) - ? certificateBlock - : $"{JsonSerializer.Serialize(certificateName)}: {{ {certificateBlock} }}"; + string escapedCertificatePath = certificatePath.Replace(@"\", @"\\", StringComparison.Ordinal); + string escapedKeyPath = keyPath.Replace(@"\", @"\\", StringComparison.Ordinal); - return $$""" + return string.IsNullOrEmpty(certificateName) + ? $$""" + { + "Certificates": { + "CertificateFilePath": "{{escapedCertificatePath}}", + "PrivateKeyFilePath": "{{escapedKeyPath}}" + } + } + """ + : $$""" { "Certificates": { - {{namedCertificateSection}} + "{{certificateName}}": { + "CertificateFilePath": "{{escapedCertificatePath}}", + "PrivateKeyFilePath": "{{escapedKeyPath}}" + } } } """; diff --git a/src/Discovery/src/Eureka/AppInfo/ApplicationInfo.cs b/src/Discovery/src/Eureka/AppInfo/ApplicationInfo.cs index df11be4347..257a55202d 100644 --- a/src/Discovery/src/Eureka/AppInfo/ApplicationInfo.cs +++ b/src/Discovery/src/Eureka/AppInfo/ApplicationInfo.cs @@ -53,7 +53,7 @@ private List GetInstancesSnapshot() /// public override string ToString() { - return JsonSerializer.Serialize(this, DebugSerializerOptions.Instance); + return JsonSerializer.Serialize(this, DebugJsonSerializerContext.Default.ApplicationInfo); } internal void Add(InstanceInfo instance) diff --git a/src/Discovery/src/Eureka/AppInfo/ApplicationInfoCollection.cs b/src/Discovery/src/Eureka/AppInfo/ApplicationInfoCollection.cs index 28e0fdbae6..e42f48370e 100644 --- a/src/Discovery/src/Eureka/AppInfo/ApplicationInfoCollection.cs +++ b/src/Discovery/src/Eureka/AppInfo/ApplicationInfoCollection.cs @@ -73,7 +73,7 @@ internal ReadOnlyCollection GetInstancesByVipAddress(string vipAdd /// public override string ToString() { - return JsonSerializer.Serialize(this, DebugSerializerOptions.Instance); + return JsonSerializer.Serialize(this, DebugJsonSerializerContext.Default.ApplicationInfoCollection); } IEnumerator IEnumerable.GetEnumerator() diff --git a/src/Discovery/src/Eureka/AppInfo/DataCenterName.cs b/src/Discovery/src/Eureka/AppInfo/DataCenterName.cs index 5cf05013d3..ede53c628b 100644 --- a/src/Discovery/src/Eureka/AppInfo/DataCenterName.cs +++ b/src/Discovery/src/Eureka/AppInfo/DataCenterName.cs @@ -6,8 +6,11 @@ #pragma warning disable SA1602 // Enumeration items should be documented #endif +using System.Text.Json.Serialization; + namespace Steeltoe.Discovery.Eureka.AppInfo; +[JsonConverter(typeof(JsonStringEnumConverter))] public enum DataCenterName { Netflix, diff --git a/src/Discovery/src/Eureka/AppInfo/InstanceInfo.cs b/src/Discovery/src/Eureka/AppInfo/InstanceInfo.cs index d89d9e6aae..0015f8078e 100644 --- a/src/Discovery/src/Eureka/AppInfo/InstanceInfo.cs +++ b/src/Discovery/src/Eureka/AppInfo/InstanceInfo.cs @@ -268,7 +268,7 @@ public override int GetHashCode() /// public override string ToString() { - return JsonSerializer.Serialize(this, DebugSerializerOptions.Instance); + return JsonSerializer.Serialize(this, DebugJsonSerializerContext.Default.InstanceInfo); } internal static InstanceInfo FromConfiguration(EurekaInstanceOptions options, TimeProvider timeProvider) diff --git a/src/Discovery/src/Eureka/AppInfo/LeaseInfo.cs b/src/Discovery/src/Eureka/AppInfo/LeaseInfo.cs index ecab87747e..2a1d76b2f1 100644 --- a/src/Discovery/src/Eureka/AppInfo/LeaseInfo.cs +++ b/src/Discovery/src/Eureka/AppInfo/LeaseInfo.cs @@ -48,7 +48,7 @@ private LeaseInfo() /// public override string ToString() { - return JsonSerializer.Serialize(this, DebugSerializerOptions.Instance); + return JsonSerializer.Serialize(this, DebugJsonSerializerContext.Default.LeaseInfo); } internal static LeaseInfo? FromJson(JsonLeaseInfo? jsonLeaseInfo) diff --git a/src/Discovery/src/Eureka/EurekaClient.cs b/src/Discovery/src/Eureka/EurekaClient.cs index 47fa8b7de6..ad4b97e420 100644 --- a/src/Discovery/src/Eureka/EurekaClient.cs +++ b/src/Discovery/src/Eureka/EurekaClient.cs @@ -8,7 +8,6 @@ using System.Net.Http.Json; using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -37,21 +36,6 @@ public sealed partial class EurekaClient private static readonly Task TaskOfNull = Task.FromResult(null); private static readonly TimeSpan GetAccessTokenTimeout = TimeSpan.FromSeconds(10); - private static readonly JsonSerializerOptions RequestSerializerOptions = new() - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - private static readonly JsonSerializerOptions ResponseSerializerOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = - { - new JsonApplicationConverter(), - new JsonInstanceInfoConverter() - } - }; - private readonly IHttpClientFactory _httpClientFactory; private readonly IOptionsMonitor _optionsMonitor; private readonly EurekaServiceUriStateManager _eurekaServiceUriStateManager; @@ -98,7 +82,7 @@ public async Task RegisterAsync(InstanceInfo instance, CancellationToken cancell string requestBody = JsonSerializer.Serialize(new JsonInstanceInfoRoot { Instance = instance.ToJson() - }, RequestSerializerOptions); + }, EurekaJsonSerializerContext.Default.JsonInstanceInfoRoot); string path = $"apps/{WebUtility.UrlEncode(instance.AppName)}"; await ExecuteRequestAsync(HttpMethod.Post, path, null, requestBody, cancellationToken); @@ -224,7 +208,7 @@ private async Task GetApplicationsAtPathAsync(string { return await ExecuteRequestAsync(HttpMethod.Get, path, null, null, async response => { - var root = await response.Content.ReadFromJsonAsync(ResponseSerializerOptions, cancellationToken); + JsonApplicationsRoot? root = await response.Content.ReadFromJsonAsync(EurekaJsonSerializerContext.Default.JsonApplicationsRoot, cancellationToken); return ApplicationInfoCollection.FromJson(root?.Applications, _timeProvider); }, cancellationToken); } diff --git a/src/Discovery/src/Eureka/Transport/DebugJsonSerializerContext.cs b/src/Discovery/src/Eureka/Transport/DebugJsonSerializerContext.cs new file mode 100644 index 0000000000..07fabaacc2 --- /dev/null +++ b/src/Discovery/src/Eureka/Transport/DebugJsonSerializerContext.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; +using Steeltoe.Discovery.Eureka.AppInfo; + +namespace Steeltoe.Discovery.Eureka.Transport; + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(ApplicationInfoCollection))] +internal sealed partial class DebugJsonSerializerContext : JsonSerializerContext; diff --git a/src/Discovery/src/Eureka/Transport/DebugSerializerOptions.cs b/src/Discovery/src/Eureka/Transport/DebugSerializerOptions.cs deleted file mode 100644 index 4a75651bb1..0000000000 --- a/src/Discovery/src/Eureka/Transport/DebugSerializerOptions.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Text.Encodings.Web; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Steeltoe.Discovery.Eureka.Transport; - -internal static class DebugSerializerOptions -{ - public static JsonSerializerOptions Instance { get; } = new() - { - WriteIndented = true, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - ReferenceHandler = ReferenceHandler.IgnoreCycles - }; -} diff --git a/src/Discovery/src/Eureka/Transport/EurekaJsonSerializerContext.cs b/src/Discovery/src/Eureka/Transport/EurekaJsonSerializerContext.cs new file mode 100644 index 0000000000..861981f501 --- /dev/null +++ b/src/Discovery/src/Eureka/Transport/EurekaJsonSerializerContext.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; + +namespace Steeltoe.Discovery.Eureka.Transport; + +[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(JsonApplicationsRoot))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(JsonInstanceInfoRoot))] +[JsonSerializable(typeof(List))] +internal sealed partial class EurekaJsonSerializerContext : JsonSerializerContext; diff --git a/src/Discovery/src/Eureka/Transport/JsonApplicationConverter.cs b/src/Discovery/src/Eureka/Transport/JsonApplicationConverter.cs index 906aaa80f1..7129f7d517 100644 --- a/src/Discovery/src/Eureka/Transport/JsonApplicationConverter.cs +++ b/src/Discovery/src/Eureka/Transport/JsonApplicationConverter.cs @@ -13,10 +13,10 @@ internal sealed class JsonApplicationConverter : JsonConverter>(ref reader, options)!; + return JsonSerializer.Deserialize(ref reader, EurekaJsonSerializerContext.Default.ListJsonApplication)!; } - var application = JsonSerializer.Deserialize(ref reader, options); + JsonApplication? application = JsonSerializer.Deserialize(ref reader, EurekaJsonSerializerContext.Default.JsonApplication); return application != null ? [application] : []; } diff --git a/src/Discovery/src/Eureka/Transport/JsonInstanceInfoConverter.cs b/src/Discovery/src/Eureka/Transport/JsonInstanceInfoConverter.cs index 2b751a374c..56f2f21ed7 100644 --- a/src/Discovery/src/Eureka/Transport/JsonInstanceInfoConverter.cs +++ b/src/Discovery/src/Eureka/Transport/JsonInstanceInfoConverter.cs @@ -13,10 +13,10 @@ internal sealed class JsonInstanceInfoConverter : JsonConverter>(ref reader, options)!; + return JsonSerializer.Deserialize(ref reader, EurekaJsonSerializerContext.Default.ListJsonInstanceInfo)!; } - var instanceInfo = JsonSerializer.Deserialize(ref reader, options); + JsonInstanceInfo? instanceInfo = JsonSerializer.Deserialize(ref reader, EurekaJsonSerializerContext.Default.JsonInstanceInfo); return instanceInfo != null ? [instanceInfo] : []; } diff --git a/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesJsonSerializerContext.cs b/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesJsonSerializerContext.cs new file mode 100644 index 0000000000..3193aebbc5 --- /dev/null +++ b/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesJsonSerializerContext.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; + +namespace Steeltoe.Discovery.HttpClients.LoadBalancers; + +[JsonSourceGenerationOptions] +[JsonSerializable(typeof(ServiceInstancesResolver.JsonSerializableServiceInstance[]))] +internal sealed partial class ServiceInstancesJsonSerializerContext : JsonSerializerContext; diff --git a/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs b/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs index 6a13c24690..e1231d171d 100644 --- a/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs +++ b/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs @@ -116,7 +116,8 @@ public async Task> ResolveInstancesAsync(string serviceI { if (cacheValue is { Length: > 0 }) { - var serializableInstances = JsonSerializer.Deserialize>(cacheValue); + JsonSerializableServiceInstance[]? serializableInstances = + JsonSerializer.Deserialize(cacheValue, ServiceInstancesJsonSerializerContext.Default.JsonSerializableServiceInstanceArray); if (serializableInstances != null) { @@ -127,10 +128,10 @@ public async Task> ResolveInstancesAsync(string serviceI return null; } - private static byte[] ToCacheValue(IEnumerable instances) + private static byte[] ToCacheValue(List instances) { JsonSerializableServiceInstance[] serializableInstances = instances.Select(JsonSerializableServiceInstance.CopyFrom).ToArray(); - return JsonSerializer.SerializeToUtf8Bytes(serializableInstances); + return JsonSerializer.SerializeToUtf8Bytes(serializableInstances, ServiceInstancesJsonSerializerContext.Default.JsonSerializableServiceInstanceArray); } [LoggerMessage(Level = LogLevel.Warning, Message = "No discovery clients are registered.")] @@ -142,7 +143,7 @@ private static byte[] ToCacheValue(IEnumerable instances) [LoggerMessage(Level = LogLevel.Error, Message = "Failed to get instances from {DiscoveryClient}.")] private partial void LogFailedToGetInstances(Exception exception, Type discoveryClient); - private sealed class JsonSerializableServiceInstance : IServiceInstance + internal sealed class JsonSerializableServiceInstance : IServiceInstance { // Trust that deserialized instances meet the IServiceInstance contract, so suppress nullability warnings. diff --git a/src/Discovery/test/Eureka.Test/AppInfo/ApplicationInfoCollectionTest.cs b/src/Discovery/test/Eureka.Test/AppInfo/ApplicationInfoCollectionTest.cs index 6648a50ce8..5dca00ae23 100644 --- a/src/Discovery/test/Eureka.Test/AppInfo/ApplicationInfoCollectionTest.cs +++ b/src/Discovery/test/Eureka.Test/AppInfo/ApplicationInfoCollectionTest.cs @@ -3,8 +3,11 @@ // See the LICENSE file in the project root for more information. using System.Collections.ObjectModel; +using FluentAssertions.Extensions; using Steeltoe.Discovery.Eureka.AppInfo; +using Steeltoe.Discovery.Eureka.Configuration; using Steeltoe.Discovery.Eureka.Transport; +using Steeltoe.Discovery.Eureka.Util; namespace Steeltoe.Discovery.Eureka.Test.AppInfo; @@ -797,4 +800,127 @@ public void FromJsonApplications_WithMissingInstanceId() app.Name.Should().Be("myApp"); app.Instances.Should().BeEmpty(); } + + [Fact] + public void ToString_ReturnsExpected() + { + var apps = new ApplicationInfoCollection([ + new ApplicationInfo("ServiceA", [ + new InstanceInfo("full-instance-001", "ServiceA", "prod-server-01.example.com", "10.20.30.40", new DataCenterInfo + { + Name = DataCenterName.Amazon + }, TimeProvider.System) + { + AppGroupName = "ServiceGroup", + Status = InstanceStatus.Up, + OverriddenStatus = InstanceStatus.OutOfService, + VipAddress = "service-a-vip", + SecureVipAddress = "service-a-secure-vip", + NonSecurePort = 8080, + IsNonSecurePortEnabled = true, + SecurePort = 8443, + IsSecurePortEnabled = true, + HomePageUrl = "http://prod-server-01.example.com:8080/", + StatusPageUrl = "http://prod-server-01.example.com:8080/actuator/info", + HealthCheckUrl = "http://prod-server-01.example.com:8080/actuator/health", + SecureHealthCheckUrl = "https://prod-server-01.example.com:8443/actuator/health", + LeaseInfo = LeaseInfo.FromJson(new JsonLeaseInfo + { + RenewalIntervalInSeconds = 30, + DurationInSeconds = 90, + RegistrationTimestamp = DateTimeConversions.ToJavaMilliseconds(15.June(2024).At(14, 30, 55, 123).AsUtc()), + LastRenewalTimestamp = DateTimeConversions.ToJavaMilliseconds(18.June(2024).At(23, 1, 27, 789).AsUtc()), + EvictionTimestamp = DateTimeConversions.ToJavaMilliseconds(19.June(2024).At(1, 1, 27, 789).AsUtc()), + ServiceUpTimestamp = DateTimeConversions.ToJavaMilliseconds(15.June(2024).At(14, 31, 2, 456).AsUtc()) + }), + ActionType = ActionType.Added, + Metadata = new Dictionary + { + ["datacenter"] = "us-east-1", + ["availability-zone"] = "us-east-1a", + ["version"] = "3.2.1", + ["environment"] = "production", + ["deployment"] = "blue", + ["team"] = "platform" + } + }, + new InstanceInfo("minimal-instance-001", "ServiceA", "prod-server-02.local", "10.20.30.41", new DataCenterInfo + { + Name = DataCenterName.Netflix + }, TimeProvider.System) + ]), + new ApplicationInfo("EmptyService") + ]); + + apps.ToString().Should().Be(""" + [ + { + "Name": "EmptyService", + "Instances": [] + }, + { + "Name": "ServiceA", + "Instances": [ + { + "InstanceId": "full-instance-001", + "AppName": "ServiceA", + "AppGroupName": "ServiceGroup", + "HostName": "prod-server-01.example.com", + "IPAddress": "10.20.30.40", + "DataCenterInfo": { + "Name": "Amazon" + }, + "VipAddress": "service-a-vip", + "SecureVipAddress": "service-a-secure-vip", + "NonSecurePort": 8080, + "IsNonSecurePortEnabled": true, + "SecurePort": 8443, + "IsSecurePortEnabled": true, + "Status": "UP", + "OverriddenStatus": "OUT_OF_SERVICE", + "EffectiveStatus": "OUT_OF_SERVICE", + "HomePageUrl": "http://prod-server-01.example.com:8080/", + "StatusPageUrl": "http://prod-server-01.example.com:8080/actuator/info", + "HealthCheckUrl": "http://prod-server-01.example.com:8080/actuator/health", + "SecureHealthCheckUrl": "https://prod-server-01.example.com:8443/actuator/health", + "LeaseInfo": { + "RenewalInterval": "00:00:30", + "Duration": "00:01:30", + "RegistrationTimeUtc": "2024-06-15T14:30:55.123Z", + "LastRenewalTimeUtc": "2024-06-18T23:01:27.789Z", + "EvictionTimeUtc": "2024-06-19T01:01:27.789Z", + "ServiceUpTimeUtc": "2024-06-15T14:31:02.456Z" + }, + "Metadata": { + "datacenter": "us-east-1", + "availability-zone": "us-east-1a", + "version": "3.2.1", + "environment": "production", + "deployment": "blue", + "team": "platform" + }, + "ActionType": "ADDED", + "IsDirty": false + }, + { + "InstanceId": "minimal-instance-001", + "AppName": "ServiceA", + "HostName": "prod-server-02.local", + "IPAddress": "10.20.30.41", + "DataCenterInfo": { + "Name": "Netflix" + }, + "NonSecurePort": 0, + "IsNonSecurePortEnabled": false, + "SecurePort": 0, + "IsSecurePortEnabled": false, + "EffectiveStatus": "UNKNOWN", + "Metadata": {}, + "IsDirty": false + } + ] + } + ] + """); + } } diff --git a/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs b/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs index 9e5958cacf..b69254588b 100644 --- a/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs +++ b/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs @@ -4,7 +4,7 @@ using System.Net; using System.Text; -using System.Text.Json; +using System.Text.Json.Nodes; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using RichardSzalay.MockHttp; @@ -327,7 +327,7 @@ public async Task RegisterAsync_SendsRequestToServer() { using var capturingLoggerProvider = new CapturingLoggerProvider(category => category.StartsWith("Steeltoe.", StringComparison.Ordinal)); - using JsonDocument requestDocument = JsonDocument.Parse(""" + string jsonRequest = JsonNode.Parse(""" { "instance": { "instanceId": "some", @@ -354,9 +354,7 @@ public async Task RegisterAsync_SendsRequestToServer() "lastDirtyTimestamp": "1708427732823" } } - """); - - string jsonRequest = JsonSerializer.Serialize(requestDocument); + """)!.ToJsonString(); var services = new ServiceCollection(); services.AddLogging(options => options.SetMinimumLevel(LogLevel.Trace).AddProvider(capturingLoggerProvider)); diff --git a/src/Discovery/test/Eureka.Test/Transport/JsonApplicationRootTest.cs b/src/Discovery/test/Eureka.Test/Transport/JsonApplicationRootTest.cs deleted file mode 100644 index 5df09733ae..0000000000 --- a/src/Discovery/test/Eureka.Test/Transport/JsonApplicationRootTest.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Text.Json; -using Steeltoe.Discovery.Eureka.Transport; - -namespace Steeltoe.Discovery.Eureka.Test.Transport; - -public sealed class JsonApplicationRootTest -{ - [Fact] - public void Deserialize_GoodJson() - { - const string json = """ - { - "application": { - "name": "FOO", - "instance": [ - { - "instanceId": "localhost:foo", - "hostName": "localhost", - "app": "FOO", - "ipAddr": "192.168.56.1", - "status": "UP", - "overriddenStatus": "UNKNOWN", - "port": { - "$": 8080, - "@enabled": "true" - }, - "securePort": { - "$": 443, - "@enabled": "false" - }, - "countryId": 1, - "dataCenterInfo": { - "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", - "name": "MyOwn" - }, - "leaseInfo": { - "renewalIntervalInSecs": 30, - "durationInSecs": 90, - "registrationTimestamp": 1458152330783, - "lastRenewalTimestamp": 1458243422342, - "evictionTimestamp": 0, - "serviceUpTimestamp": 1458152330783 - }, - "metadata": { - "@class": "java.util.Collections$EmptyMap" - }, - "homePageUrl": "http://localhost:8080/", - "statusPageUrl": "http://localhost:8080/info", - "healthCheckUrl": "http://localhost:8080/health", - "vipAddress": "foo", - "isCoordinatingDiscoveryServer": "false", - "lastUpdatedTimestamp": "1458152330783", - "lastDirtyTimestamp": "1458152330696", - "actionType": "ADDED" - } - ] - } - } - """; - - var result = JsonSerializer.Deserialize(json); - - result.Should().NotBeNull(); - result.Application.Should().NotBeNull(); - result.Application.Name.Should().Be("FOO"); - result.Application.Instances.Should().ContainSingle(); - } -} diff --git a/src/Discovery/test/Eureka.Test/Transport/JsonApplicationTest.cs b/src/Discovery/test/Eureka.Test/Transport/JsonApplicationTest.cs index 2bebc8ce36..e528bb2e64 100644 --- a/src/Discovery/test/Eureka.Test/Transport/JsonApplicationTest.cs +++ b/src/Discovery/test/Eureka.Test/Transport/JsonApplicationTest.cs @@ -10,62 +10,48 @@ namespace Steeltoe.Discovery.Eureka.Test.Transport; public sealed class JsonApplicationTest { [Fact] - public void Deserialize_GoodJson() + public void Deserialize_InstanceArray() { const string json = """ { "name": "FOO", "instance": [ { - "instanceId": "localhost:foo", - "hostName": "localhost", - "app": "FOO", - "ipAddr": "192.168.56.1", - "status": "UP", - "overriddenStatus": "UNKNOWN", - "port": { - "$": 8080, - "@enabled": "true" - }, - "securePort": { - "$": 443, - "@enabled": "false" - }, - "countryId": 1, - "dataCenterInfo": { - "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", - "name": "MyOwn" - }, - "leaseInfo": { - "renewalIntervalInSecs": 30, - "durationInSecs": 90, - "registrationTimestamp": 1457714988223, - "lastRenewalTimestamp": 1457716158319, - "evictionTimestamp": 0, - "serviceUpTimestamp": 1457714988223 - }, - "metadata": { - "@class": "java.util.Collections$EmptyMap" - }, - "homePageUrl": "http://localhost:8080/", - "statusPageUrl": "http://localhost:8080/info", - "healthCheckUrl": "http://localhost:8080/health", - "vipAddress": "foo", - "isCoordinatingDiscoveryServer": "false", - "lastUpdatedTimestamp": "1457714988223", - "lastDirtyTimestamp": "1457714988172", - "actionType": "ADDED" + "instanceId": "localhost:foo" } ] } """; - var result = JsonSerializer.Deserialize(json); + JsonApplication? result = JsonSerializer.Deserialize(json, EurekaJsonSerializerContext.Default.JsonApplication); result.Should().NotBeNull(); result.Name.Should().Be("FOO"); - result.Instances.Should().ContainSingle(); - // Rest is validated by JsonInstanceInfoTest + JsonInstanceInfo? instance = result.Instances.Should().ContainSingle().Subject; + instance.Should().NotBeNull(); + instance.InstanceId.Should().Be("localhost:foo"); + } + + [Fact] + public void Deserialize_InstanceSingleElement() + { + const string json = """ + { + "name": "FOO", + "instance": { + "instanceId": "localhost:foo" + } + } + """; + + JsonApplication? result = JsonSerializer.Deserialize(json, EurekaJsonSerializerContext.Default.JsonApplication); + + result.Should().NotBeNull(); + result.Name.Should().Be("FOO"); + + JsonInstanceInfo? instance = result.Instances.Should().ContainSingle().Subject; + instance.Should().NotBeNull(); + instance.InstanceId.Should().Be("localhost:foo"); } } diff --git a/src/Discovery/test/Eureka.Test/Transport/JsonApplicationsRootTest.cs b/src/Discovery/test/Eureka.Test/Transport/JsonApplicationsRootTest.cs index fcbd465e8d..5e3c9a94b1 100644 --- a/src/Discovery/test/Eureka.Test/Transport/JsonApplicationsRootTest.cs +++ b/src/Discovery/test/Eureka.Test/Transport/JsonApplicationsRootTest.cs @@ -10,69 +10,17 @@ namespace Steeltoe.Discovery.Eureka.Test.Transport; public sealed class JsonApplicationsRootTest { [Fact] - public void Deserialize_GoodJson() + public void Deserialize() { const string json = """ { - "applications": { - "versions__delta": "1", - "apps__hashcode": "UP_1_", - "application": [ - { - "name": "FOO", - "instance": [ - { - "instanceId": "localhost:foo", - "hostName": "localhost", - "app": "FOO", - "ipAddr": "192.168.56.1", - "status": "UP", - "overriddenStatus": "UNKNOWN", - "port": { - "$": 8080, - "@enabled": "true" - }, - "securePort": { - "$": 443, - "@enabled": "false" - }, - "countryId": 1, - "dataCenterInfo": { - "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", - "name": "MyOwn" - }, - "leaseInfo": { - "renewalIntervalInSecs": 30, - "durationInSecs": 90, - "registrationTimestamp": 1457714988223, - "lastRenewalTimestamp": 1457716158319, - "evictionTimestamp": 0, - "serviceUpTimestamp": 1457714988223 - }, - "metadata": { - "@class": "java.util.Collections$EmptyMap" - }, - "homePageUrl": "http://localhost:8080/", - "statusPageUrl": "http://localhost:8080/info", - "healthCheckUrl": "http://localhost:8080/health", - "vipAddress": "foo", - "isCoordinatingDiscoveryServer": "false", - "lastUpdatedTimestamp": "1457714988223", - "lastDirtyTimestamp": "1457714988172", - "actionType": "ADDED" - } - ] - } - ] - } + "applications": {} } """; - var result = JsonSerializer.Deserialize(json); + JsonApplicationsRoot? result = JsonSerializer.Deserialize(json, EurekaJsonSerializerContext.Default.JsonApplicationsRoot); result.Should().NotBeNull(); result.Applications.Should().NotBeNull(); - - // Rest is validated by JsonApplicationsTest } } diff --git a/src/Discovery/test/Eureka.Test/Transport/JsonApplicationsTest.cs b/src/Discovery/test/Eureka.Test/Transport/JsonApplicationsTest.cs index 60f22290c5..84696bdf91 100644 --- a/src/Discovery/test/Eureka.Test/Transport/JsonApplicationsTest.cs +++ b/src/Discovery/test/Eureka.Test/Transport/JsonApplicationsTest.cs @@ -10,7 +10,31 @@ namespace Steeltoe.Discovery.Eureka.Test.Transport; public sealed class JsonApplicationsTest { [Fact] - public void Deserialize_GoodJson() + public void Deserialize_ApplicationSingleElement() + { + const string json = """ + { + "versions__delta": "1", + "apps__hashcode": "UP_1_", + "application": { + "name": "FOO" + } + } + """; + + JsonApplications? result = JsonSerializer.Deserialize(json, EurekaJsonSerializerContext.Default.JsonApplications); + + result.Should().NotBeNull(); + result.VersionDelta.Should().Be(1); + result.AppsHashCode.Should().Be("UP_1_"); + + JsonApplication? app = result.Applications.Should().ContainSingle().Subject; + app.Should().NotBeNull(); + app.Name.Should().Be("FOO"); + } + + [Fact] + public void Deserialize_ApplicationArray() { const string json = """ { @@ -18,61 +42,20 @@ public void Deserialize_GoodJson() "apps__hashcode": "UP_1_", "application": [ { - "name": "FOO", - "instance": [ - { - "instanceId": "localhost:foo", - "hostName": "localhost", - "app": "FOO", - "ipAddr": "192.168.56.1", - "status": "UP", - "overriddenStatus": "UNKNOWN", - "port": { - "$": 8080, - "@enabled": "true" - }, - "securePort": { - "$": 443, - "@enabled": "false" - }, - "countryId": 1, - "dataCenterInfo": { - "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", - "name": "MyOwn" - }, - "leaseInfo": { - "renewalIntervalInSecs": 30, - "durationInSecs": 90, - "registrationTimestamp": 1457714988223, - "lastRenewalTimestamp": 1457716158319, - "evictionTimestamp": 0, - "serviceUpTimestamp": 1457714988223 - }, - "metadata": { - "@class": "java.util.Collections$EmptyMap" - }, - "homePageUrl": "http://localhost:8080/", - "statusPageUrl": "http://localhost:8080/info", - "healthCheckUrl": "http://localhost:8080/health", - "vipAddress": "foo", - "isCoordinatingDiscoveryServer": "false", - "lastUpdatedTimestamp": "1457714988223", - "lastDirtyTimestamp": "1457714988172", - "actionType": "ADDED" - } - ] + "name": "FOO" } ] } """; - var result = JsonSerializer.Deserialize(json); + JsonApplications? result = JsonSerializer.Deserialize(json, EurekaJsonSerializerContext.Default.JsonApplications); result.Should().NotBeNull(); - result.AppsHashCode.Should().Be("UP_1_"); result.VersionDelta.Should().Be(1); - result.Applications.Should().ContainSingle(); + result.AppsHashCode.Should().Be("UP_1_"); - // Rest is validated by JsonApplicationTest + JsonApplication? app = result.Applications.Should().ContainSingle().Subject; + app.Should().NotBeNull(); + app.Name.Should().Be("FOO"); } } diff --git a/src/Discovery/test/Eureka.Test/Transport/JsonInstanceInfoRootTest.cs b/src/Discovery/test/Eureka.Test/Transport/JsonInstanceInfoRootTest.cs index 6eb99be928..83903f2149 100644 --- a/src/Discovery/test/Eureka.Test/Transport/JsonInstanceInfoRootTest.cs +++ b/src/Discovery/test/Eureka.Test/Transport/JsonInstanceInfoRootTest.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Text.Json; -using Steeltoe.Discovery.Eureka.AppInfo; +using Steeltoe.Common.TestResources; using Steeltoe.Discovery.Eureka.Transport; namespace Steeltoe.Discovery.Eureka.Test.Transport; @@ -11,68 +11,34 @@ namespace Steeltoe.Discovery.Eureka.Test.Transport; public sealed class JsonInstanceInfoRootTest { [Fact] - public void Deserialize_GoodJson() + public void Serialize() + { + var root = new JsonInstanceInfoRoot + { + Instance = new JsonInstanceInfo() + }; + + string result = JsonSerializer.Serialize(root, EurekaJsonSerializerContext.Default.JsonInstanceInfoRoot); + + result.Should().BeJson(""" + { + "instance": {} + } + """); + } + + [Fact] + public void Deserialize() { const string json = """ { - "instance": { - "instanceId": "DESKTOP-GNQ5SUT", - "app": "FOOBAR", - "appGroupName": null, - "ipAddr": "192.168.0.147", - "sid": "na", - "port": { - "@enabled": true, - "$": 80 - }, - "securePort": { - "@enabled": false, - "$": 443 - }, - "homePageUrl": "http://DESKTOP-GNQ5SUT:80/", - "statusPageUrl": "http://DESKTOP-GNQ5SUT:80/Status", - "healthCheckUrl": "http://DESKTOP-GNQ5SUT:80/health-check", - "secureHealthCheckUrl": null, - "vipAddress": "DESKTOP-GNQ5SUT:80", - "secureVipAddress": "DESKTOP-GNQ5SUT:443", - "countryId": 1, - "dataCenterInfo": { - "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", - "name": "MyOwn" - }, - "hostName": "DESKTOP-GNQ5SUT", - "status": "UP", - "overriddenStatus": "UNKNOWN", - "leaseInfo": { - "renewalIntervalInSecs": 30, - "durationInSecs": 90, - "registrationTimestamp": 0, - "lastRenewalTimestamp": 0, - "renewalTimestamp": 0, - "evictionTimestamp": 0, - "serviceUpTimestamp": 0 - }, - "isCoordinatingDiscoveryServer": false, - "metadata": { - "@class": "java.util.Collections$EmptyMap", - "metadata": null - }, - "lastUpdatedTimestamp": 1458116137663, - "lastDirtyTimestamp": 1458116137663, - "actionType": "ADDED", - "asgName": null - } + "instance": {} } """; - var result = JsonSerializer.Deserialize(json); + JsonInstanceInfoRoot? result = JsonSerializer.Deserialize(json, EurekaJsonSerializerContext.Default.JsonInstanceInfoRoot); result.Should().NotBeNull(); result.Instance.Should().NotBeNull(); - - // Random check some values - result.Instance.ActionType.Should().Be(ActionType.Added); - result.Instance.HealthCheckUrl.Should().Be("http://DESKTOP-GNQ5SUT:80/health-check"); - result.Instance.AppName.Should().Be("FOOBAR"); } } diff --git a/src/Discovery/test/Eureka.Test/Transport/JsonInstanceInfoTest.cs b/src/Discovery/test/Eureka.Test/Transport/JsonInstanceInfoTest.cs index eebfffe565..7c842dae07 100644 --- a/src/Discovery/test/Eureka.Test/Transport/JsonInstanceInfoTest.cs +++ b/src/Discovery/test/Eureka.Test/Transport/JsonInstanceInfoTest.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Text.Json; +using Steeltoe.Common.TestResources; using Steeltoe.Discovery.Eureka.AppInfo; using Steeltoe.Discovery.Eureka.Transport; @@ -11,7 +12,105 @@ namespace Steeltoe.Discovery.Eureka.Test.Transport; public sealed class JsonInstanceInfoTest { [Fact] - public void Deserialize_GoodJson() + public void Serialize() + { + var instanceInfo = new JsonInstanceInfo + { + InstanceId = "localhost:foo", + HostName = "localhost", + AppName = "FOO", + IPAddress = "192.168.56.1", + Status = InstanceStatus.Up, + OverriddenStatus = InstanceStatus.OutOfService, + OverriddenStatusLegacy = InstanceStatus.Down, + Port = new JsonPortWrapper + { + Enabled = true, + Port = 8080 + }, + SecurePort = new JsonPortWrapper + { + Enabled = false, + Port = 443 + }, + CountryId = 1, + DataCenterInfo = new JsonDataCenterInfo + { + ClassName = "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", + Name = "MyOwn" + }, + LeaseInfo = new JsonLeaseInfo + { + RenewalIntervalInSeconds = 30, + DurationInSeconds = 90, + RegistrationTimestamp = 1_457_714_988_223, + LastRenewalTimestamp = 1_457_716_158_319, + EvictionTimestamp = 1_457_715_134_123, + ServiceUpTimestamp = 1_457_714_988_223 + }, + Metadata = new Dictionary + { + ["@class"] = "java.util.Collections$EmptyMap" + }, + HomePageUrl = "http://localhost:8080/", + StatusPageUrl = "http://localhost:8080/info", + HealthCheckUrl = "http://localhost:8080/health", + VipAddress = "foo", + IsCoordinatingDiscoveryServer = false, + LastUpdatedTimestamp = 1_457_714_988_223, + LastDirtyTimestamp = 1_457_714_988_172, + ActionType = ActionType.Added + }; + + string result = JsonSerializer.Serialize(instanceInfo, EurekaJsonSerializerContext.Default.JsonInstanceInfo); + + result.Should().BeJson(""" + { + "instanceId": "localhost:foo", + "app": "FOO", + "ipAddr": "192.168.56.1", + "port": { + "@enabled": "true", + "$": 8080 + }, + "securePort": { + "@enabled": "false", + "$": 443 + }, + "homePageUrl": "http://localhost:8080/", + "statusPageUrl": "http://localhost:8080/info", + "healthCheckUrl": "http://localhost:8080/health", + "vipAddress": "foo", + "countryId": 1, + "dataCenterInfo": { + "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", + "name": "MyOwn" + }, + "hostName": "localhost", + "status": "UP", + "overriddenStatus": "OUT_OF_SERVICE", + "overriddenstatus": "DOWN", + "leaseInfo": { + "renewalIntervalInSecs": 30, + "durationInSecs": 90, + "registrationTimestamp": "1457714988223", + "lastRenewalTimestamp": "1457716158319", + "evictionTimestamp": "1457715134123", + "serviceUpTimestamp": "1457714988223" + }, + "isCoordinatingDiscoveryServer": "false", + "metadata": { + "@class": "java.util.Collections$EmptyMap" + }, + "lastUpdatedTimestamp": "1457714988223", + "lastDirtyTimestamp": "1457714988172", + "actionType": "ADDED" + } + """); + } + + [Fact] + public void Deserialize() { const string json = """ { @@ -40,7 +139,7 @@ public void Deserialize_GoodJson() "durationInSecs": 90, "registrationTimestamp": 1457714988223, "lastRenewalTimestamp": 1457716158319, - "evictionTimestamp": 0, + "evictionTimestamp": 1457715134123, "serviceUpTimestamp": 1457714988223 }, "metadata": { @@ -57,7 +156,7 @@ public void Deserialize_GoodJson() } """; - var result = JsonSerializer.Deserialize(json); + JsonInstanceInfo? result = JsonSerializer.Deserialize(json, EurekaJsonSerializerContext.Default.JsonInstanceInfo); result.Should().NotBeNull(); result.InstanceId.Should().Be("localhost:foo"); @@ -82,7 +181,7 @@ public void Deserialize_GoodJson() result.LeaseInfo.DurationInSeconds.Should().Be(90); result.LeaseInfo.RegistrationTimestamp.Should().Be(1_457_714_988_223); result.LeaseInfo.LastRenewalTimestamp.Should().Be(1_457_716_158_319); - result.LeaseInfo.EvictionTimestamp.Should().Be(0); + result.LeaseInfo.EvictionTimestamp.Should().Be(1_457_715_134_123); result.LeaseInfo.ServiceUpTimestamp.Should().Be(1_457_714_988_223); result.Metadata.Should().ContainSingle(); result.Metadata.Should().ContainKey("@class").WhoseValue.Should().Be("java.util.Collections$EmptyMap"); diff --git a/src/Discovery/test/Eureka.Test/Transport/JsonLeaseTest.cs b/src/Discovery/test/Eureka.Test/Transport/JsonLeaseTest.cs index c8366ad635..689312b069 100644 --- a/src/Discovery/test/Eureka.Test/Transport/JsonLeaseTest.cs +++ b/src/Discovery/test/Eureka.Test/Transport/JsonLeaseTest.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Text.Json; +using Steeltoe.Common.TestResources; using Steeltoe.Discovery.Eureka.Transport; namespace Steeltoe.Discovery.Eureka.Test.Transport; @@ -10,7 +11,34 @@ namespace Steeltoe.Discovery.Eureka.Test.Transport; public sealed class JsonLeaseTest { [Fact] - public void Deserialize_GoodJson() + public void Serialize() + { + var leaseInfo = new JsonLeaseInfo + { + RenewalIntervalInSeconds = 30, + DurationInSeconds = 90, + RegistrationTimestamp = 1_457_714_988_223, + LastRenewalTimestamp = 1_457_716_158_319, + EvictionTimestamp = 1_457_715_134_123, + ServiceUpTimestamp = 1_457_714_988_223 + }; + + string result = JsonSerializer.Serialize(leaseInfo, EurekaJsonSerializerContext.Default.JsonLeaseInfo); + + result.Should().BeJson(""" + { + "renewalIntervalInSecs": 30, + "durationInSecs": 90, + "registrationTimestamp": "1457714988223", + "lastRenewalTimestamp": "1457716158319", + "evictionTimestamp": "1457715134123", + "serviceUpTimestamp": "1457714988223" + } + """); + } + + [Fact] + public void Deserialize() { const string json = """ { @@ -18,19 +46,19 @@ public void Deserialize_GoodJson() "durationInSecs": 90, "registrationTimestamp": 1457714988223, "lastRenewalTimestamp": 1457716158319, - "evictionTimestamp": 0, + "evictionTimestamp": 1457715134123, "serviceUpTimestamp": 1457714988223 } """; - var leaseInfo = JsonSerializer.Deserialize(json); + JsonLeaseInfo? result = JsonSerializer.Deserialize(json, EurekaJsonSerializerContext.Default.JsonLeaseInfo); - leaseInfo.Should().NotBeNull(); - leaseInfo.RenewalIntervalInSeconds.Should().Be(30); - leaseInfo.DurationInSeconds.Should().Be(90); - leaseInfo.RegistrationTimestamp.Should().Be(1_457_714_988_223); - leaseInfo.LastRenewalTimestamp.Should().Be(1_457_716_158_319); - leaseInfo.EvictionTimestamp.Should().Be(0); - leaseInfo.ServiceUpTimestamp.Should().Be(1_457_714_988_223); + result.Should().NotBeNull(); + result.RenewalIntervalInSeconds.Should().Be(30); + result.DurationInSeconds.Should().Be(90); + result.RegistrationTimestamp.Should().Be(1_457_714_988_223); + result.LastRenewalTimestamp.Should().Be(1_457_716_158_319); + result.EvictionTimestamp.Should().Be(1_457_715_134_123); + result.ServiceUpTimestamp.Should().Be(1_457_714_988_223); } } diff --git a/src/Discovery/test/Eureka.Test/Transport/JsonSerializationTest.cs b/src/Discovery/test/Eureka.Test/Transport/JsonSerializationTest.cs index 34bc67c396..ae7d02c9b8 100644 --- a/src/Discovery/test/Eureka.Test/Transport/JsonSerializationTest.cs +++ b/src/Discovery/test/Eureka.Test/Transport/JsonSerializationTest.cs @@ -22,7 +22,7 @@ public void Deserialize_BadJson_Throws() """; #pragma warning restore JSON001 // Invalid JSON pattern - Action action = () => JsonSerializer.Deserialize(json); + Action action = () => JsonSerializer.Deserialize(json, EurekaJsonSerializerContext.Default.JsonInstanceInfo); action.Should().ThrowExactly(); } diff --git a/src/Management/src/Abstractions/Configuration/EndpointPermissions.cs b/src/Management/src/Abstractions/Configuration/EndpointPermissions.cs index ce61c2964e..3e46238110 100644 --- a/src/Management/src/Abstractions/Configuration/EndpointPermissions.cs +++ b/src/Management/src/Abstractions/Configuration/EndpointPermissions.cs @@ -10,7 +10,7 @@ namespace Steeltoe.Management.Configuration; public enum EndpointPermissions { /// - /// Indicates no permission constraints. + /// Indicates no permissions. /// None, diff --git a/src/Management/src/Endpoint/Actuators/CloudFoundry/CloudFoundryJsonSerializerContext.cs b/src/Management/src/Endpoint/Actuators/CloudFoundry/CloudFoundryJsonSerializerContext.cs new file mode 100644 index 0000000000..91b49a4f3c --- /dev/null +++ b/src/Management/src/Endpoint/Actuators/CloudFoundry/CloudFoundryJsonSerializerContext.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; + +namespace Steeltoe.Management.Endpoint.Actuators.CloudFoundry; + +[JsonSourceGenerationOptions] +[JsonSerializable(typeof(PermissionsProvider.PermissionsResponse))] +[JsonSerializable(typeof(SecurityResult))] +internal sealed partial class CloudFoundryJsonSerializerContext : JsonSerializerContext; diff --git a/src/Management/src/Endpoint/Actuators/CloudFoundry/CloudFoundrySecurityMiddleware.cs b/src/Management/src/Endpoint/Actuators/CloudFoundry/CloudFoundrySecurityMiddleware.cs index c8734d6ed0..9fedf69e56 100644 --- a/src/Management/src/Endpoint/Actuators/CloudFoundry/CloudFoundrySecurityMiddleware.cs +++ b/src/Management/src/Endpoint/Actuators/CloudFoundry/CloudFoundrySecurityMiddleware.cs @@ -171,7 +171,7 @@ private async Task ReturnErrorAsync(HttpContext context, SecurityResult error) context.Response.StatusCode = (int)error.Code; } - await JsonSerializer.SerializeAsync(context.Response.Body, error, cancellationToken: context.RequestAborted); + await JsonSerializer.SerializeAsync(context.Response.Body, error, CloudFoundryJsonSerializerContext.Default.SecurityResult, context.RequestAborted); } [LoggerMessage(Level = LogLevel.Debug, Message = "Entering Cloud Foundry Security middleware at path {RequestPath}.")] diff --git a/src/Management/src/Endpoint/Actuators/CloudFoundry/PermissionsProvider.cs b/src/Management/src/Endpoint/Actuators/CloudFoundry/PermissionsProvider.cs index c44bfd35e9..ba03042926 100644 --- a/src/Management/src/Endpoint/Actuators/CloudFoundry/PermissionsProvider.cs +++ b/src/Management/src/Endpoint/Actuators/CloudFoundry/PermissionsProvider.cs @@ -4,8 +4,8 @@ using System.Net; using System.Net.Http.Headers; -using System.Security; using System.Text.Json; +using System.Text.Json.Serialization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -19,7 +19,6 @@ namespace Steeltoe.Management.Endpoint.Actuators.CloudFoundry; internal sealed partial class PermissionsProvider { - private const string ReadSensitiveDataJsonPropertyName = "read_sensitive_data"; public const string HttpClientName = "CloudFoundrySecurity"; private static readonly TimeSpan GetPermissionsTimeout = TimeSpan.FromMilliseconds(5_000); @@ -77,8 +76,11 @@ public async Task GetPermissionsAsync(string accessToken, Cancel : new SecurityResult(HttpStatusCode.ServiceUnavailable, Messages.CloudFoundryNotReachable); } - EndpointPermissions permissions = await ParsePermissionsResponseAsync(response, cancellationToken); - return new SecurityResult(permissions); + EndpointPermissions? permissions = await ParsePermissionsResponseAsync(response, cancellationToken); + + return permissions != null + ? new SecurityResult(permissions.Value) + : new SecurityResult(HttpStatusCode.BadGateway, Messages.CloudFoundryBrokenResponse); } catch (HttpRequestException exception) { @@ -91,34 +93,31 @@ public async Task GetPermissionsAsync(string accessToken, Cancel } } - public async Task ParsePermissionsResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken) + public async Task ParsePermissionsResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(response); - string json = string.Empty; - var permissions = EndpointPermissions.None; - try { - json = await response.Content.ReadAsStringAsync(cancellationToken); - + string json = await response.Content.ReadAsStringAsync(cancellationToken); LogResponseJson(SecurityUtilities.SanitizeInput(json)); - var result = JsonSerializer.Deserialize>(json); + PermissionsResponse? result = JsonSerializer.Deserialize(json, CloudFoundryJsonSerializerContext.Default.PermissionsResponse); - if (result != null && result.TryGetValue(ReadSensitiveDataJsonPropertyName, out JsonElement permissionElement)) + EndpointPermissions permissions = result switch { - bool enabled = JsonSerializer.Deserialize(permissionElement.GetRawText()); - permissions = enabled ? EndpointPermissions.Full : EndpointPermissions.Restricted; - } + { ReadBasicData: true, ReadSensitiveData: true } => EndpointPermissions.Full, + { ReadBasicData: true, ReadSensitiveData: false } => EndpointPermissions.Restricted, + _ => EndpointPermissions.None + }; + + LogPermissions(permissions); + return permissions; } catch (Exception exception) when (!exception.IsCancellation()) { - throw new SecurityException($"Exception extracting permissions from json: {SecurityUtilities.SanitizeInput(json)}", exception); + return null; } - - LogPermissions(permissions); - return permissions; } private HttpClient CreateHttpClient() @@ -140,6 +139,15 @@ private HttpClient CreateHttpClient() [LoggerMessage(Level = LogLevel.Debug, Message = "Resolved permissions to {Permissions}.")] private partial void LogPermissions(EndpointPermissions permissions); + internal sealed class PermissionsResponse + { + [JsonPropertyName("read_basic_data")] + public bool ReadBasicData { get; set; } + + [JsonPropertyName("read_sensitive_data")] + public bool ReadSensitiveData { get; set; } + } + internal static class Messages { public const string AccessDenied = "Access denied"; @@ -148,6 +156,7 @@ internal static class Messages public const string CloudFoundryApiMissing = "Cloud controller URL is not available"; public const string CloudFoundryNotReachable = "Cloud controller not reachable"; public const string CloudFoundryTimeout = "Cloud controller request timed out"; + public const string CloudFoundryBrokenResponse = "Failed to parse Cloud controller response"; public const string InvalidToken = "Invalid token"; } } diff --git a/src/Management/src/Endpoint/Actuators/DbMigrations/DbMigrationsDescriptor.cs b/src/Management/src/Endpoint/Actuators/DbMigrations/DbMigrationsDescriptor.cs index 656246f636..17f78cceb1 100644 --- a/src/Management/src/Endpoint/Actuators/DbMigrations/DbMigrationsDescriptor.cs +++ b/src/Management/src/Endpoint/Actuators/DbMigrations/DbMigrationsDescriptor.cs @@ -2,10 +2,15 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Text.Json.Serialization; + namespace Steeltoe.Management.Endpoint.Actuators.DbMigrations; public sealed class DbMigrationsDescriptor { + [JsonPropertyName("pendingMigrations")] public IList PendingMigrations { get; } = new List(); + + [JsonPropertyName("appliedMigrations")] public IList AppliedMigrations { get; } = new List(); } diff --git a/src/Management/src/Endpoint/Actuators/Health/HealthEndpointRequest.cs b/src/Management/src/Endpoint/Actuators/Health/HealthEndpointRequest.cs index fd1fe47c94..dc593685fd 100644 --- a/src/Management/src/Endpoint/Actuators/Health/HealthEndpointRequest.cs +++ b/src/Management/src/Endpoint/Actuators/Health/HealthEndpointRequest.cs @@ -2,11 +2,16 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Text.Json.Serialization; + namespace Steeltoe.Management.Endpoint.Actuators.Health; public sealed class HealthEndpointRequest { + [JsonPropertyName("groupName")] public string GroupName { get; } + + [JsonPropertyName("hasClaim")] public bool HasClaim { get; } public HealthEndpointRequest(string groupName, bool hasClaim) diff --git a/src/Management/src/Endpoint/Actuators/Health/HealthEndpointResponse.cs b/src/Management/src/Endpoint/Actuators/Health/HealthEndpointResponse.cs index 231d84fc53..09e734c9c5 100644 --- a/src/Management/src/Endpoint/Actuators/Health/HealthEndpointResponse.cs +++ b/src/Management/src/Endpoint/Actuators/Health/HealthEndpointResponse.cs @@ -14,17 +14,20 @@ public sealed class HealthEndpointResponse /// /// Gets the status of the health check. /// + [JsonPropertyName("status")] [JsonConverter(typeof(SnakeCaseAllCapsEnumMemberJsonConverter))] public HealthStatus Status { get; init; } /// /// Gets a description of the health check result. /// + [JsonPropertyName("description")] public string? Description { get; init; } /// /// Gets the individual health check components, including their details. /// + [JsonPropertyName("components")] [JsonIgnoreEmptyCollection] public IDictionary Components { get; } = new Dictionary(); @@ -32,6 +35,7 @@ public sealed class HealthEndpointResponse /// Gets the list of available health groups. /// [JsonIgnoreEmptyCollection] + [JsonPropertyName("groups")] public IList Groups { get; } = new List(); /// diff --git a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchange.cs b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchange.cs index ef97df0eea..1ca2564a46 100644 --- a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchange.cs +++ b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchange.cs @@ -12,10 +12,19 @@ public sealed class HttpExchange [JsonPropertyName("timeTaken")] public string? SerializedTimeTaken => TimeTaken != null ? XmlConvert.ToString(TimeTaken.Value) : null; + [JsonPropertyName("timestamp")] public DateTime Timestamp { get; } + + [JsonPropertyName("principal")] public HttpExchangePrincipal? Principal { get; } + + [JsonPropertyName("session")] public HttpExchangeSession? Session { get; } + + [JsonPropertyName("request")] public HttpExchangeRequest Request { get; } + + [JsonPropertyName("response")] public HttpExchangeResponse Response { get; } [JsonIgnore] diff --git a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangePrincipal.cs b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangePrincipal.cs index 2e7f976c12..f8e870963a 100644 --- a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangePrincipal.cs +++ b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangePrincipal.cs @@ -2,10 +2,13 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Text.Json.Serialization; + namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges; public sealed class HttpExchangePrincipal { + [JsonPropertyName("name")] public string Name { get; } public HttpExchangePrincipal(string name) diff --git a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangeRequest.cs b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangeRequest.cs index ddce7c1e83..2f8d0bc53a 100644 --- a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangeRequest.cs +++ b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangeRequest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Text.Json.Serialization; using Microsoft.Extensions.Primitives; using Steeltoe.Common.Json; @@ -9,12 +10,17 @@ namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges; public sealed class HttpExchangeRequest { + [JsonPropertyName("method")] public string Method { get; } + + [JsonPropertyName("uri")] public Uri Uri { get; } + [JsonPropertyName("headers")] [JsonIgnoreEmptyCollection] public IDictionary Headers { get; } + [JsonPropertyName("remoteAddress")] public string? RemoteAddress { get; } public HttpExchangeRequest(string method, Uri uri, IDictionary headers, string? remoteAddress) diff --git a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangeResponse.cs b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangeResponse.cs index 63e6338a98..4ecf185834 100644 --- a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangeResponse.cs +++ b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangeResponse.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Text.Json.Serialization; using Microsoft.Extensions.Primitives; using Steeltoe.Common.Json; @@ -9,8 +10,10 @@ namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges; public sealed class HttpExchangeResponse { + [JsonPropertyName("status")] public int Status { get; } + [JsonPropertyName("headers")] [JsonIgnoreEmptyCollection] public IDictionary Headers { get; } diff --git a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangeSession.cs b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangeSession.cs index 33ed0af39c..b2ad567174 100644 --- a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangeSession.cs +++ b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangeSession.cs @@ -2,10 +2,13 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Text.Json.Serialization; + namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges; public sealed class HttpExchangeSession { + [JsonPropertyName("id")] public string Id { get; } public HttpExchangeSession(string id) diff --git a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangesResult.cs b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangesResult.cs index d687f08313..32bc9a2505 100644 --- a/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangesResult.cs +++ b/src/Management/src/Endpoint/Actuators/HttpExchanges/HttpExchangesResult.cs @@ -2,12 +2,14 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Text.Json.Serialization; using Steeltoe.Common; namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges; public sealed class HttpExchangesResult { + [JsonPropertyName("exchanges")] public IList Exchanges { get; } public HttpExchangesResult(IList exchanges) diff --git a/src/Management/src/Endpoint/Actuators/Hypermedia/Link.cs b/src/Management/src/Endpoint/Actuators/Hypermedia/Link.cs index a6cb952bbe..05fb09a975 100644 --- a/src/Management/src/Endpoint/Actuators/Hypermedia/Link.cs +++ b/src/Management/src/Endpoint/Actuators/Hypermedia/Link.cs @@ -8,6 +8,7 @@ namespace Steeltoe.Management.Endpoint.Actuators.Hypermedia; public sealed class Link { + [JsonPropertyName("href")] public string Href { get; set; } [JsonPropertyName("templated")] diff --git a/src/Management/src/Endpoint/Actuators/Hypermedia/Links.cs b/src/Management/src/Endpoint/Actuators/Hypermedia/Links.cs index 6a748f1c6c..777f7f5e93 100644 --- a/src/Management/src/Endpoint/Actuators/Hypermedia/Links.cs +++ b/src/Management/src/Endpoint/Actuators/Hypermedia/Links.cs @@ -14,6 +14,7 @@ public sealed class Links /// /// Gets or sets the type of links contained in this collection. /// + [JsonPropertyName("type")] public string Type { get; set; } = "steeltoe"; /// diff --git a/src/Management/src/Endpoint/Actuators/Loggers/LoggersEndpointMiddleware.cs b/src/Management/src/Endpoint/Actuators/Loggers/LoggersEndpointMiddleware.cs index 30427ca776..aa8b9fc352 100644 --- a/src/Management/src/Endpoint/Actuators/Loggers/LoggersEndpointMiddleware.cs +++ b/src/Management/src/Endpoint/Actuators/Loggers/LoggersEndpointMiddleware.cs @@ -31,11 +31,12 @@ internal sealed partial class LoggersEndpointMiddleware( if (httpContext.Request.Path.StartsWithSegments(path, out PathString remaining) && remaining.HasValue) { - string loggerName = remaining.Value!.TrimStart('/'); + string loggerName = remaining.Value.TrimStart('/'); - Dictionary change = await DeserializeRequestAsync(httpContext.Request.Body, cancellationToken); + Dictionary changes = await DeserializeRequestAsync(httpContext.Request.Body, cancellationToken); - change.TryGetValue("configuredLevel", out string? level); + // Spring Boot Admin sends {} to reset the level, while Apps Manager sends {"configuredLevel":null} + _ = changes.TryGetValue("configuredLevel", out string? level); LogChangeRequest(loggerName, level ?? "RESET"); @@ -55,11 +56,12 @@ internal sealed partial class LoggersEndpointMiddleware( return new LoggersRequest(); } - private async Task> DeserializeRequestAsync(Stream stream, CancellationToken cancellationToken) + private async Task> DeserializeRequestAsync(Stream stream, CancellationToken cancellationToken) { try { - var dictionary = await JsonSerializer.DeserializeAsync>(stream, cancellationToken: cancellationToken); + JsonSerializerOptions options = ManagementOptionsMonitor.CurrentValue.SerializerOptions; + var dictionary = await JsonSerializer.DeserializeAsync>(stream, options, cancellationToken); if (dictionary != null) { diff --git a/src/Management/src/Endpoint/ManagementPort/ManagementPortMiddleware.cs b/src/Management/src/Endpoint/ManagementPort/ManagementPortMiddleware.cs index 6ba88fb103..cd94e77298 100644 --- a/src/Management/src/Endpoint/ManagementPort/ManagementPortMiddleware.cs +++ b/src/Management/src/Endpoint/ManagementPort/ManagementPortMiddleware.cs @@ -74,7 +74,7 @@ private bool HasMappedInstancePort(int managementPort, int? requestPort) if (!string.IsNullOrEmpty(instancePorts)) { - var portMappings = JsonSerializer.Deserialize>(instancePorts); + List? portMappings = JsonSerializer.Deserialize(instancePorts, PortMappingJsonSerializerContext.Default.ListPortMapping); PortMapping? portMapping = portMappings?.Find(mapping => mapping.Internal == managementPort && (requestPort == mapping.ExternalTlsProxy || requestPort == mapping.InternalTlsProxy)); @@ -116,7 +116,7 @@ private void SetResponseError(HttpContext context, int managementPort) Message = "Access to {Path} on port {Port} denied because 'Management:Endpoints:Port' is set to {ManagementPort}.")] private partial void LogAccessDenied(PathString path, int? port, int managementPort); - private sealed record PortMapping + internal sealed record PortMapping { [JsonPropertyName("internal")] public int? Internal { get; init; } diff --git a/src/Discovery/src/Eureka/Transport/JsonApplicationRoot.cs b/src/Management/src/Endpoint/ManagementPort/PortMappingJsonSerializerContext.cs similarity index 50% rename from src/Discovery/src/Eureka/Transport/JsonApplicationRoot.cs rename to src/Management/src/Endpoint/ManagementPort/PortMappingJsonSerializerContext.cs index 5957fa4a22..1e59b524ed 100644 --- a/src/Discovery/src/Eureka/Transport/JsonApplicationRoot.cs +++ b/src/Management/src/Endpoint/ManagementPort/PortMappingJsonSerializerContext.cs @@ -4,10 +4,8 @@ using System.Text.Json.Serialization; -namespace Steeltoe.Discovery.Eureka.Transport; +namespace Steeltoe.Management.Endpoint.ManagementPort; -internal sealed class JsonApplicationRoot -{ - [JsonPropertyName("application")] - public JsonApplication? Application { get; set; } -} +[JsonSourceGenerationOptions] +[JsonSerializable(typeof(List))] +internal sealed partial class PortMappingJsonSerializerContext : JsonSerializerContext; diff --git a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudControllerPermissionsMock.cs b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudControllerPermissionsMock.cs index 5d581d2d1b..3c7a6e22de 100644 --- a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudControllerPermissionsMock.cs +++ b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudControllerPermissionsMock.cs @@ -32,11 +32,17 @@ internal static DelegateToMockHttpClientHandler GetHttpMessageHandler() httpClientHandler.Mock.When(HttpMethod.Get, "https://example.api.com/v2/apps/exception/permissions") .Throw(new HttpRequestException(HttpRequestError.NameResolutionError)); - httpClientHandler.Mock.When(HttpMethod.Get, "https://example.api.com/v2/apps/no_sensitive_data/permissions").Respond(HttpStatusCode.OK, + httpClientHandler.Mock.When(HttpMethod.Get, "https://example.api.com/v2/apps/broken-response/permissions") + .Respond(HttpStatusCode.OK, "application/json", "{"); + + httpClientHandler.Mock.When(HttpMethod.Get, "https://example.api.com/v2/apps/no-permissions/permissions").Respond(HttpStatusCode.OK, "application/json", + """{"read_sensitive_data": false, "read_basic_data": false}"""); + + httpClientHandler.Mock.When(HttpMethod.Get, "https://example.api.com/v2/apps/restricted-permissions/permissions").Respond(HttpStatusCode.OK, "application/json", """{"read_sensitive_data": false, "read_basic_data": true}"""); - httpClientHandler.Mock.When(HttpMethod.Get, "https://example.api.com/v2/apps/success/permissions").Respond(HttpStatusCode.OK, "application/json", - """{"read_sensitive_data": true, "read_basic_data": true}"""); + httpClientHandler.Mock.When(HttpMethod.Get, "https://example.api.com/v2/apps/full-permissions/permissions").Respond(HttpStatusCode.OK, + "application/json", """{"read_sensitive_data": true, "read_basic_data": true}"""); return httpClientHandler; } diff --git a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs index 5484052d8a..14f42128c6 100644 --- a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs @@ -27,7 +27,7 @@ public sealed class CloudFoundryActuatorTest private const string VcapApplicationForMock = """ { "cf_api": "https://example.api.com", - "application_id": "success" + "application_id": "full-permissions" } """; diff --git a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs index 99a64e2782..18e350eb83 100644 --- a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs @@ -327,7 +327,7 @@ public async Task RedactsHttpHeaders() { var appSettings = new Dictionary { - ["vcap:application:application_id"] = "success", + ["vcap:application:application_id"] = "full-permissions", ["vcap:application:cf_api"] = "https://example.api.com" }; @@ -379,26 +379,26 @@ public async Task Returns_expected_response_on_permission_check(string scenario, await host.StartAsync(TestContext.Current.CancellationToken); using var client = new HttpClient(); - var testAuthenticationRequestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost:5000/cloudfoundryapplication")); - testAuthenticationRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", MockAccessToken); - var testAuthorizationRequestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost:5000/cloudfoundryapplication/info")); - testAuthorizationRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", MockAccessToken); + var authenticationRequest = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost:5000/cloudfoundryapplication")); + authenticationRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", MockAccessToken); + var authorizationRequest = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost:5000/cloudfoundryapplication/info")); + authorizationRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", MockAccessToken); - HttpResponseMessage response = await client.SendAsync(testAuthenticationRequestMessage, TestContext.Current.CancellationToken); - response.StatusCode.Should().Be(steeltoeStatusCode); + HttpResponseMessage authenticationResponse = await client.SendAsync(authenticationRequest, TestContext.Current.CancellationToken); + authenticationResponse.StatusCode.Should().Be(steeltoeStatusCode); if (errorMessage != null) { string jsonErrorValue = JsonValue.Create(errorMessage).ToJsonString(); - string errorText = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); + string errorText = await authenticationResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); errorText.Should().Be(steeltoeStatusCode == HttpStatusCode.InternalServerError ? errorMessage : $$"""{"security_error":{{jsonErrorValue}}}"""); } else { - string responseBody = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); + string authenticationResponseBody = await authenticationResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - responseBody.Should().BeJson(""" + authenticationResponseBody.Should().BeJson(""" { "type":"steeltoe", "_links":{ @@ -415,8 +415,8 @@ public async Task Returns_expected_response_on_permission_check(string scenario, """); } - HttpResponseMessage fullPermissionResponse = await client.SendAsync(testAuthorizationRequestMessage, TestContext.Current.CancellationToken); - fullPermissionResponse.StatusCode.Should().Be(scenario == "no_sensitive_data" ? HttpStatusCode.Forbidden : steeltoeStatusCode); + HttpResponseMessage authorizationResponse = await client.SendAsync(authorizationRequest, TestContext.Current.CancellationToken); + authorizationResponse.StatusCode.Should().Be(scenario == "restricted-permissions" ? HttpStatusCode.Forbidden : steeltoeStatusCode); string logLines = loggerProvider.GetAsText(); logLines.Should().ContainAll(expectedLogs); diff --git a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTestScenarios.cs b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTestScenarios.cs index 7b7419395a..4ab86ca1be 100644 --- a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTestScenarios.cs +++ b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTestScenarios.cs @@ -10,8 +10,8 @@ namespace Steeltoe.Management.Endpoint.Test.Actuators.CloudFoundry; internal sealed class CloudFoundrySecurityMiddlewareTestScenarios : TheoryData { - private const string SuccessLog = - "INFO System.Net.Http.HttpClient.CloudFoundrySecurity.ClientHandler: Sending HTTP request GET https://example.api.com/v2/apps/success/permissions"; + private const string FullPermissionsLog = + "INFO System.Net.Http.HttpClient.CloudFoundrySecurity.ClientHandler: Sending HTTP request GET https://example.api.com/v2/apps/full-permissions/permissions"; private static readonly string PermissionsCheckForbiddenLog = $"INFO {typeof(PermissionsProvider)}: Cloud Foundry returned status Forbidden while obtaining permissions from https://example.api.com/v2/apps/forbidden/permissions."; @@ -25,6 +25,9 @@ internal sealed class CloudFoundrySecurityMiddlewareTestScenarios : TheoryData @@ -87,10 +91,10 @@ public async Task Returns_expected_response_on_permission_check(string scenario, switch (scenario) { - case "success": + case "full-permissions": result.Permissions.Should().Be(EndpointPermissions.Full); break; - case "no_sensitive_data": + case "restricted-permissions": result.Permissions.Should().Be(EndpointPermissions.Restricted); break; default: diff --git a/src/Management/test/Endpoint.Test/Actuators/Health/TestContributors/ComplexDetailsContributor.cs b/src/Management/test/Endpoint.Test/Actuators/Health/TestContributors/ComplexDetailsContributor.cs index b23a8cd54c..b3bffabe72 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Health/TestContributors/ComplexDetailsContributor.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Health/TestContributors/ComplexDetailsContributor.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Text.Json.Serialization; using Steeltoe.Common.HealthChecks; namespace Steeltoe.Management.Endpoint.Test.Actuators.Health.TestContributors; @@ -38,12 +39,22 @@ internal sealed class ComplexDetailsContributor : IHealthContributor private sealed class TestHealthDetails { + [JsonPropertyName("testString")] public string TestString { get; set; } = "test-string"; + + [JsonPropertyName("testInteger")] public int TestInteger { get; set; } = 123; + + [JsonPropertyName("testFloatingPoint")] public double TestFloatingPoint { get; set; } = 1.23; + + [JsonPropertyName("testBoolean")] public bool TestBoolean { get; set; } = true; + + [JsonPropertyName("nestedComplexType")] public TestHealthDetails? NestedComplexType { get; set; } + [JsonPropertyName("testList")] public List TestList { get; set; } = [ "A", @@ -51,6 +62,7 @@ private sealed class TestHealthDetails "C" ]; + [JsonPropertyName("testDictionary")] public Dictionary TestDictionary { get; set; } = new() { ["One"] = 1, diff --git a/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorSerilogTest.cs b/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorSerilogTest.cs index 02ccd12aca..70e96e837a 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorSerilogTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorSerilogTest.cs @@ -214,11 +214,8 @@ public async Task Can_change_minimum_levels_with_serilog() } """); - HttpResponseMessage resetResponse = await httpClient.PostAsync(new Uri("http://localhost/actuator/loggers/Fake.Category"), new StringContent(""" - { - "configuredLevel": null - } - """, RequestContentType), TestContext.Current.CancellationToken); + HttpResponseMessage resetResponse = await httpClient.PostAsync(new Uri("http://localhost/actuator/loggers/Fake.Category"), + new StringContent("{}", RequestContentType), TestContext.Current.CancellationToken); resetResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); (await resetResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)).Should().BeEmpty(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs index c5d72935cb..bde4c60857 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs @@ -289,11 +289,8 @@ public async Task Can_change_minimum_levels() } """); - HttpResponseMessage resetResponse = await httpClient.PostAsync(new Uri("http://localhost/actuator/loggers/Fake.Category"), new StringContent(""" - { - "configuredLevel": null - } - """, RequestContentType), TestContext.Current.CancellationToken); + HttpResponseMessage resetResponse = await httpClient.PostAsync(new Uri("http://localhost/actuator/loggers/Fake.Category"), + new StringContent("{}", RequestContentType), TestContext.Current.CancellationToken); resetResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); (await resetResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)).Should().BeEmpty(); @@ -345,6 +342,112 @@ public async Task Can_change_minimum_levels() """); } + [Fact] + public async Task Can_reset_minimum_level_by_sending_configuredLevel_null() + { + var appSettings = new Dictionary(AppSettings) + { + ["Logging:LogLevel:Default"] = "Error", + ["Logging:LogLevel:Fake"] = "Debug" + }; + + WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); + builder.Configuration.AddInMemoryCollection(appSettings); + EnsureLoggingConfigurationIsBound(builder.Logging, builder.Configuration); + builder.Services.AddSingleton(); + builder.Services.AddLoggersActuator(); + await using WebApplication host = builder.Build(); + + using var loggerFactory = host.Services.GetRequiredService(); + _ = loggerFactory.CreateLogger("Fake.Some"); + + await host.StartAsync(TestContext.Current.CancellationToken); + using HttpClient httpClient = host.GetTestClient(); + + HttpResponseMessage setResponse1 = await httpClient.PostAsync(new Uri("http://localhost/actuator/loggers/Fake"), new StringContent(""" + { + "configuredLevel": "TRACE" + } + """, RequestContentType), TestContext.Current.CancellationToken); + + setResponse1.StatusCode.Should().Be(HttpStatusCode.NoContent); + (await setResponse1.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)).Should().BeEmpty(); + + HttpResponseMessage getResponse1 = await httpClient.GetAsync(new Uri("http://localhost/actuator/loggers"), TestContext.Current.CancellationToken); + + getResponse1.StatusCode.Should().Be(HttpStatusCode.OK); + + string getResponseBody1 = await getResponse1.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); + + getResponseBody1.Should().BeJson(""" + { + "levels": [ + "OFF", + "FATAL", + "ERROR", + "WARN", + "INFO", + "DEBUG", + "TRACE" + ], + "loggers": { + "Default": { + "effectiveLevel": "ERROR" + }, + "Fake": { + "configuredLevel": "DEBUG", + "effectiveLevel": "TRACE" + }, + "Fake.Some": { + "effectiveLevel": "TRACE" + } + }, + "groups": {} + } + """); + + HttpResponseMessage resetResponse = await httpClient.PostAsync(new Uri("http://localhost/actuator/loggers/Fake"), new StringContent(""" + { + "configuredLevel": null + } + """, RequestContentType), TestContext.Current.CancellationToken); + + resetResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); + (await resetResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)).Should().BeEmpty(); + + HttpResponseMessage getResponse2 = await httpClient.GetAsync(new Uri("http://localhost/actuator/loggers"), TestContext.Current.CancellationToken); + + getResponse2.StatusCode.Should().Be(HttpStatusCode.OK); + + string getResponseBody2 = await getResponse2.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); + + getResponseBody2.Should().BeJson(""" + { + "levels": [ + "OFF", + "FATAL", + "ERROR", + "WARN", + "INFO", + "DEBUG", + "TRACE" + ], + "loggers": { + "Default": { + "effectiveLevel": "ERROR" + }, + "Fake": { + "effectiveLevel": "DEBUG" + }, + "Fake.Some": { + "effectiveLevel": "DEBUG" + } + }, + "groups": {} + } + """); + } + [Fact] public async Task Fails_on_invalid_request_body() { diff --git a/src/Management/test/Endpoint.Test/ContentNegotiationTest.cs b/src/Management/test/Endpoint.Test/ContentNegotiationTest.cs index e3647e9e31..a9f20d2358 100644 --- a/src/Management/test/Endpoint.Test/ContentNegotiationTest.cs +++ b/src/Management/test/Endpoint.Test/ContentNegotiationTest.cs @@ -34,6 +34,7 @@ public async Task Can_use_content_type_with_alternate_casing() using HttpClient client = host.GetTestClient(); MediaTypeHeaderValue contentType = MediaTypeHeaderValue.Parse("APPLICATION/vnd.Spring-Boot.Actuator.v3+JSON"); + HttpContent requestContent = new StringContent("{}", contentType); HttpResponseMessage response = @@ -54,6 +55,7 @@ public async Task Can_use_content_type_including_charset() using HttpClient client = host.GetTestClient(); MediaTypeHeaderValue contentType = MediaTypeHeaderValue.Parse("application/vnd.spring-boot.actuator.v3+json; charset=utf-8"); + HttpContent requestContent = new StringContent("{}", contentType); HttpResponseMessage response = @@ -74,6 +76,7 @@ public async Task Cannot_use_invalid_content_type() using HttpClient client = host.GetTestClient(); MediaTypeHeaderValue contentType = MediaTypeHeaderValue.Parse("application/xhtml+xml"); + HttpContent requestContent = new StringContent("{}", contentType); HttpResponseMessage response = diff --git a/src/Management/test/Endpoint.Test/CorsPolicyTest.cs b/src/Management/test/Endpoint.Test/CorsPolicyTest.cs index 88ef4cef2c..c1ff824cd8 100644 --- a/src/Management/test/Endpoint.Test/CorsPolicyTest.cs +++ b/src/Management/test/Endpoint.Test/CorsPolicyTest.cs @@ -183,6 +183,7 @@ public async Task ConfiguresDefaultActuatorsCorsPolicyForGetRequestOnCloudFoundr mock.Expect(HttpMethod.Get, "https://api.cloud.com/v2/apps/798c2495-fe75-49b1-88da-b81197f2bf06/permissions") .WithHeaders("Authorization", $"bearer {token}").Respond("application/json", """ { + "read_basic_data": true, "read_sensitive_data": true } """); From 58a177b3977a40f7dd9a7efe535139d38225bbb1 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 12 Mar 2026 14:41:48 +0100 Subject: [PATCH 39/81] Fix broken build netsdk 10.0.200, fix green builds on failure (#1657) * Fix broken build on .NET SDK v10.0.200 Fixes occurrences of the following errors: error IDE0370: Suppression is unnecessary (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0370) * Fix GHA: Overall build was reported Green if jobs have failures * Test: build failure must abort the job and report outcome Red * Revert "Test: build failure must abort the job and report outcome Red" This reverts commit 9ee452840b99f69aad194d8adc23ecad64364ef5. * Test: test failure must upload results and report outcome Red * Revert "Test: test failure must upload results and report outcome Red" This reverts commit e20a39c30ed61944b8b122124658c2ab35001052. * Test: report failure must abort the job and report outcome Red * Revert "Test: report failure must abort the job and report outcome Red" This reverts commit 500945319bdaf79e3aa3bd9dc4a148591a32da13. --- .github/workflows/Steeltoe.All.yml | 7 ++++--- .github/workflows/component-shared-workflow.yml | 6 ++++-- .../CosmosDb/CosmosDbHealthContributorTest.cs | 2 +- .../Eureka.Test/PostConfigureEurekaInstanceOptionsTest.cs | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/Steeltoe.All.yml b/.github/workflows/Steeltoe.All.yml index 13c8e49972..2b399806f2 100644 --- a/.github/workflows/Steeltoe.All.yml +++ b/.github/workflows/Steeltoe.All.yml @@ -41,7 +41,6 @@ jobs: - os: macos-latest skipIntegrationTests: true runs-on: ${{ matrix.os }} - continue-on-error: true services: eurekaServer: @@ -91,13 +90,15 @@ jobs: run: dotnet build ${{ env.SOLUTION_FILE }} --no-restore --configuration Release --verbosity minimal - name: Test + id: test run: dotnet test ${{ env.SOLUTION_FILE }} --filter "${{ matrix.skipIntegrationTests == true && 'Category!=MemoryDumps&Category!=Integration' || 'Category!=MemoryDumps' }}" ${{ env.COMMON_TEST_ARGS }} - name: Test (memory dumps) + id: test-memory-dumps run: dotnet test src/Management/test/Endpoint.Test --filter "Category=MemoryDumps" ${{ env.COMMON_TEST_ARGS }} - name: Upload crash/hang dumps (on failure) - if: ${{ failure() }} + if: ${{ !cancelled() && (steps.test.outcome == 'failure' || steps.test-memory-dumps.outcome == 'failure') }} uses: actions/upload-artifact@v5 with: name: FailedTestOutput-${{ matrix.os }} @@ -107,7 +108,7 @@ jobs: if-no-files-found: ignore - name: Report test results - if: ${{ !cancelled() }} + if: ${{ !cancelled() && (steps.test.outcome != 'skipped' || steps.test-memory-dumps.outcome != 'skipped') }} uses: dorny/test-reporter@v2 with: name: ${{ matrix.os }} test results diff --git a/.github/workflows/component-shared-workflow.yml b/.github/workflows/component-shared-workflow.yml index 52ca149a42..f22f44241e 100644 --- a/.github/workflows/component-shared-workflow.yml +++ b/.github/workflows/component-shared-workflow.yml @@ -81,14 +81,16 @@ jobs: run: dotnet build ${{ env.SOLUTION_FILE }} --no-restore --configuration Release --verbosity minimal - name: Test + id: test run: dotnet test ${{ env.SOLUTION_FILE }} --filter "Category!=MemoryDumps" ${{ env.COMMON_TEST_ARGS }} - name: Test (memory dumps) + id: test-memory-dumps if: ${{ inputs.component == 'Management' }} run: dotnet test src/Management/test/Endpoint.Test --filter "Category=MemoryDumps" ${{ env.COMMON_TEST_ARGS }} - name: Upload crash/hang dumps (on failure) - if: ${{ failure() }} + if: ${{ !cancelled() && (steps.test.outcome == 'failure' || steps.test-memory-dumps.outcome == 'failure') }} uses: actions/upload-artifact@v5 with: name: FailedTestOutput-${{ inputs.OS }}-latest @@ -98,7 +100,7 @@ jobs: if-no-files-found: ignore - name: Report test results - if: ${{ !cancelled() }} + if: ${{ !cancelled() && (steps.test.outcome != 'skipped' || steps.test-memory-dumps.outcome != 'skipped') }} uses: dorny/test-reporter@v2 with: name: ${{ inputs.OS }}-latest test results diff --git a/src/Connectors/test/Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs index 258f2d82c0..303f5c0c21 100644 --- a/src/Connectors/test/Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs @@ -105,7 +105,7 @@ public async Task Canceled_Throws() cosmosClientMock.Setup(client => client.ReadAccountAsync()).Returns(async () => { await Task.Delay(3.Seconds(), TestContext.Current.CancellationToken); - return null!; + return null; }); await using ServiceProvider serviceProvider = CreateServiceProvider(serviceName, connectionString, cosmosClientMock.Object); diff --git a/src/Discovery/test/Eureka.Test/PostConfigureEurekaInstanceOptionsTest.cs b/src/Discovery/test/Eureka.Test/PostConfigureEurekaInstanceOptionsTest.cs index 6200afa727..2b554d1457 100644 --- a/src/Discovery/test/Eureka.Test/PostConfigureEurekaInstanceOptionsTest.cs +++ b/src/Discovery/test/Eureka.Test/PostConfigureEurekaInstanceOptionsTest.cs @@ -329,7 +329,7 @@ public async Task Adds_random_number_to_instance_ID_when_ports_are_zero() EurekaInstanceOptions instanceOptions = optionsMonitor.CurrentValue; instanceOptions.InstanceId.Should().NotBeNull(); - string[] parts = instanceOptions.InstanceId!.Split(':'); + string[] parts = instanceOptions.InstanceId.Split(':'); parts.Should().HaveCount(3); int.TryParse(parts[2], CultureInfo.InvariantCulture, out int number).Should().BeTrue(); From 067370c05fc8a3b4bfa77017e68d27a63b0480b7 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:05:25 +0100 Subject: [PATCH 40/81] Fix binding options against null values (#1663) * Fix binding options against null values * Added test for empty string --- .../CompositeConfigurationProvider.cs | 34 +++++++++++++- .../DecryptionConfigurationProvider.cs | 2 +- .../PlaceholderConfigurationProvider.cs | 2 +- .../PlaceholderConfigurationTest.cs | 45 ++++++++++++++++++- .../test/Placeholder.Test/TestOptions.cs | 2 +- 5 files changed, 79 insertions(+), 6 deletions(-) diff --git a/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs b/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs index f7296f00d4..0664a92946 100644 --- a/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs +++ b/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs @@ -84,8 +84,13 @@ public virtual bool TryGet(string key, out string? value) LogTryGet(GetType().Name, key); - value = ConfigurationRoot?.GetValue(key); - bool found = value != null; + if (ConfigurationRoot == null) + { + value = null; + return false; + } + + bool found = InnerTryGet(ConfigurationRoot, key, out value); if (found) { @@ -95,6 +100,31 @@ public virtual bool TryGet(string key, out string? value) return found; } + private static bool InnerTryGet(IConfigurationRoot root, string key, out string? value) + { + IList providers = root.Providers as IList ?? root.Providers.ToList(); + + for (int index = providers.Count - 1; index >= 0; index--) + { + IConfigurationProvider provider = providers[index]; + + try + { + if (provider.TryGet(key, out value)) + { + return true; + } + } + catch (ObjectDisposedException) + { + // Skip disposed providers to avoid exceptions during access. + } + } + + value = null; + return false; + } + public void Set(string key, string? value) { ArgumentNullException.ThrowIfNull(key); diff --git a/src/Configuration/src/Encryption/DecryptionConfigurationProvider.cs b/src/Configuration/src/Encryption/DecryptionConfigurationProvider.cs index 0b8be46740..b5f536f2d5 100644 --- a/src/Configuration/src/Encryption/DecryptionConfigurationProvider.cs +++ b/src/Configuration/src/Encryption/DecryptionConfigurationProvider.cs @@ -46,7 +46,7 @@ public override bool TryGet(string key, out string? value) } } - return value != null; + return found; } private ITextDecryptor EnsureDecryptor(IConfigurationRoot configurationRoot) diff --git a/src/Configuration/src/Placeholder/PlaceholderConfigurationProvider.cs b/src/Configuration/src/Placeholder/PlaceholderConfigurationProvider.cs index 1c4c341578..3f956affad 100644 --- a/src/Configuration/src/Placeholder/PlaceholderConfigurationProvider.cs +++ b/src/Configuration/src/Placeholder/PlaceholderConfigurationProvider.cs @@ -36,7 +36,7 @@ public override bool TryGet(string key, out string? value) } } - return value != null; + return found; } [LoggerMessage(Level = LogLevel.Trace, Message = "Replaced value '{OriginalValue}' at key '{Key}' with '{ReplacementValue}'.")] diff --git a/src/Configuration/test/Placeholder.Test/PlaceholderConfigurationTest.cs b/src/Configuration/test/Placeholder.Test/PlaceholderConfigurationTest.cs index c8f73a2c1f..7869c46492 100644 --- a/src/Configuration/test/Placeholder.Test/PlaceholderConfigurationTest.cs +++ b/src/Configuration/test/Placeholder.Test/PlaceholderConfigurationTest.cs @@ -205,7 +205,8 @@ public void Substitutes_placeholders_in_key_lookups() ["key2"] = "${key1?not-found}", ["key3"] = "${no-key?not-found}", ["key4"] = "${no-key}", - ["key5"] = string.Empty + ["key5"] = string.Empty, + ["key6"] = null }; var builder = new ConfigurationBuilder(); @@ -219,6 +220,8 @@ public void Substitutes_placeholders_in_key_lookups() configuration["key3"].Should().Be("not-found"); configuration["key4"].Should().Be("${no-key}"); configuration["key5"].Should().BeEmpty(); + configuration["key6"].Should().BeNull(); + configuration["key7"].Should().BeNull(); configuration["no-key"] = "new-key-value"; @@ -408,6 +411,46 @@ static void AssertTypesInSourceTree(IList sources, int ind } } + [Fact] + public void Binding_property_against_null_overwrites_default_value() + { + var appSettings = new Dictionary + { + ["Root:TestOptions:Value"] = null + }; + + var builder = new ConfigurationBuilder(); + builder.AddInMemoryCollection(appSettings); + builder.AddPlaceholderResolver(); + IConfigurationRoot configuration = builder.Build(); + + IConfigurationSection section = configuration.GetSection("Root:TestOptions"); + var options = section.Get(); + + options.Should().NotBeNull(); + options.Value.Should().BeNull(); + } + + [Fact] + public void Binding_property_against_empty_string_overwrites_default_value() + { + var appSettings = new Dictionary + { + ["Root:TestOptions:Value"] = string.Empty + }; + + var builder = new ConfigurationBuilder(); + builder.AddInMemoryCollection(appSettings); + builder.AddPlaceholderResolver(); + IConfigurationRoot configuration = builder.Build(); + + IConfigurationSection section = configuration.GetSection("Root:TestOptions"); + var options = section.Get(); + + options.Should().NotBeNull(); + options.Value.Should().BeEmpty(); + } + public void Dispose() { _loggerFactory.Dispose(); diff --git a/src/Configuration/test/Placeholder.Test/TestOptions.cs b/src/Configuration/test/Placeholder.Test/TestOptions.cs index bbbeca4572..7600520855 100644 --- a/src/Configuration/test/Placeholder.Test/TestOptions.cs +++ b/src/Configuration/test/Placeholder.Test/TestOptions.cs @@ -6,5 +6,5 @@ namespace Steeltoe.Configuration.Placeholder.Test; internal sealed class TestOptions { - public string? Value { get; set; } + public string? Value { get; set; } = "DefaultValue"; } From 972d11d4fe543806673367b4eedd7e582e460818 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Mon, 6 Apr 2026 07:43:18 -0500 Subject: [PATCH 41/81] Fix Eureka Dynamic Port Assignment overriding user-configured ports (#1666) - Detect explicitly configured eureka:instance:port or eureka:instance:securePort to avoid overwriting - Add public UseAspNetCoreUrls property (default: true, to match Consul) to allow disabling dynamic port detection. - Consul optimization: evaluate if port should be set by Steeltoe before collecting listen addresses Made-with: Cursor --- .../Configuration/ConsulDiscoveryOptions.cs | 3 + .../src/Consul/ConfigurationSchema.json | 2 +- .../PostConfigureConsulDiscoveryOptions.cs | 47 +++--- .../Configuration/EurekaInstanceOptions.cs | 106 +++++++------ .../src/Eureka/ConfigurationSchema.json | 4 + .../DynamicPortAssignmentHostedService.cs | 2 +- .../EurekaServiceCollectionExtensions.cs | 4 +- .../PostConfigureEurekaInstanceOptions.cs | 2 +- .../src/Eureka/PublicAPI.Unshipped.txt | 2 + .../Eureka.Test/DynamicPortAssignmentTest.cs | 145 ++++++++++++++++++ .../Eureka.Test/EurekaInstanceOptionsTest.cs | 1 + 11 files changed, 238 insertions(+), 80 deletions(-) diff --git a/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs b/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs index b0758008e8..fd9b8b77d9 100644 --- a/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs +++ b/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs @@ -175,6 +175,9 @@ public sealed class ConsulDiscoveryOptions /// /// Gets or sets a value indicating whether to register with the port number ASP.NET Core is listening on. Default value: true. + /// + /// This property is ignored when or is explicitly configured, or when is + /// true. /// public bool UseAspNetCoreUrls { get; set; } = true; } diff --git a/src/Discovery/src/Consul/ConfigurationSchema.json b/src/Discovery/src/Consul/ConfigurationSchema.json index 3111a531f0..a00e7f69bf 100644 --- a/src/Discovery/src/Consul/ConfigurationSchema.json +++ b/src/Discovery/src/Consul/ConfigurationSchema.json @@ -188,7 +188,7 @@ }, "UseAspNetCoreUrls": { "type": "boolean", - "description": "Gets or sets a value indicating whether to register with the port number ASP.NET Core is listening on. Default value: true." + "description": "Gets or sets a value indicating whether to register with the port number ASP.NET Core is listening on. Default value: true.\n\nThis property is ignored when 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Port' or 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Scheme' is explicitly configured, or when 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.UseNetworkInterfaces' is true." }, "UseNetworkInterfaces": { "type": "boolean", diff --git a/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs b/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs index 6ad1371335..91dcd274cc 100644 --- a/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs +++ b/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs @@ -62,7 +62,7 @@ public void PostConfigure(string? name, ConsulDiscoveryOptions options) options.HostName = options.IPAddress; } - if (options.Port == 0) + if (options is { UseAspNetCoreUrls: true, Port: 0, Scheme: null, UseNetworkInterfaces: false }) { ICollection addresses = _configuration.GetListenAddresses(); SetSchemeWithPortFromListenAddresses(options, addresses); @@ -79,38 +79,33 @@ private string GetServiceName(ConsulDiscoveryOptions options) private void SetSchemeWithPortFromListenAddresses(ConsulDiscoveryOptions options, IEnumerable listenOnAddresses) { - // Try to pull some values out of server configuration to override defaults, but only if not using NetUtils. - // If NetUtils are configured, the user probably wants to define their own behavior. - if (options is { UseAspNetCoreUrls: true, Port: 0, Scheme: null }) - { - int? listenHttpPort = null; - int? listenHttpsPort = null; + int? listenHttpPort = null; + int? listenHttpsPort = null; - foreach (string address in listenOnAddresses) - { - BindingAddress bindingAddress = BindingAddress.Parse(address); - - if (bindingAddress is { Scheme: "http", Port: > 0 } && listenHttpPort == null) - { - listenHttpPort = bindingAddress.Port; - } - else if (bindingAddress is { Scheme: "https", Port: > 0 } && listenHttpsPort == null) - { - listenHttpsPort = bindingAddress.Port; - } - } + foreach (string address in listenOnAddresses) + { + BindingAddress bindingAddress = BindingAddress.Parse(address); - if (listenHttpsPort != null) + if (bindingAddress is { Scheme: "http", Port: > 0 } && listenHttpPort == null) { - options.Port = listenHttpsPort.Value; - options.Scheme = "https"; + listenHttpPort = bindingAddress.Port; } - else if (listenHttpPort != null) + else if (bindingAddress is { Scheme: "https", Port: > 0 } && listenHttpsPort == null) { - options.Port = listenHttpPort.Value; - options.Scheme = "http"; + listenHttpsPort = bindingAddress.Port; } } + + if (listenHttpsPort != null) + { + options.Port = listenHttpsPort.Value; + options.Scheme = "https"; + } + else if (listenHttpPort != null) + { + options.Port = listenHttpPort.Value; + options.Scheme = "http"; + } } private string GetInstanceId(ConsulDiscoveryOptions options) diff --git a/src/Discovery/src/Eureka/Configuration/EurekaInstanceOptions.cs b/src/Discovery/src/Eureka/Configuration/EurekaInstanceOptions.cs index 08e0f3f51e..24b391e3da 100644 --- a/src/Discovery/src/Eureka/Configuration/EurekaInstanceOptions.cs +++ b/src/Discovery/src/Eureka/Configuration/EurekaInstanceOptions.cs @@ -20,7 +20,11 @@ public sealed partial class EurekaInstanceOptions internal const string DefaultStatusPageUrlPath = "/info"; internal const string DefaultHealthCheckUrlPath = "/health"; - private bool UseAspNetCoreUrls => !Platform.IsCloudFoundry || IsContainerToContainerMethod() || IsForceHostNameMethod(); + private bool IsAnyPortConfigured => + this is { IsNonSecurePortEnabled: true, NonSecurePort: not null } or { IsSecurePortEnabled: true, SecurePort: not null }; + + private bool IsSuitableRegistrationMethod => !Platform.IsCloudFoundry || IsContainerToContainerMethod() || IsForceHostNameMethod(); + internal bool ShouldSetPortsFromListenAddresses => UseAspNetCoreUrls && IsSuitableRegistrationMethod && !IsAnyPortConfigured; internal TimeSpan LeaseRenewalInterval => TimeSpan.FromSeconds(LeaseRenewalIntervalInSeconds); internal TimeSpan LeaseExpirationDuration => TimeSpan.FromSeconds(LeaseExpirationDurationInSeconds); @@ -205,6 +209,13 @@ public sealed partial class EurekaInstanceOptions Name = DataCenterName.MyOwn }; + /// + /// Gets or sets a value indicating whether to register with the port number(s) ASP.NET Core is listening on. Default value: true. + /// + /// This property is ignored when or is explicitly configured. + /// + public bool UseAspNetCoreUrls { get; set; } = true; + /// /// Gets or sets a value indicating whether is used to determine and /// . Default value: false. @@ -231,72 +242,69 @@ internal bool IsForceHostNameMethod() internal void SetPortsFromListenAddresses(IEnumerable listenOnAddresses, string source, ILogger logger) { - if (UseAspNetCoreUrls) + int? listenHttpPort = null; + int? listenHttpsPort = null; + + foreach (string address in listenOnAddresses) { - int? listenHttpPort = null; - int? listenHttpsPort = null; + BindingAddress bindingAddress = BindingAddress.Parse(address); - foreach (string address in listenOnAddresses) + if (bindingAddress is { Scheme: "http", Port: > 0 } && listenHttpPort == null) { - BindingAddress bindingAddress = BindingAddress.Parse(address); - - if (bindingAddress is { Scheme: "http", Port: > 0 } && listenHttpPort == null) - { - listenHttpPort = bindingAddress.Port; - } - else if (bindingAddress is { Scheme: "https", Port: > 0 } && listenHttpsPort == null) - { - listenHttpsPort = bindingAddress.Port; - } + listenHttpPort = bindingAddress.Port; + } + else if (bindingAddress is { Scheme: "https", Port: > 0 } && listenHttpsPort == null) + { + listenHttpsPort = bindingAddress.Port; } + } - int? nonSecurePort = IsNonSecurePortEnabled ? NonSecurePort : null; - int? securePort = IsSecurePortEnabled ? SecurePort : null; + int? nonSecurePort = IsNonSecurePortEnabled ? NonSecurePort : null; + int? securePort = IsSecurePortEnabled ? SecurePort : null; - if (nonSecurePort != listenHttpPort) + if (nonSecurePort != listenHttpPort) + { + if (listenHttpPort != null) { - if (listenHttpPort != null) + if (nonSecurePort == null) { - if (nonSecurePort == null) - { - LogActivatingNonSecurePort(logger, listenHttpPort, source); - } - else - { - LogChangingNonSecurePort(logger, listenHttpPort, source); - } - - NonSecurePort = listenHttpPort.Value; - IsNonSecurePortEnabled = true; + LogActivatingNonSecurePort(logger, listenHttpPort, source); } - else if (nonSecurePort != null) + else { - LogDeactivatingNonSecurePort(logger, source); - IsNonSecurePortEnabled = false; + LogChangingNonSecurePort(logger, listenHttpPort, source); } + + NonSecurePort = listenHttpPort.Value; + IsNonSecurePortEnabled = true; + } + else if (nonSecurePort != null) + { + LogDeactivatingNonSecurePort(logger, source); + IsNonSecurePortEnabled = false; } + } - if (securePort != listenHttpsPort) + if (securePort != listenHttpsPort) + { + if (listenHttpsPort != null) { - if (listenHttpsPort != null) + if (securePort == null) { - if (securePort == null) - { - LogActivatingSecurePort(logger, listenHttpsPort, source); - } - else - { - LogChangingSecurePort(logger, listenHttpsPort, source); - } - - SecurePort = listenHttpsPort.Value; - IsSecurePortEnabled = true; + LogActivatingSecurePort(logger, listenHttpsPort, source); } - else if (securePort != null) + else { - LogDeactivatingSecurePort(logger, source); - IsSecurePortEnabled = false; + LogChangingSecurePort(logger, listenHttpsPort, source); } + + SecurePort = listenHttpsPort.Value; + IsSecurePortEnabled = true; + } + else if (securePort != null) + { + LogDeactivatingSecurePort(logger, source); + IsSecurePortEnabled = false; } } } diff --git a/src/Discovery/src/Eureka/ConfigurationSchema.json b/src/Discovery/src/Eureka/ConfigurationSchema.json index 2e1c600204..95634e3416 100644 --- a/src/Discovery/src/Eureka/ConfigurationSchema.json +++ b/src/Discovery/src/Eureka/ConfigurationSchema.json @@ -258,6 +258,10 @@ "type": "string", "description": "Gets or sets the relative path to the status page for the instance. The status page URL is then constructed out of the 'Steeltoe.Discovery.Eureka.Configuration.EurekaInstanceOptions.HostName' and the type of communication - secure or non-secure, as specified in 'Steeltoe.Discovery.Eureka.Configuration.EurekaInstanceOptions.SecurePort' and 'Steeltoe.Discovery.Eureka.Configuration.EurekaInstanceOptions.NonSecurePort'. It is normally used for informational purposes for other services to find out about the status of the instance. Users can provide a simple HTML page indicating what the current status of the instance is. Default value: /info." }, + "UseAspNetCoreUrls": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to register with the port number(s) ASP.NET Core is listening on. Default value: true.\n\nThis property is ignored when 'Steeltoe.Discovery.Eureka.Configuration.EurekaInstanceOptions.NonSecurePort' or 'Steeltoe.Discovery.Eureka.Configuration.EurekaInstanceOptions.SecurePort' is explicitly configured." + }, "UseNetworkInterfaces": { "type": "boolean", "description": "Gets or sets a value indicating whether 'System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces' is used to determine 'Steeltoe.Discovery.Eureka.Configuration.EurekaInstanceOptions.IPAddress' and 'Steeltoe.Discovery.Eureka.Configuration.EurekaInstanceOptions.HostName'. Default value: false." diff --git a/src/Discovery/src/Eureka/DynamicPortAssignmentHostedService.cs b/src/Discovery/src/Eureka/DynamicPortAssignmentHostedService.cs index e58cefb6bf..da517432b7 100644 --- a/src/Discovery/src/Eureka/DynamicPortAssignmentHostedService.cs +++ b/src/Discovery/src/Eureka/DynamicPortAssignmentHostedService.cs @@ -136,7 +136,7 @@ public void PostConfigure(string? name, EurekaInstanceOptions options) { ArgumentNullException.ThrowIfNull(options); - if (_listenState.ListenOnAddresses != null) + if (_listenState.ListenOnAddresses != null && options.ShouldSetPortsFromListenAddresses) { options.SetPortsFromListenAddresses(_listenState.ListenOnAddresses, "address features", _optionsLogger); } diff --git a/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs b/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs index 3348501e08..38de08bce5 100644 --- a/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs +++ b/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs @@ -81,11 +81,11 @@ private static void ConfigureEurekaClientOptions(IServiceCollection services) private static void ConfigureEurekaInstanceOptions(IServiceCollection services) { + DynamicPortAssignmentHostedService.Wire(services); + services.AddOptions().BindConfiguration(EurekaInstanceOptions.ConfigurationPrefix); services.AddOptions().BindConfiguration(InetOptions.ConfigurationPrefix); services.AddSingleton, PostConfigureEurekaInstanceOptions>(); - - DynamicPortAssignmentHostedService.Wire(services); } private static void AddEurekaServices(IServiceCollection services) diff --git a/src/Discovery/src/Eureka/PostConfigureEurekaInstanceOptions.cs b/src/Discovery/src/Eureka/PostConfigureEurekaInstanceOptions.cs index 3ef1f30b96..b33208d520 100644 --- a/src/Discovery/src/Eureka/PostConfigureEurekaInstanceOptions.cs +++ b/src/Discovery/src/Eureka/PostConfigureEurekaInstanceOptions.cs @@ -140,7 +140,7 @@ private void SetPorts(EurekaInstanceOptions options) options.IsSecurePortEnabled = true; } - if (options.NonSecurePort == null && options.SecurePort == null) + if (options.ShouldSetPortsFromListenAddresses) { var optionsLogger = _serviceProvider.GetRequiredService>(); ICollection addresses = _configuration.GetListenAddresses(); diff --git a/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt b/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt index 7dc5c58110..e09ccfe57f 100644 --- a/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt +++ b/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Steeltoe.Discovery.Eureka.Configuration.EurekaInstanceOptions.UseAspNetCoreUrls.get -> bool +Steeltoe.Discovery.Eureka.Configuration.EurekaInstanceOptions.UseAspNetCoreUrls.set -> void diff --git a/src/Discovery/test/Eureka.Test/DynamicPortAssignmentTest.cs b/src/Discovery/test/Eureka.Test/DynamicPortAssignmentTest.cs index 45f9666e17..b3adc4687e 100644 --- a/src/Discovery/test/Eureka.Test/DynamicPortAssignmentTest.cs +++ b/src/Discovery/test/Eureka.Test/DynamicPortAssignmentTest.cs @@ -4,9 +4,11 @@ using System.Runtime.InteropServices; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Steeltoe.Common.TestResources; +using Steeltoe.Discovery.Eureka.Configuration; namespace Steeltoe.Discovery.Eureka.Test; @@ -38,4 +40,147 @@ public async Task Applies_dynamically_assigned_ports_after_startup() infoManager.Instance.IsSecurePortEnabled.Should().BeTrue(); infoManager.Instance.SecurePort.Should().BePositive(); } + + [Fact] + public async Task Applies_dynamically_assigned_ports_when_kestrel_overrides_urls_config() + { + var appSettings = new Dictionary + { + ["Eureka:Client:ShouldFetchRegistry"] = "false", + ["Eureka:Client:ShouldRegisterWithEureka"] = "false", + ["urls"] = "http://*:5000" + }; + + WebApplicationBuilder builder = TestWebApplicationBuilderFactory.CreateDefault(false); + builder.Configuration.AddInMemoryCollection(appSettings); + + builder.WebHost.ConfigureKestrel(options => + { + options.ListenAnyIP(0); + }); + + builder.Services.AddEurekaDiscoveryClient(); + + await using WebApplication app = builder.Build(); + await app.StartAsync(TestContext.Current.CancellationToken); + + var infoManager = app.Services.GetRequiredService(); + + infoManager.Instance.IsNonSecurePortEnabled.Should().BeTrue(); + infoManager.Instance.NonSecurePort.Should().NotBe(5000); + infoManager.Instance.NonSecurePort.Should().BePositive(); + infoManager.Instance.IsSecurePortEnabled.Should().BeFalse(); + infoManager.Instance.SecurePort.Should().Be(0); + } + + [Fact] + public async Task Does_not_override_explicitly_configured_secure_port() + { + var appSettings = new Dictionary + { + ["Eureka:Client:ShouldFetchRegistry"] = "false", + ["Eureka:Client:ShouldRegisterWithEureka"] = "false", + ["Eureka:Instance:SecurePort"] = "443", + ["Eureka:Instance:SecurePortEnabled"] = "true" + }; + + WebApplicationBuilder builder = TestWebApplicationBuilderFactory.CreateDefault(false); + builder.WebHost.UseSetting("urls", "http://*:0"); + builder.Configuration.AddInMemoryCollection(appSettings); + builder.Services.AddEurekaDiscoveryClient(); + + await using WebApplication app = builder.Build(); + await app.StartAsync(TestContext.Current.CancellationToken); + + var infoManager = app.Services.GetRequiredService(); + + infoManager.Instance.IsSecurePortEnabled.Should().BeTrue(); + infoManager.Instance.SecurePort.Should().Be(443); + infoManager.Instance.IsNonSecurePortEnabled.Should().BeFalse(); + infoManager.Instance.NonSecurePort.Should().Be(0); + } + + [Fact] + public async Task Does_not_override_explicitly_configured_non_secure_port() + { + var appSettings = new Dictionary + { + ["Eureka:Client:ShouldFetchRegistry"] = "false", + ["Eureka:Client:ShouldRegisterWithEureka"] = "false", + ["Eureka:Instance:Port"] = "80", + ["Eureka:Instance:NonSecurePortEnabled"] = "true" + }; + + WebApplicationBuilder builder = TestWebApplicationBuilderFactory.CreateDefault(false); + builder.WebHost.UseSetting("urls", "http://*:0"); + builder.Configuration.AddInMemoryCollection(appSettings); + builder.Services.AddEurekaDiscoveryClient(); + + await using WebApplication app = builder.Build(); + await app.StartAsync(TestContext.Current.CancellationToken); + + var infoManager = app.Services.GetRequiredService(); + + infoManager.Instance.IsNonSecurePortEnabled.Should().BeTrue(); + infoManager.Instance.NonSecurePort.Should().Be(80); + infoManager.Instance.IsSecurePortEnabled.Should().BeFalse(); + infoManager.Instance.SecurePort.Should().Be(0); + } + + [Fact] + public async Task Does_not_override_ports_configured_via_code() + { + var appSettings = new Dictionary + { + ["Eureka:Client:ShouldFetchRegistry"] = "false", + ["Eureka:Client:ShouldRegisterWithEureka"] = "false" + }; + + WebApplicationBuilder builder = TestWebApplicationBuilderFactory.CreateDefault(false); + builder.WebHost.UseSetting("urls", "http://*:0"); + builder.Configuration.AddInMemoryCollection(appSettings); + builder.Services.AddEurekaDiscoveryClient(); + + builder.Services.Configure(options => + { + options.SecurePort = 8443; + options.IsSecurePortEnabled = true; + }); + + await using WebApplication app = builder.Build(); + await app.StartAsync(TestContext.Current.CancellationToken); + + var infoManager = app.Services.GetRequiredService(); + + infoManager.Instance.IsSecurePortEnabled.Should().BeTrue(); + infoManager.Instance.SecurePort.Should().Be(8443); + infoManager.Instance.IsNonSecurePortEnabled.Should().BeFalse(); + infoManager.Instance.NonSecurePort.Should().Be(0); + } + + [Fact] + public async Task Does_not_apply_dynamic_ports_when_UseAspNetCoreUrls_is_false() + { + var appSettings = new Dictionary + { + ["Eureka:Client:ShouldFetchRegistry"] = "false", + ["Eureka:Client:ShouldRegisterWithEureka"] = "false", + ["Eureka:Instance:UseAspNetCoreUrls"] = "false" + }; + + WebApplicationBuilder builder = TestWebApplicationBuilderFactory.CreateDefault(false); + builder.WebHost.UseSetting("urls", "http://*:0"); + builder.Configuration.AddInMemoryCollection(appSettings); + builder.Services.AddEurekaDiscoveryClient(); + + await using WebApplication app = builder.Build(); + await app.StartAsync(TestContext.Current.CancellationToken); + + var infoManager = app.Services.GetRequiredService(); + + infoManager.Instance.IsNonSecurePortEnabled.Should().BeFalse(); + infoManager.Instance.NonSecurePort.Should().Be(0); + infoManager.Instance.IsSecurePortEnabled.Should().BeFalse(); + infoManager.Instance.SecurePort.Should().Be(0); + } } diff --git a/src/Discovery/test/Eureka.Test/EurekaInstanceOptionsTest.cs b/src/Discovery/test/Eureka.Test/EurekaInstanceOptionsTest.cs index d5a1a9ee6f..180fe7fbf9 100644 --- a/src/Discovery/test/Eureka.Test/EurekaInstanceOptionsTest.cs +++ b/src/Discovery/test/Eureka.Test/EurekaInstanceOptionsTest.cs @@ -43,6 +43,7 @@ public void Constructor_Initializes_Defaults() instanceOptions.SecureHealthCheckUrl.Should().BeNull(); instanceOptions.AutoScalingGroupName.Should().BeNull(); instanceOptions.DataCenterInfo.Name.Should().Be(DataCenterName.MyOwn); + instanceOptions.UseAspNetCoreUrls.Should().BeTrue(); instanceOptions.UseNetworkInterfaces.Should().BeFalse(); } From 9aa324dc21e530c838e403a750fd0b9d2fb65b0b Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:28:40 +0200 Subject: [PATCH 42/81] Config Server stability improvements (#1667) * Remove duplicate tests * Update tests using Sandbox to use MemoryFileProvider (and without delay), to reduce test flakiness * Update existing test to verify precedence between options and appsettings * Make ConfigServerClientOptions reactive to configuration changes `ConfigServerClientOptions` are now re-evaluated from initial options + configuration on every settings change, instead of being mutated in-place. This prevents stale or torn reads when the provider runs concurrently (e.g. polling timer vs reload). Key changes: - The provider clones options on each configuration reload, starting from the initial options passed via code, then applying configuration on top. This ensures code-level defaults are restored when keys are removed from configuration. - Client settings (spring:cloud:config:*) are no longer written into the provider's data dictionary, eliminating a circular feedback loop where the provider's own output could influence its input. - Discovery lookup results are tracked separately and applied on top of the options snapshot at load time, rather than mutating shared state. - Client certificate configuration is moved from the source's `Build` method into `ConfigureConfigServerClientOptions`, so it participates in the options pipeline. - `ConfigServerClientOptions.Clone()` is introduced to produce isolated snapshots, preventing tearing when options are read during a concurrent reload. A bug was fixed that prevented using global certificates. - An internal `HttpClientHandler` parameter is threaded through the source and builder extensions, enabling direct handler injection for tests without reflection. - `IOptionsChangeTokenSource` is registered in DI so that `IOptionsMonitor` properly triggers on configuration changes. * Improve test coverage * Fix references to Config Server in comments * Fix threading bugs and resource leaks in Config Server provider The provider had several concurrency and lifecycle issues: - Vault token renewal timers were created unboundedly on every HTTP request that carried a token, leaking timers that were never disposed. Vault renewal is now managed as a single timer with the same lifecycle as the polling timer. - Timer management, handler configuration, and disposal could race with each other without synchronization. A lifecycle lock now guards these operations, while non-blocking try-enter locks prevent timer callbacks from queueing up. - The HTTP client handler's certificates were reconfigured on every HTTP request, racing with concurrent requests sharing the same handler. Certificate and validation configuration is now applied once per settings change under the lifecycle lock, with certificates cleared before re-adding to prevent unbounded accumulation across reloads. - Options cloning did not copy the certificate issuer chain, losing intermediate CA certificates across reloads. - Disposal is now coordinated via a volatile flag so that in-flight timer callbacks and Load() calls exit gracefully instead of throwing unobserved exceptions. * Fixed: use latest options snapshot during discovery in Config Server * Fixed: preserve original stack trace when load throws * Fix Config Server health contributor to act on a consistent snapshot * Fix torn reads in _lastDiscoveryLookupResult * Fixed: act on single snapshot of _httpClientHandler * Refresh discovery during polled reload and fix lazy initialization race Polled configuration reloads now refresh the discovery service lookup before fetching from Config Server, enabling detection of server address changes between polling intervals. Unlike initial load, polled reload does not apply FailFast or retry semantics. Fixed a race where concurrent calls to LoadInternalAsync could create multiple ConfigServerDiscoveryService instances, each constructing their own temporary DI container and discovery clients. * Fix change callback accumulation on repeated configuration reloads Each configuration reload re-registered a new change callback via RegisterChangeCallback, without disposing the previous one. Rapid reloads could accumulate stale callbacks, causing redundant OnSettingsChanged invocations on multiple threads. Replaced with a single ChangeToken.OnChange registration in the constructor, which automatically handles re-registration and is disposed on shutdown. * Fix HttpClientHandler mutation race and timer disposal race Replace shared mutable HttpClientHandler with a per-request factory, eliminating the race where OnSettingsChanged reconfigured a handler concurrently in use by HTTP requests. Production code creates and disposes a fresh handler per request; tests inject a factory for mocking. Replace _isDisposed flag with CancellationToken-based shutdown, enabling in-flight HTTP requests in timer callbacks to terminate promptly on disposal instead of running to completion. * Fix threadpool starvation during retries * Fix timer callbacks potentially using stale options when settings change * Fix stale reads in ConfigServerDiscoveryService * Add overloads to post-configure Config Server options * Cleanup existing tests * Various fixes - Improved log messages (and don't log multiple times), fix exception stack traces - Don't remote-fetch twice at startup (load + timer that immediately fired) - Moved options bind and remote-fetch from constructor to Load, fix combination with placeholder * Increase timeout to reduce failures in CI * Update src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Loading.cs Co-authored-by: Tim Hess * Review feedback: replace overrule with override * Review feedback: Change initial to default options * Review feedback: remove redundant settings in test * Review feedback: adjust test name * Review feedback: remove duplicate configuration * Review feedback: reformat JSON snippets in tests --------- Co-authored-by: Tim Hess --- .../src/AutoConfiguration/BootstrapScanner.cs | 2 +- .../src/Certificates/CertificateOptions.cs | 16 +- .../ConfigureCertificateOptionsTest.cs | 6 +- .../ApplicationInstanceInfoTest.cs | 13 +- .../test/TestResources/MemoryFileProvider.cs | 2 - ...MemoryFileProviderAppSettingsExtensions.cs | 36 + ...eProviderConfigurationBuilderExtensions.cs | 77 ++ .../CompositeConfigurationProvider.cs | 5 +- .../ConfigServer/ConfigServerClientOptions.cs | 63 +- ...figServerConfigurationBuilderExtensions.cs | 72 +- .../ConfigServerConfigurationProvider.cs | 765 ++++++++------ .../ConfigServerConfigurationSource.cs | 99 +- .../ConfigServerDiscoveryService.cs | 74 +- .../ConfigServerHealthContributor.cs | 46 +- .../ConfigServerHostBuilderExtensions.cs | 127 ++- ...ConfigServerServiceCollectionExtensions.cs | 51 +- .../src/ConfigServer/ConfigurationSchema.json | 2 +- .../ConfigureConfigServerClientOptions.cs | 37 +- .../HostBuilderWrapperExtensions.cs | 33 +- .../src/ConfigServer/PublicAPI.Unshipped.txt | 11 + .../ConfigServerClientOptionsTest.cs | 430 ++++++++ .../ConfigServerDiscoveryServiceTest.cs | 20 +- ...rConfigurationExtensionsIntegrationTest.cs | 64 +- .../ConfigServerClientOptionsTest.cs | 358 ++++++- ...rConfigurationBuilderExtensionsCoreTest.cs | 99 +- ...erverConfigurationBuilderExtensionsTest.cs | 117 ++- ...ServerConfigurationProviderTest.Loading.cs | 971 ++++++++++-------- ...erverConfigurationProviderTest.Settings.cs | 83 +- .../ConfigServerConfigurationProviderTest.cs | 186 ++-- .../ConfigServerConfigurationSourceTest.cs | 12 +- .../ConfigServerDiscoveryServiceTest.cs | 6 +- .../ConfigServerHealthContributorTest.cs | 60 +- .../ConfigServerHostBuilderExtensionsTest.cs | 24 +- .../ConfigServerHostedServiceTest.cs | 4 +- ...igServerServiceCollectionExtensionsTest.cs | 21 - .../ForwardingHttpClientHandler.cs | 22 - .../TestConfigServerConfigurationSource.cs | 17 - .../TestConfigServerStartup.cs | 129 --- .../test/ConfigServer.Test/TestHelper.cs | 4 +- .../ConfigServer.Test/TestServiceInstance.cs | 20 + .../PlaceholderConfigurationTest.cs | 30 +- .../PlaceholderWebApplicationTest.cs | 74 +- .../ConfigurationChangeDetectionTest.cs | 25 +- .../Consul/Registry/ConsulServiceRegistrar.cs | 2 +- .../Eureka/EurekaApplicationInfoManager.cs | 4 +- .../ConfigurationDiscoveryClientTest.cs | 11 +- .../Eureka.Test/EurekaClientOptionsTest.cs | 12 +- .../Eureka.Test/EurekaInstanceOptionsTest.cs | 12 +- .../RegisterMultipleDiscoveryClientsTest.cs | 27 +- .../DynamicConsoleLoggerProviderTest.cs | 38 +- .../DynamicSerilogLoggerProviderTest.cs | 52 +- .../Configuration/ManagementOptions.cs | 2 +- .../src/Endpoint/ConfigurationSchema.json | 2 +- .../CloudFoundry/CloudFoundryActuatorTest.cs | 38 +- .../Environment/EnvironmentActuatorTest.cs | 82 +- .../Actuators/Health/HealthActuatorTest.cs | 48 +- .../HttpExchangesActuatorTest.cs | 38 +- .../Hypermedia/HypermediaActuatorTest.cs | 38 +- .../Loggers/LoggersActuatorSerilogTest.cs | 46 +- .../Actuators/Loggers/LoggersActuatorTest.cs | 42 +- .../HttpVerbInConventionalRoutingTest.cs | 48 +- .../Refresh/HttpVerbInEndpointRoutingTest.cs | 48 +- .../Actuators/Refresh/RefreshActuatorTest.cs | 28 +- .../RouteMappingsActuatorTest.cs | 28 +- .../test/Endpoint.Test/EndpointOptionsTest.cs | 100 +- .../SpringBootAdminClient/HostBuilderTest.cs | 24 +- .../test/RazorPagesTestWebApp/Program.cs | 2 +- 67 files changed, 3168 insertions(+), 1917 deletions(-) create mode 100644 src/Common/test/TestResources/MemoryFileProviderAppSettingsExtensions.cs create mode 100644 src/Common/test/TestResources/MemoryFileProviderConfigurationBuilderExtensions.cs create mode 100644 src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerClientOptionsTest.cs delete mode 100644 src/Configuration/test/ConfigServer.Test/ForwardingHttpClientHandler.cs delete mode 100644 src/Configuration/test/ConfigServer.Test/TestConfigServerConfigurationSource.cs delete mode 100644 src/Configuration/test/ConfigServer.Test/TestConfigServerStartup.cs create mode 100644 src/Configuration/test/ConfigServer.Test/TestServiceInstance.cs diff --git a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs index a3c471e64d..5b58a286a8 100644 --- a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs +++ b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs @@ -91,7 +91,7 @@ public void ConfigureSteeltoe() private void WireConfigServer() { - _wrapper.AddConfigServer(_loggerFactory); + _wrapper.AddConfigServer(null, _loggerFactory); LogConfigServerConfigured(); } diff --git a/src/Common/src/Certificates/CertificateOptions.cs b/src/Common/src/Certificates/CertificateOptions.cs index 3b062e0f38..9482b2d792 100644 --- a/src/Common/src/Certificates/CertificateOptions.cs +++ b/src/Common/src/Certificates/CertificateOptions.cs @@ -14,6 +14,20 @@ public sealed class CertificateOptions internal const string ConfigurationKeyPrefix = "Certificates"; public X509Certificate2? Certificate { get; set; } - public IList IssuerChain { get; } = []; + + internal CertificateOptions Clone() + { + var clone = new CertificateOptions + { + Certificate = Certificate + }; + + foreach (X509Certificate2 issuer in IssuerChain) + { + clone.IssuerChain.Add(issuer); + } + + return clone; + } } diff --git a/src/Common/test/Certificates.Test/ConfigureCertificateOptionsTest.cs b/src/Common/test/Certificates.Test/ConfigureCertificateOptionsTest.cs index 2a93ec848b..f14de9eed7 100644 --- a/src/Common/test/Certificates.Test/ConfigureCertificateOptionsTest.cs +++ b/src/Common/test/Certificates.Test/ConfigureCertificateOptionsTest.cs @@ -148,7 +148,7 @@ public async Task CertificateOptions_update_on_changed_contents(string certifica string secondPrivateKeyContent = await File.ReadAllTextAsync("secondInstance.key", TestContext.Current.CancellationToken); using var secondX509 = X509Certificate2.CreateFromPemFile("secondInstance.crt", "secondInstance.key"); string appSettings = BuildAppSettingsJson(certificateName, certificateFilePath, privateKeyFilePath); - string appSettingsPath = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); + string appSettingsPath = sandbox.CreateFile("appsettings.json", appSettings); var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddJsonFile(appSettingsPath, false, true); IConfiguration configuration = configurationBuilder.Build(); @@ -167,7 +167,7 @@ public async Task CertificateOptions_update_on_changed_contents(string certifica await File.WriteAllTextAsync(privateKeyFilePath, secondPrivateKeyContent, TestContext.Current.CancellationToken); using Task pollTask = WaitUntilCertificateChangedToAsync(secondX509, optionsMonitor, certificateName, TestContext.Current.CancellationToken); - await pollTask.WaitAsync(TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken); + await pollTask.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); optionsMonitor.Get(certificateName).Certificate.Should().Be(secondX509); } @@ -185,7 +185,7 @@ public async Task CertificateOptions_update_on_changed_path(string certificateNa string firstPrivateKeyFilePath = sandbox.CreateFile(Guid.NewGuid() + ".key", firstPrivateKeyContent); using var secondX509 = X509Certificate2.CreateFromPemFile("secondInstance.crt", "secondInstance.key"); string appSettings = BuildAppSettingsJson(certificateName, firstCertificateFilePath, firstPrivateKeyFilePath); - string appSettingsPath = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); + string appSettingsPath = sandbox.CreateFile("appsettings.json", appSettings); var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddJsonFile(appSettingsPath, false, true); IConfiguration configuration = configurationBuilder.Build(); diff --git a/src/Common/test/Common.Test/ApplicationInstanceInfoTest.cs b/src/Common/test/Common.Test/ApplicationInstanceInfoTest.cs index 94f5c04d4b..b38457aaf7 100644 --- a/src/Common/test/Common.Test/ApplicationInstanceInfoTest.cs +++ b/src/Common/test/Common.Test/ApplicationInstanceInfoTest.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.DependencyInjection; using Steeltoe.Common.Extensions; using Steeltoe.Common.TestResources; -using Steeltoe.Common.TestResources.IO; namespace Steeltoe.Common.Test; @@ -24,7 +23,7 @@ public void ConstructorSetsDefaults() [Fact] public async Task ReadsApplicationConfiguration() { - const string configJson = """ + const string appSettings = """ { "Spring": { "Application": { @@ -34,13 +33,11 @@ public async Task ReadsApplicationConfiguration() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, configJson); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); + var builder = new ConfigurationBuilder(); - builder.SetBasePath(directory); - builder.AddJsonFile(fileName); + builder.AddInMemoryAppSettingsJsonFile(fileProvider); IConfiguration configuration = builder.Build(); var services = new ServiceCollection(); diff --git a/src/Common/test/TestResources/MemoryFileProvider.cs b/src/Common/test/TestResources/MemoryFileProvider.cs index d8141e3a8a..e28f45a91d 100644 --- a/src/Common/test/TestResources/MemoryFileProvider.cs +++ b/src/Common/test/TestResources/MemoryFileProvider.cs @@ -12,8 +12,6 @@ namespace Steeltoe.Common.TestResources; public sealed class MemoryFileProvider : IFileProvider { - public const string DefaultAppSettingsFileName = "appsettings.json"; - private static readonly char[] DirectorySeparators = [ Path.DirectorySeparatorChar, diff --git a/src/Common/test/TestResources/MemoryFileProviderAppSettingsExtensions.cs b/src/Common/test/TestResources/MemoryFileProviderAppSettingsExtensions.cs new file mode 100644 index 0000000000..de791453a4 --- /dev/null +++ b/src/Common/test/TestResources/MemoryFileProviderAppSettingsExtensions.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +namespace Steeltoe.Common.TestResources; + +public static class MemoryFileProviderAppSettingsExtensions +{ + public static void IncludeAppSettingsJsonFile(this MemoryFileProvider fileProvider, string contents) + { + ArgumentNullException.ThrowIfNull(fileProvider); + + fileProvider.IncludeFile(MemoryFileProviderConfigurationBuilderExtensions.AppSettingsJsonFileName, contents); + } + + public static void IncludeAppSettingsXmlFile(this MemoryFileProvider fileProvider, string contents) + { + ArgumentNullException.ThrowIfNull(fileProvider); + + fileProvider.IncludeFile(MemoryFileProviderConfigurationBuilderExtensions.AppSettingsXmlFileName, contents); + } + + public static void IncludeAppSettingsIniFile(this MemoryFileProvider fileProvider, string contents) + { + ArgumentNullException.ThrowIfNull(fileProvider); + + fileProvider.IncludeFile(MemoryFileProviderConfigurationBuilderExtensions.AppSettingsIniFileName, contents); + } + + public static void ReplaceAppSettingsJsonFile(this MemoryFileProvider fileProvider, string contents) + { + ArgumentNullException.ThrowIfNull(fileProvider); + + fileProvider.ReplaceFile(MemoryFileProviderConfigurationBuilderExtensions.AppSettingsJsonFileName, contents); + } +} diff --git a/src/Common/test/TestResources/MemoryFileProviderConfigurationBuilderExtensions.cs b/src/Common/test/TestResources/MemoryFileProviderConfigurationBuilderExtensions.cs new file mode 100644 index 0000000000..fcec5b0ef4 --- /dev/null +++ b/src/Common/test/TestResources/MemoryFileProviderConfigurationBuilderExtensions.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Ini; +using Microsoft.Extensions.Configuration.Json; +using Microsoft.Extensions.Configuration.Xml; + +namespace Steeltoe.Common.TestResources; + +public static class MemoryFileProviderConfigurationBuilderExtensions +{ + internal const string AppSettingsJsonFileName = "appsettings.json"; + internal const string AppSettingsXmlFileName = "appsettings.xml"; + internal const string AppSettingsIniFileName = "appsettings.ini"; + + public static void AddInMemoryAppSettingsJsonFile(this IConfigurationBuilder builder, MemoryFileProvider fileProvider) + { + AddInMemoryJsonFile(builder, fileProvider, AppSettingsJsonFileName); + } + + public static void AddInMemoryJsonFile(this IConfigurationBuilder builder, MemoryFileProvider fileProvider, string path) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(fileProvider); + ArgumentException.ThrowIfNullOrEmpty(path); + + var source = new JsonConfigurationSource + { + FileProvider = fileProvider, + Path = path, + Optional = false, + ReloadOnChange = true, + // Turn off debounce, so the change token triggers immediately. Then we don't need to sleep in tests. + ReloadDelay = 0 + }; + + builder.Add(source); + } + + public static void AddInMemoryAppSettingsXmlFile(this IConfigurationBuilder builder, MemoryFileProvider fileProvider) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(fileProvider); + + var source = new XmlConfigurationSource + { + FileProvider = fileProvider, + Path = AppSettingsXmlFileName, + Optional = false, + ReloadOnChange = true, + // Turn off debounce, so the change token triggers immediately. Then we don't need to sleep in tests. + ReloadDelay = 0 + }; + + builder.Add(source); + } + + public static void AddInMemoryAppSettingsIniFile(this IConfigurationBuilder builder, MemoryFileProvider fileProvider) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(fileProvider); + + var source = new IniConfigurationSource + { + FileProvider = fileProvider, + Path = AppSettingsIniFileName, + Optional = false, + ReloadOnChange = true, + // Turn off debounce, so the change token triggers immediately. Then we don't need to sleep in tests. + ReloadDelay = 0 + }; + + builder.Add(source); + } +} diff --git a/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs b/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs index 0664a92946..dae60f1e3f 100644 --- a/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs +++ b/src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs @@ -53,10 +53,9 @@ private void Load(bool isReload) public IEnumerable GetChildKeys(IEnumerable earlierKeys, string? parentPath) { + ArgumentNullException.ThrowIfNull(earlierKeys); + string[] earlierKeysArray = earlierKeys as string[] ?? earlierKeys.ToArray(); -#pragma warning disable S3236 // Caller information arguments should not be provided explicitly - ArgumentNullException.ThrowIfNull(earlierKeysArray, nameof(earlierKeys)); -#pragma warning restore S3236 // Caller information arguments should not be provided explicitly if (_logger.IsEnabled(LogLevel.Trace)) { diff --git a/src/Configuration/src/ConfigServer/ConfigServerClientOptions.cs b/src/Configuration/src/ConfigServer/ConfigServerClientOptions.cs index d9ce59f505..f8be0b3b41 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerClientOptions.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerClientOptions.cs @@ -17,10 +17,14 @@ public sealed class ConfigServerClientOptions : IValidateCertificatesOptions private const char CommaDelimiter = ','; internal const string ConfigurationPrefix = "spring:cloud:config"; - internal CertificateOptions ClientCertificate { get; } = new(); internal TimeSpan HttpTimeout => TimeSpan.FromMilliseconds(Timeout); internal bool IsMultiServerConfiguration => Uri != null && Uri.Contains(CommaDelimiter); + /// + /// Gets or sets the client certificate used for mutual TLS authentication with the Config Server. + /// + internal CertificateOptions ClientCertificate { get; set; } = new(); + /// /// Gets or sets a value indicating whether the Config Server provider is enabled. Default value: true. /// @@ -35,7 +39,7 @@ public sealed class ConfigServerClientOptions : IValidateCertificatesOptions /// Gets or sets a comma-separated list of environments used when accessing configuration data. Default value: "Production". /// [ConfigurationKeyName("Env")] - public string? Environment { get; set; } = "Production"; + public string? Environment { get; set; } /// /// Gets or sets a comma-separated list of labels to request from the server. @@ -96,17 +100,17 @@ public bool ValidateCertificatesAlt /// /// Gets retry settings. /// - public ConfigServerRetryOptions Retry { get; } = new(); + public ConfigServerRetryOptions Retry { get; private set; } = new(); /// /// Gets service discovery settings. /// - public ConfigServerDiscoveryOptions Discovery { get; } = new(); + public ConfigServerDiscoveryOptions Discovery { get; private set; } = new(); /// /// Gets health check settings. /// - public ConfigServerHealthOptions Health { get; } = new(); + public ConfigServerHealthOptions Health { get; private set; } = new(); /// /// Gets or sets the address used by the provider to obtain a OAuth Access Token. @@ -129,7 +133,7 @@ public bool ValidateCertificatesAlt public int TokenTtl { get; set; } = 300_000; /// - /// Gets or sets the vault token renew rate (in milliseconds). Default value: 60_000 (1 minute). + /// Gets or sets the Vault token renew rate (in milliseconds). Default value: 60_000 (1 minute). /// public int TokenRenewRate { get; set; } = 60_000; @@ -141,7 +145,52 @@ public bool ValidateCertificatesAlt /// /// Gets headers that will be added to the Config Server request. /// - public IDictionary Headers { get; } = new Dictionary(); + public IDictionary Headers { get; private set; } = new Dictionary(); + + internal ConfigServerClientOptions Clone() + { + return new ConfigServerClientOptions + { + ClientCertificate = ClientCertificate.Clone(), + Enabled = Enabled, + FailFast = FailFast, + Environment = Environment, + Label = Label, + Name = Name, + Uri = Uri, + Username = Username, + Password = Password, + Token = Token, + Timeout = Timeout, + PollingInterval = PollingInterval, + ValidateCertificates = ValidateCertificates, + Retry = new ConfigServerRetryOptions + { + Enabled = Retry.Enabled, + InitialInterval = Retry.InitialInterval, + MaxInterval = Retry.MaxInterval, + Multiplier = Retry.Multiplier, + MaxAttempts = Retry.MaxAttempts + }, + Discovery = new ConfigServerDiscoveryOptions + { + Enabled = Discovery.Enabled, + ServiceId = Discovery.ServiceId + }, + Health = new ConfigServerHealthOptions + { + Enabled = Health.Enabled, + TimeToLive = Health.TimeToLive + }, + AccessTokenUri = AccessTokenUri, + ClientSecret = ClientSecret, + ClientId = ClientId, + TokenTtl = TokenTtl, + TokenRenewRate = TokenRenewRate, + DisableTokenRenewal = DisableTokenRenewal, + Headers = new Dictionary(Headers) + }; + } internal List GetUris() { diff --git a/src/Configuration/src/ConfigServer/ConfigServerConfigurationBuilderExtensions.cs b/src/Configuration/src/ConfigServer/ConfigServerConfigurationBuilderExtensions.cs index 187eff2ff8..19b9ade24e 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerConfigurationBuilderExtensions.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerConfigurationBuilderExtensions.cs @@ -26,7 +26,7 @@ public static class ConfigServerConfigurationBuilderExtensions /// public static IConfigurationBuilder AddConfigServer(this IConfigurationBuilder builder) { - return AddConfigServer(builder, NullLoggerFactory.Instance); + return AddConfigServer(builder, new ConfigServerClientOptions(), null, null, NullLoggerFactory.Instance); } /// @@ -43,9 +43,24 @@ public static IConfigurationBuilder AddConfigServer(this IConfigurationBuilder b /// public static IConfigurationBuilder AddConfigServer(this IConfigurationBuilder builder, ILoggerFactory loggerFactory) { - var options = new ConfigServerClientOptions(); + return AddConfigServer(builder, new ConfigServerClientOptions(), null, null, loggerFactory); + } - return AddConfigServer(builder, options, loggerFactory); + /// + /// Adds a configuration source for Config Server to the . + /// + /// + /// The to add configuration to. + /// + /// + /// The default options, whose values are overridden from . + /// + /// + /// The incoming so that additional calls can be chained. + /// + public static IConfigurationBuilder AddConfigServer(this IConfigurationBuilder builder, ConfigServerClientOptions options) + { + return AddConfigServer(builder, options, null, null, NullLoggerFactory.Instance); } /// @@ -55,7 +70,7 @@ public static IConfigurationBuilder AddConfigServer(this IConfigurationBuilder b /// The to add configuration to. /// /// - /// Enables configuring Config Server from code. + /// The default options, whose values are overridden from . /// /// /// Used for internal logging. Pass to disable logging. @@ -64,6 +79,50 @@ public static IConfigurationBuilder AddConfigServer(this IConfigurationBuilder b /// The incoming so that additional calls can be chained. /// public static IConfigurationBuilder AddConfigServer(this IConfigurationBuilder builder, ConfigServerClientOptions options, ILoggerFactory loggerFactory) + { + return AddConfigServer(builder, options, null, null, loggerFactory); + } + + /// + /// Adds a configuration source for Config Server to the . + /// + /// + /// The to add configuration to. + /// + /// + /// An optional delegate that further configures options from code, after settings from have been applied. + /// + /// + /// The incoming so that additional calls can be chained. + /// + public static IConfigurationBuilder AddConfigServer(this IConfigurationBuilder builder, Action? configure) + { + return AddConfigServer(builder, new ConfigServerClientOptions(), configure, null, NullLoggerFactory.Instance); + } + + /// + /// Adds a configuration source for Config Server to the . + /// + /// + /// The to add configuration to. + /// + /// + /// An optional delegate that further configures options from code, after settings from have been applied. + /// + /// + /// Used for internal logging. Pass to disable logging. + /// + /// + /// The incoming so that additional calls can be chained. + /// + public static IConfigurationBuilder AddConfigServer(this IConfigurationBuilder builder, Action? configure, + ILoggerFactory loggerFactory) + { + return AddConfigServer(builder, new ConfigServerClientOptions(), configure, null, loggerFactory); + } + + internal static IConfigurationBuilder AddConfigServer(this IConfigurationBuilder builder, ConfigServerClientOptions options, + Action? configure, Func? createHttpClientHandler, ILoggerFactory loggerFactory) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(options); @@ -74,10 +133,7 @@ public static IConfigurationBuilder AddConfigServer(this IConfigurationBuilder b builder.AddCloudFoundry(); builder.AddKubernetesServiceBindings(); - ConfigServerConfigurationSource source = builder is IConfiguration configuration - ? new ConfigServerConfigurationSource(options, configuration, loggerFactory) - : new ConfigServerConfigurationSource(options, builder.Sources, builder.Properties, loggerFactory); - + var source = new ConfigServerConfigurationSource(options, builder.Sources, builder.Properties, configure, createHttpClientHandler, loggerFactory); builder.Add(source); } diff --git a/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs b/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs index 36be91f368..2246ff3e45 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs @@ -7,6 +7,7 @@ using System.Net.Http.Headers; using System.Net.Http.Json; using System.Net.Sockets; +using System.Runtime.ExceptionServices; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -14,11 +15,19 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using Steeltoe.Common.Configuration; using Steeltoe.Common.Discovery; using Steeltoe.Common.Extensions; using Steeltoe.Common.Http; using Steeltoe.Common.Http.HttpClientPooling; +using LockPrimitive = +#if NET10_0_OR_GREATER + System.Threading.Lock +#else + object +#endif + ; namespace Steeltoe.Configuration.ConfigServer; @@ -30,22 +39,27 @@ internal sealed partial class ConfigServerConfigurationProvider : ConfigurationP private const string VaultRenewPath = "vault/v1/auth/token/renew-self"; private const string VaultTokenHeader = "X-Vault-Token"; private const char CommaDelimiter = ','; - internal const string TokenHeader = "X-Config-Token"; - private static readonly string[] EmptyLabels = [string.Empty]; - private readonly ILoggerFactory _loggerFactory; - private readonly ILogger _logger; - private readonly IConfiguration _configuration; - private readonly bool _hasConfiguration; - private readonly bool _ownsHttpClientHandler; + private readonly ILogger _logger; + private readonly Func _createHttpClientHandler; + private readonly bool _disposeHttpClientHandler; private readonly ConfigureConfigServerClientOptions _configurer; - private HttpClientHandler? _httpClientHandler; - - private ConfigServerDiscoveryService? _configServerDiscoveryService; - private Timer? _refreshTimer; - private SemaphoreSlim? _timerTickLock = new(1, 1); + private readonly ConfigServerClientOptions _defaultOptions; + private readonly LockPrimitive _lifecycleLock = new(); + private readonly LockPrimitive _configurationReloadTickLock = new(); + private readonly LockPrimitive _vaultRenewTickLock = new(); + private readonly ConfigServerDiscoveryService _configServerDiscoveryService; + private readonly IDisposable _changeTokenRegistration; + private readonly CancellationTokenSource _shutdownTokenSource = new(); + private readonly CancellationToken _shutdownToken; + + private Timer? _configurationReloadTimer; + private Timer? _vaultRenewTimer; + private volatile DiscoveryLookupResult? _lastDiscoveryLookupResult; + private volatile ConfigServerClientOptions _clientOptions; + private long _isReload; internal static JsonSerializerOptions SerializerOptions { get; } = new() { @@ -54,12 +68,13 @@ internal sealed partial class ConfigServerConfigurationProvider : ConfigurationP PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate }; - internal IDictionary Properties => Data; + internal IDictionary InnerData => Data; /// - /// Gets the configuration settings the provider uses when accessing the server. + /// Gets the settings used to access Config Server, excluding information found during service discovery (so that a provider (re)load properly observes + /// changes and triggers its change token). Returns a cloned snapshot to prevent tearing during reads/writes. /// - public ConfigServerClientOptions ClientOptions { get; } + internal ConfigServerClientOptions ClientOptions => _clientOptions.Clone(); /// /// Initializes a new instance of the class from a . @@ -71,205 +86,309 @@ internal sealed partial class ConfigServerConfigurationProvider : ConfigurationP /// Used for internal logging. Pass to disable logging. /// public ConfigServerConfigurationProvider(ConfigServerConfigurationSource source, ILoggerFactory loggerFactory) - : this(source.DefaultOptions, source.Configuration, null, loggerFactory) + : this(source.DefaultOptions, source.Configuration, source.Configure, source.CreateHttpClientHandler, loggerFactory) { } - internal ConfigServerConfigurationProvider(ConfigServerClientOptions clientOptions, IConfiguration? configuration, HttpClientHandler? httpClientHandler, - ILoggerFactory loggerFactory) + internal ConfigServerConfigurationProvider(ConfigServerClientOptions clientOptions, IConfiguration? configuration, + Action? configure, Func? createHttpClientHandler, ILoggerFactory loggerFactory) { ArgumentNullException.ThrowIfNull(clientOptions); ArgumentNullException.ThrowIfNull(loggerFactory); - _loggerFactory = loggerFactory; - _logger = _loggerFactory.CreateLogger(); + _logger = loggerFactory.CreateLogger(); + _shutdownToken = _shutdownTokenSource.Token; // Don't inline: the token survives disposal, while the source does not. + IConfiguration effectiveConfiguration = configuration ?? new ConfigurationBuilder().Build(); + _configurer = new ConfigureConfigServerClientOptions(effectiveConfiguration, configure); + _configServerDiscoveryService = new ConfigServerDiscoveryService(effectiveConfiguration, loggerFactory); + + _defaultOptions = clientOptions.Clone(); + _clientOptions = _defaultOptions; - if (configuration != null) + if (createHttpClientHandler != null) { - _configuration = configuration; - _hasConfiguration = true; + _createHttpClientHandler = createHttpClientHandler; + _disposeHttpClientHandler = false; } else { - _configuration = new ConfigurationBuilder().Build(); - _hasConfiguration = false; + _createHttpClientHandler = static () => new HttpClientHandler(); + _disposeHttpClientHandler = true; } - _configurer = new ConfigureConfigServerClientOptions(_configuration); + _changeTokenRegistration = ChangeToken.OnChange(effectiveConfiguration.GetReloadToken, () => OnSettingsChanged(true)); + } + + private void OnSettingsChanged(bool skipTimerDueTime) + { + LogEnteringOnSettingsChanged(); - ClientOptions = clientOptions; + ConfigServerClientOptions newOptions = _defaultOptions.Clone(); - if (httpClientHandler == null) + try { - _httpClientHandler = new HttpClientHandler(); - _ownsHttpClientHandler = true; + _configurer.Configure(newOptions); } - else + catch (Exception exception) { - _httpClientHandler = httpClientHandler; + LogBindSettingsFailed(exception); + throw; } - OnSettingsChanged(); - } + lock (_lifecycleLock) + { + if (_shutdownToken.IsCancellationRequested) + { + return; + } - private void OnSettingsChanged() - { - TimeSpan existingPollingInterval = ClientOptions.PollingInterval; + TimeSpan previousPollingInterval = _clientOptions.PollingInterval; + int previousTokenRenewRate = _clientOptions.TokenRenewRate; + + _clientOptions = newOptions; - _configurer.Configure(ClientOptions); + UpdateConfigurationReloadTimer(newOptions, previousPollingInterval, skipTimerDueTime); + UpdateVaultRenewTimer(newOptions, previousTokenRenewRate, skipTimerDueTime); + } + } - if (_hasConfiguration) + private void UpdateConfigurationReloadTimer(ConfigServerClientOptions optionsSnapshot, TimeSpan previousPollingInterval, bool skipDueTime) + { + if (optionsSnapshot.PollingInterval == TimeSpan.Zero || !optionsSnapshot.Enabled) { - _configuration.GetReloadToken().RegisterChangeCallback(_ => OnSettingsChanged(), null); + _configurationReloadTimer?.Dispose(); + _configurationReloadTimer = null; } + else if (_configurationReloadTimer == null) + { + _configurationReloadTimer = new Timer(_ => ConfigurationReloadTimerTick(), null, skipDueTime ? TimeSpan.Zero : optionsSnapshot.PollingInterval, + optionsSnapshot.PollingInterval); + } + else if (previousPollingInterval != optionsSnapshot.PollingInterval) + { + _configurationReloadTimer.Change(skipDueTime ? TimeSpan.Zero : optionsSnapshot.PollingInterval, optionsSnapshot.PollingInterval); + } + } - if (ClientOptions.PollingInterval == TimeSpan.Zero || !ClientOptions.Enabled) + private void UpdateVaultRenewTimer(ConfigServerClientOptions optionsSnapshot, int previousTokenRenewRate, bool skipDueTime) + { + if (string.IsNullOrEmpty(optionsSnapshot.Token) || optionsSnapshot.DisableTokenRenewal || + optionsSnapshot is not { Uri: not null, IsMultiServerConfiguration: false }) { - _refreshTimer?.Dispose(); - _refreshTimer = null; + _vaultRenewTimer?.Dispose(); + _vaultRenewTimer = null; } - else if (ClientOptions.Enabled) + else if (_vaultRenewTimer == null) { - if (_refreshTimer == null) - { -#pragma warning disable S4462 // Calls to "async" methods should not be blocking - // Justification: Configuration sources and providers don't support async. - _refreshTimer = new Timer(_ => DoPolledLoadAsync().GetAwaiter().GetResult(), null, TimeSpan.Zero, ClientOptions.PollingInterval); -#pragma warning restore S4462 // Calls to "async" methods should not be blocking - } - else if (existingPollingInterval != ClientOptions.PollingInterval) - { - _refreshTimer.Change(TimeSpan.Zero, ClientOptions.PollingInterval); - } + TimeSpan refreshInterval = TimeSpan.FromMilliseconds(optionsSnapshot.TokenRenewRate); + _vaultRenewTimer = new Timer(_ => VaultRenewTimerTick(), null, skipDueTime ? TimeSpan.Zero : refreshInterval, refreshInterval); + } + else if (previousTokenRenewRate != optionsSnapshot.TokenRenewRate) + { + TimeSpan refreshInterval = TimeSpan.FromMilliseconds(optionsSnapshot.TokenRenewRate); + _vaultRenewTimer.Change(skipDueTime ? TimeSpan.Zero : refreshInterval, refreshInterval); } } /// - /// DoPolledLoad is called by a Timer callback, so must catch all exceptions. + /// ConfigurationReloadTimerTick is called by a Timer callback, so must catch all exceptions. /// - private async Task DoPolledLoadAsync() + private void ConfigurationReloadTimerTick() { - LogEnteringTimerCycle(); - bool lockTaken = false; + LogEnteringConfigurationReloadCycle(); + +#if NET10_0_OR_GREATER + bool lockTaken = _configurationReloadTickLock.TryEnter(); +#else + bool lockTaken = Monitor.TryEnter(_configurationReloadTickLock); +#endif try { - lockTaken = _timerTickLock != null && await _timerTickLock.WaitAsync(0); + if (!lockTaken || _shutdownToken.IsCancellationRequested) + { + LogSkippingConfigurationReloadCycle(); + return; + } + + LogConfigurationReloadCycleLockObtained(); + ConfigServerClientOptions optionsSnapshot = ClientOptions; + +#pragma warning disable S4462 // Calls to "async" methods should not be blocking + // Justification: Configuration sources and providers don't support async. + UpdateDiscoveryAsync(optionsSnapshot, false, _shutdownToken).GetAwaiter().GetResult(); + DoLoadAsync(optionsSnapshot, true, _shutdownToken).GetAwaiter().GetResult(); +#pragma warning restore S4462 // Calls to "async" methods should not be blocking + + LogConfigurationReloadCycleCompleted(); } - catch (ObjectDisposedException) + catch (Exception exception) { - // Ignore exception originating from potential race condition. + if (!_shutdownToken.IsCancellationRequested) + { + LogConfigurationReloadCycleFailed(exception); + } } - - try + finally { if (lockTaken) { - LogExclusiveLockObtained(); - await DoLoadAsync(true, CancellationToken.None); +#if NET10_0_OR_GREATER + _configurationReloadTickLock.Exit(); +#else + Monitor.Exit(_configurationReloadTickLock); +#endif } - else + } + } + + /// + /// VaultRenewTimerTick is called by a Timer callback, so must catch all exceptions. + /// + private void VaultRenewTimerTick() + { + LogEnteringVaultRenewCycle(); + +#if NET10_0_OR_GREATER + bool lockTaken = _vaultRenewTickLock.TryEnter(); +#else + bool lockTaken = Monitor.TryEnter(_vaultRenewTickLock); +#endif + + try + { + if (!lockTaken || _shutdownToken.IsCancellationRequested) { - LogSkippingCycle(); + LogSkippingVaultRenewCycle(); + return; } + + LogVaultRenewCycleLockObtained(); + +#pragma warning disable S4462 // Calls to "async" methods should not be blocking + // Justification: Configuration sources and providers don't support async. + RefreshVaultTokenAsync(ClientOptions, _shutdownToken).GetAwaiter().GetResult(); +#pragma warning restore S4462 // Calls to "async" methods should not be blocking + + LogVaultRenewCycleCompleted(); } catch (Exception exception) { - LogCouldNotReloadDuringPolling(exception); + if (!_shutdownToken.IsCancellationRequested) + { + LogVaultRenewCycleFailed(exception); + } } finally { if (lockTaken) { - LogTimerCycleCompleted(); - - try - { - _timerTickLock?.Release(); - } - catch (ObjectDisposedException) - { - // Ignore exception originating from potential race condition. - } +#if NET10_0_OR_GREATER + _vaultRenewTickLock.Exit(); +#else + Monitor.Exit(_vaultRenewTickLock); +#endif } } } /// - /// Loads configuration data from the Spring Cloud Configuration Server as specified by the . + /// Loads configuration data from the Spring Cloud Config Server as specified by the . /// public override void Load() { + long previousIsReload = Interlocked.CompareExchange(ref _isReload, 1, 0); + + if (previousIsReload == 0) + { + OnSettingsChanged(false); + } + + ConfigServerClientOptions optionsSnapshot = ClientOptions; + + try + { #pragma warning disable S4462 // Calls to "async" methods should not be blocking - // Justification: Configuration sources and providers don't support async. - LoadInternalAsync(true, CancellationToken.None).GetAwaiter().GetResult(); + // Justification: Configuration sources and providers don't support async. + LoadInternalAsync(optionsSnapshot, true, _shutdownToken).GetAwaiter().GetResult(); #pragma warning restore S4462 // Calls to "async" methods should not be blocking + } + catch (OperationCanceledException) when (_shutdownToken.IsCancellationRequested) + { + // Expected during disposal; silently ignore. + } + catch (ConfigServerException exception) + { + if (optionsSnapshot.FailFast) + { + throw; + } + + LogFetchingRemoteConfigurationFailed(exception); + } } - internal async Task LoadInternalAsync(bool updateDictionary, CancellationToken cancellationToken) + internal async Task LoadInternalAsync(ConfigServerClientOptions optionsSnapshot, bool updateDictionary, + CancellationToken cancellationToken) { - if (!ClientOptions.Enabled) + if (!optionsSnapshot.Enabled) { LogConfigServerClientDisabled(); return null; } - if (IsDiscoveryFirstEnabled()) - { - _configServerDiscoveryService ??= new ConfigServerDiscoveryService(_configuration, ClientOptions, _loggerFactory); - await DiscoverServerInstancesAsync(_configServerDiscoveryService, cancellationToken); - } + await UpdateDiscoveryAsync(optionsSnapshot, optionsSnapshot.FailFast, cancellationToken); - // Adds client settings (e.g. spring:cloud:config:uri, etc.) to the Data dictionary - AddConfigServerClientOptions(); - - if (ClientOptions is { Retry.Enabled: true, FailFast: true }) + if (optionsSnapshot is { Retry.Enabled: true, FailFast: true }) { int attempts = 0; - int backOff = ClientOptions.Retry.InitialInterval; + int backOff = optionsSnapshot.Retry.InitialInterval; + List errors = []; do { - LogFetchingConfiguration(); - try { - return await DoLoadAsync(updateDictionary, cancellationToken); + return await DoLoadAsync(optionsSnapshot, updateDictionary, cancellationToken); } catch (ConfigServerException exception) { - LogFailedFetchingConfiguration(exception); + errors.Add(exception); attempts++; - if (attempts < ClientOptions.Retry.MaxAttempts) + if (attempts < optionsSnapshot.Retry.MaxAttempts) { - Thread.CurrentThread.Join(backOff); - int nextBackOff = (int)(backOff * ClientOptions.Retry.Multiplier); - backOff = Math.Min(nextBackOff, ClientOptions.Retry.MaxInterval); + await Task.Delay(backOff, cancellationToken); + int nextBackOff = (int)(backOff * optionsSnapshot.Retry.Multiplier); + backOff = Math.Min(nextBackOff, optionsSnapshot.Retry.MaxInterval); } else { - throw; + throw new ConfigServerException($"Failed fetching remote configuration from server(s) after {attempts} attempts.", + new AggregateException(null, errors)); } } } while (true); } - LogFetchingConfiguration(); - return await DoLoadAsync(updateDictionary, cancellationToken); + return await DoLoadAsync(optionsSnapshot, updateDictionary, cancellationToken); } - internal async Task DoLoadAsync(bool updateDictionary, CancellationToken cancellationToken) + internal async Task DoLoadAsync(ConfigServerClientOptions optionsSnapshot, bool updateDictionary, CancellationToken cancellationToken) { + LogFetchingRemoteConfiguration(); + + ApplyLastDiscoveryLookupResultToClientOptions(optionsSnapshot); + Exception? error = null; // Get list of Config Server uris to check - List uris = ClientOptions.GetUris(); + List uris = optionsSnapshot.GetUris(); try { - foreach (string label in GetLabels()) + foreach (string label in GetLabels(optionsSnapshot)) { LogProcessingLabel(label); @@ -279,7 +398,7 @@ public override void Load() } // Invoke Config Servers - ConfigEnvironment? env = await RemoteLoadAsync(uris, label, cancellationToken); + ConfigEnvironment? env = await RemoteLoadAsync(optionsSnapshot, uris, label, cancellationToken); // Update configuration Data dictionary with any results if (env != null) @@ -289,6 +408,7 @@ public override void Load() if (updateDictionary) { var data = new Dictionary(StringComparer.OrdinalIgnoreCase); + CopyLastDiscoveryLookupResultToData(data, optionsSnapshot.Discovery.Enabled); if (!string.IsNullOrEmpty(env.State)) { @@ -301,16 +421,12 @@ public override void Load() } IList sources = env.PropertySources; - int index = sources.Count - 1; - for (; index >= 0; index--) + for (int index = sources.Count - 1; index >= 0; index--) { AddPropertySource(sources[index], data); } - // Adds client settings (e.g. spring:cloud:config:uri, etc.) back to the (new) Data dictionary - AddConfigServerClientOptions(data); - if (!AreDictionariesEqual(Data, data)) { LogDataChanged(); @@ -332,15 +448,47 @@ public override void Load() error = exception; } - LogCouldNotLocatePropertySource(error); + throw new ConfigServerException("Failed fetching remote configuration from server(s).", error); + } + + internal void ApplyLastDiscoveryLookupResultToClientOptions(ConfigServerClientOptions optionsSnapshot) + { + DiscoveryLookupResult? lastResult = _lastDiscoveryLookupResult; - if (ClientOptions.FailFast) + if (lastResult != null && optionsSnapshot.Discovery.Enabled) { - LogFailFastEnabled(error); - throw new ConfigServerException("Could not locate PropertySource, fail fast property is set, failing", error); + optionsSnapshot.Uri = lastResult.ConfigServerUri; + + if (lastResult.Username != null) + { + optionsSnapshot.Username = lastResult.Username; + } + + if (lastResult.Password != null) + { + optionsSnapshot.Password = lastResult.Password; + } } + } - return null; + private void CopyLastDiscoveryLookupResultToData(Dictionary data, bool isDiscoveryEnabled) + { + DiscoveryLookupResult? lastResult = _lastDiscoveryLookupResult; + + if (lastResult != null && isDiscoveryEnabled) + { + data["spring:cloud:config:uri"] = lastResult.ConfigServerUri; + + if (lastResult.Username != null) + { + data["spring:cloud:config:username"] = lastResult.Username; + } + + if (lastResult.Password != null) + { + data["spring:cloud:config:password"] = lastResult.Password; + } + } } private static bool AreDictionariesEqual(IDictionary first, Dictionary second) @@ -350,93 +498,92 @@ private static bool AreDictionariesEqual(IDictionary second.ContainsKey(firstKey) && EqualityComparer.Default.Equals(first[firstKey], second[firstKey])); } - internal string[] GetLabels() + internal string[] GetLabels(ConfigServerClientOptions optionsSnapshot) { - if (string.IsNullOrWhiteSpace(ClientOptions.Label)) + if (string.IsNullOrWhiteSpace(optionsSnapshot.Label)) { return EmptyLabels; } - return ClientOptions.Label.Split(CommaDelimiter, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + return optionsSnapshot.Label.Split(CommaDelimiter, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); } - private async Task DiscoverServerInstancesAsync(ConfigServerDiscoveryService configServerDiscoveryService, CancellationToken cancellationToken) + private async Task UpdateDiscoveryAsync(ConfigServerClientOptions optionsSnapshot, bool failFast, CancellationToken cancellationToken) { - IServiceInstance[] instances = (await configServerDiscoveryService.GetConfigServerInstancesAsync(cancellationToken)).ToArray(); - - if (instances.Length == 0) + if (optionsSnapshot.Discovery.Enabled) { - if (ClientOptions.FailFast) + List instances = await _configServerDiscoveryService.GetConfigServerInstancesAsync(optionsSnapshot, cancellationToken); + SetLastDiscoveryLookupResult(instances); + + if (instances.Count == 0 && failFast) { throw new ConfigServerException("Could not locate Config Server via discovery, are you missing a Discovery service assembly?"); } - - return; } - - UpdateSettingsFromDiscovery(instances, ClientOptions); + else + { + SetLastDiscoveryLookupResult([]); + } } - internal void UpdateSettingsFromDiscovery(IEnumerable instances, ConfigServerClientOptions clientOptions) + internal void SetLastDiscoveryLookupResult(IEnumerable instances) { - var endpoints = new StringBuilder(); + var endpointBuilder = new StringBuilder(); + string? username = null; + string? password = null; foreach (IServiceInstance instance in instances) { + if (instance.Metadata.TryGetValue("password", out string? instancePassword)) + { + instance.Metadata.TryGetValue("user", out string? instanceUsername); + username = instanceUsername ?? "user"; + password = instancePassword; + } + string uri = instance.Uri.ToString(); - IReadOnlyDictionary metaData = instance.Metadata; - if (metaData.Count > 0) + if (instance.Metadata.TryGetValue("configPath", out string? path) && path != null) { - if (metaData.TryGetValue("password", out string? password)) + if (uri.EndsWith('/') && path.StartsWith('/')) { - metaData.TryGetValue("user", out string? username); - username ??= "user"; - clientOptions.Username = username; - clientOptions.Password = password; + uri = uri[..^1]; } - if (metaData.TryGetValue("configPath", out string? path) && path != null) - { - if (uri.EndsWith('/') && path.StartsWith('/')) - { - uri = uri[..^1]; - } - - uri += path; - } + uri += path; } - endpoints.Append(uri); - endpoints.Append(','); + endpointBuilder.Append(uri); + endpointBuilder.Append(','); } - if (endpoints.Length > 0) + if (endpointBuilder.Length > 0) { - string uris = endpoints.ToString(0, endpoints.Length - 1); - clientOptions.Uri = uris; + string uris = endpointBuilder.ToString(0, endpointBuilder.Length - 1); + _lastDiscoveryLookupResult = new DiscoveryLookupResult(uris, username, password); + } + else + { + _lastDiscoveryLookupResult = null; } } internal async Task ProvideRuntimeReplacementsAsync(ICollection discoveryClientsFromServiceProvider, CancellationToken cancellationToken) { - if (_configServerDiscoveryService is not null) - { - await _configServerDiscoveryService.ProvideRuntimeReplacementsAsync(discoveryClientsFromServiceProvider, cancellationToken); - } + await _configServerDiscoveryService.ProvideRuntimeReplacementsAsync(discoveryClientsFromServiceProvider, cancellationToken); } internal async Task ShutdownAsync(CancellationToken cancellationToken) { - if (_configServerDiscoveryService is not null) - { - await _configServerDiscoveryService.ShutdownAsync(cancellationToken); - } + await _configServerDiscoveryService.ShutdownAsync(cancellationToken); } /// - /// Creates the that will be used in accessing the Spring Cloud Configuration server. + /// Creates the that will be used in accessing the Spring Cloud Config server. /// + /// + /// A snapshot of the client options to use for this request. + /// /// /// The Uri used when accessing the server. /// @@ -446,7 +593,8 @@ internal async Task ShutdownAsync(CancellationToken cancellationToken) /// /// The HttpRequestMessage built from the path. /// - internal async Task GetRequestMessageAsync(Uri requestUri, CancellationToken cancellationToken) + internal async Task GetRequestMessageAsync(ConfigServerClientOptions optionsSnapshot, Uri requestUri, + CancellationToken cancellationToken) { var uriWithoutUserInfo = new Uri(requestUri.GetComponents(UriComponents.HttpRequestUrl, UriFormat.UriEscaped)); var requestMessage = new HttpRequestMessage(HttpMethod.Get, uriWithoutUserInfo); @@ -460,105 +608,47 @@ internal async Task GetRequestMessageAsync(Uri requestUri, C } else { - if (!string.IsNullOrEmpty(ClientOptions.AccessTokenUri)) + if (!string.IsNullOrEmpty(optionsSnapshot.AccessTokenUri)) { - using HttpClient httpClient = CreateHttpClient(ClientOptions); - var accessTokenUri = new Uri(ClientOptions.AccessTokenUri); + using HttpClient httpClient = CreateHttpClient(optionsSnapshot); + var accessTokenUri = new Uri(optionsSnapshot.AccessTokenUri); string accessToken = - await httpClient.GetAccessTokenAsync(accessTokenUri, ClientOptions.ClientId, ClientOptions.ClientSecret, cancellationToken); + await httpClient.GetAccessTokenAsync(accessTokenUri, optionsSnapshot.ClientId, optionsSnapshot.ClientSecret, cancellationToken); LogAccessTokenFetched(accessTokenUri.ToMaskedString()); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); } } - if (!string.IsNullOrEmpty(ClientOptions.Token) && ClientOptions is { Uri: not null, IsMultiServerConfiguration: false }) + if (!string.IsNullOrEmpty(optionsSnapshot.Token) && optionsSnapshot is { Uri: not null, IsMultiServerConfiguration: false }) { - if (!ClientOptions.DisableTokenRenewal) - { - RenewToken(); - } - - requestMessage.Headers.Add(TokenHeader, ClientOptions.Token); + requestMessage.Headers.Add(TokenHeader, optionsSnapshot.Token); } return requestMessage; } - /// - /// Adds the client settings for the Configuration Server to the data dictionary. - /// - internal void AddConfigServerClientOptions() - { - Dictionary data = Data.ToDictionary(entry => entry.Key, entry => entry.Value, StringComparer.OrdinalIgnoreCase); - - AddConfigServerClientOptions(data); - - Data = data; - } - - /// - /// Adds the client settings for the Configuration Server to the data dictionary. - /// - /// - /// The client settings to add. - /// - private void AddConfigServerClientOptions(Dictionary data) - { - data["spring:cloud:config:enabled"] = ClientOptions.Enabled.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:failFast"] = ClientOptions.FailFast.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:env"] = ClientOptions.Environment; - data["spring:cloud:config:label"] = ClientOptions.Label; - data["spring:cloud:config:name"] = ClientOptions.Name; - data["spring:cloud:config:uri"] = ClientOptions.Uri; - data["spring:cloud:config:username"] = ClientOptions.Username; - data["spring:cloud:config:password"] = ClientOptions.Password; - data["spring:cloud:config:token"] = ClientOptions.Token; - data["spring:cloud:config:timeout"] = ClientOptions.Timeout.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:pollingInterval"] = ClientOptions.PollingInterval.ToString(null, CultureInfo.InvariantCulture); - data["spring:cloud:config:validateCertificates"] = ClientOptions.ValidateCertificates.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:accessTokenUri"] = ClientOptions.AccessTokenUri; - data["spring:cloud:config:clientSecret"] = ClientOptions.ClientSecret; - data["spring:cloud:config:clientId"] = ClientOptions.ClientId; - data["spring:cloud:config:tokenTtl"] = ClientOptions.TokenTtl.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:tokenRenewRate"] = ClientOptions.TokenRenewRate.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:disableTokenRenewal"] = ClientOptions.DisableTokenRenewal.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:retry:enabled"] = ClientOptions.Retry.Enabled.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:retry:initialInterval"] = ClientOptions.Retry.InitialInterval.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:retry:maxInterval"] = ClientOptions.Retry.MaxInterval.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:retry:multiplier"] = ClientOptions.Retry.Multiplier.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:retry:maxAttempts"] = ClientOptions.Retry.MaxAttempts.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:discovery:enabled"] = ClientOptions.Discovery.Enabled.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:discovery:serviceId"] = ClientOptions.Discovery.ServiceId; - data["spring:cloud:config:health:enabled"] = ClientOptions.Health.Enabled.ToString(CultureInfo.InvariantCulture); - data["spring:cloud:config:health:timeToLive"] = ClientOptions.Health.TimeToLive.ToString(CultureInfo.InvariantCulture); - - foreach ((string headerName, string headerValue) in ClientOptions.Headers) - { - data[$"spring:cloud:config:headers:{headerName}"] = headerValue; - } - } - - internal async Task RemoteLoadAsync(List requestUris, string? label, CancellationToken cancellationToken) + internal async Task RemoteLoadAsync(ConfigServerClientOptions optionsSnapshot, List requestUris, string? label, + CancellationToken cancellationToken) { LogRemoteLoadEntered(nameof(RemoteLoadAsync)); // Get client if not already set - using HttpClient httpClient = CreateHttpClient(ClientOptions); + using HttpClient httpClient = CreateHttpClient(optionsSnapshot); Exception? error = null; foreach (Uri requestUri in requestUris) { // Make Config Server URI from settings - Uri uri = BuildConfigServerUri(requestUri, label); + Uri uri = BuildConfigServerUri(optionsSnapshot, requestUri, label); LogTryingToConnect(uri.ToMaskedString()); // Get the request message LogBuildingHttpRequest(); - HttpRequestMessage request = await GetRequestMessageAsync(uri, cancellationToken); + HttpRequestMessage request = await GetRequestMessageAsync(optionsSnapshot, uri, cancellationToken); // Invoke Config Server try @@ -603,7 +693,7 @@ private void AddConfigServerClientOptions(Dictionary data) if (error != null) { - throw error; + ExceptionDispatchInfo.Capture(error).Throw(); } return null; @@ -612,6 +702,9 @@ private void AddConfigServerClientOptions(Dictionary data) /// /// Creates the Uri that will be used in accessing the Configuration Server. /// + /// + /// A snapshot of the client options to use for URI construction. + /// /// /// Base server uri to use. /// @@ -621,23 +714,23 @@ private void AddConfigServerClientOptions(Dictionary data) /// /// The request URI for the Configuration Server. /// - internal Uri BuildConfigServerUri(Uri serverUri, string? label) + internal Uri BuildConfigServerUri(ConfigServerClientOptions optionsSnapshot, Uri serverUri, string? label) { ArgumentNullException.ThrowIfNull(serverUri); var uriBuilder = new UriBuilder(serverUri); - if (!string.IsNullOrEmpty(ClientOptions.Username)) + if (!string.IsNullOrEmpty(optionsSnapshot.Username)) { - uriBuilder.UserName = WebUtility.UrlEncode(ClientOptions.Username); + uriBuilder.UserName = WebUtility.UrlEncode(optionsSnapshot.Username); } - if (!string.IsNullOrEmpty(ClientOptions.Password)) + if (!string.IsNullOrEmpty(optionsSnapshot.Password)) { - uriBuilder.Password = WebUtility.UrlEncode(ClientOptions.Password); + uriBuilder.Password = WebUtility.UrlEncode(optionsSnapshot.Password); } - string pathSuffix = $"{WebUtility.UrlEncode(ClientOptions.Name)}/{WebUtility.UrlEncode(ClientOptions.Environment)}"; + string pathSuffix = $"{WebUtility.UrlEncode(optionsSnapshot.Name)}/{WebUtility.UrlEncode(optionsSnapshot.Environment)}"; if (!string.IsNullOrWhiteSpace(label)) { @@ -698,33 +791,27 @@ private void AddPropertySource(PropertySource? source, Dictionary RefreshVaultTokenAsync(CancellationToken.None).GetAwaiter().GetResult(), null, - TimeSpan.FromMilliseconds(ClientOptions.TokenRenewRate), TimeSpan.FromMilliseconds(ClientOptions.TokenRenewRate)); -#pragma warning restore S4462 // Calls to "async" methods should not be blocking - } - - // fire and forget - internal async Task RefreshVaultTokenAsync(CancellationToken cancellationToken) + /// + /// Extends the lease of the current HashiCorp Vault token; it does not generate a new token. A new token is only picked up when the configuration + /// changes and reconfigures the timer. + /// + internal async Task RefreshVaultTokenAsync(ConfigServerClientOptions optionsSnapshot, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(ClientOptions.Token)) + if (string.IsNullOrEmpty(optionsSnapshot.Token)) { return; } - string obscuredToken = $"{ClientOptions.Token[..4]}[*]{ClientOptions.Token[^4..]}"; + string obscuredToken = $"{optionsSnapshot.Token[..4]}[*]{optionsSnapshot.Token[^4..]}"; try { - using HttpClient httpClient = CreateHttpClient(ClientOptions); + using HttpClient httpClient = CreateHttpClient(optionsSnapshot); - Uri uri = GetVaultRenewUri(); - HttpRequestMessage message = await GetVaultRenewRequestMessageAsync(uri, cancellationToken); + Uri uri = GetVaultRenewUri(optionsSnapshot); + HttpRequestMessage message = await GetVaultRenewRequestMessageAsync(optionsSnapshot, uri, cancellationToken); - LogRenewingVaultToken(obscuredToken, ClientOptions.TokenTtl, uri.ToMaskedString()); + LogRenewingVaultToken(obscuredToken, optionsSnapshot.TokenTtl, uri.ToMaskedString()); using HttpResponseMessage response = await httpClient.SendAsync(message, cancellationToken); if (response.StatusCode != HttpStatusCode.OK) @@ -738,9 +825,9 @@ internal async Task RefreshVaultTokenAsync(CancellationToken cancellationToken) } } - private Uri GetVaultRenewUri() + private static Uri GetVaultRenewUri(ConfigServerClientOptions optionsSnapshot) { - string baseUri = ClientOptions.Uri!.Split(',')[0].Trim(); + string baseUri = optionsSnapshot.Uri!.Split(',')[0].Trim(); if (!baseUri.EndsWith('/')) { @@ -750,42 +837,38 @@ private Uri GetVaultRenewUri() return new Uri(baseUri + VaultRenewPath, UriKind.RelativeOrAbsolute); } - private async Task GetVaultRenewRequestMessageAsync(Uri requestUri, CancellationToken cancellationToken) + private async Task GetVaultRenewRequestMessageAsync(ConfigServerClientOptions optionsSnapshot, Uri requestUri, + CancellationToken cancellationToken) { var uriWithoutUserInfo = new Uri(requestUri.GetComponents(UriComponents.HttpRequestUrl, UriFormat.UriEscaped)); var requestMessage = new HttpRequestMessage(HttpMethod.Post, uriWithoutUserInfo); - if (!string.IsNullOrEmpty(ClientOptions.AccessTokenUri)) + if (!string.IsNullOrEmpty(optionsSnapshot.AccessTokenUri)) { - using HttpClient httpClient = CreateHttpClient(ClientOptions); - var accessTokenUri = new Uri(ClientOptions.AccessTokenUri); + using HttpClient httpClient = CreateHttpClient(optionsSnapshot); + var accessTokenUri = new Uri(optionsSnapshot.AccessTokenUri); - string accessToken = await httpClient.GetAccessTokenAsync(accessTokenUri, ClientOptions.ClientId, ClientOptions.ClientSecret, cancellationToken); + string accessToken = + await httpClient.GetAccessTokenAsync(accessTokenUri, optionsSnapshot.ClientId, optionsSnapshot.ClientSecret, cancellationToken); LogAccessTokenFetched(accessTokenUri.ToMaskedString()); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); } - if (!string.IsNullOrEmpty(ClientOptions.Token)) + if (!string.IsNullOrEmpty(optionsSnapshot.Token)) { - requestMessage.Headers.Add(VaultTokenHeader, ClientOptions.Token); + requestMessage.Headers.Add(VaultTokenHeader, optionsSnapshot.Token); } - int renewTtlInSeconds = ClientOptions.TokenTtl / 1000; + int renewTtlInSeconds = optionsSnapshot.TokenTtl / 1000; string json = $"{{\"increment\":{renewTtlInSeconds}}}"; requestMessage.Content = new StringContent(json, Encoding.UTF8, "application/json"); return requestMessage; } - internal bool IsDiscoveryFirstEnabled() - { - IConfigurationSection clientConfigSection = _configuration.GetSection(ConfigServerClientOptions.ConfigurationPrefix); - return clientConfigSection.GetValue("discovery:enabled", ClientOptions.Discovery.Enabled); - } - /// - /// Creates an appropriately configured HttpClient that can be used in communicating with the Spring Cloud Configuration Server. + /// Creates an appropriately configured HttpClient that can be used in communicating with the Spring Cloud Config Server. /// /// /// The settings used to configure the HttpClient. @@ -796,17 +879,11 @@ internal bool IsDiscoveryFirstEnabled() internal HttpClient CreateHttpClient(ConfigServerClientOptions clientOptions) { ArgumentNullException.ThrowIfNull(clientOptions); - ObjectDisposedException.ThrowIf(_httpClientHandler == null, this); - - var clientCertificateConfigurer = new ClientCertificateHttpClientHandlerConfigurer(OptionsMonitorWrapper.Create(clientOptions.ClientCertificate)); - clientCertificateConfigurer.Configure("ConfigServer", _httpClientHandler); - - var validateCertificatesHandler = - new ValidateCertificatesHttpClientHandlerConfigurer(OptionsMonitorWrapper.Create(clientOptions)); - validateCertificatesHandler.Configure(Options.DefaultName, _httpClientHandler); + HttpClientHandler handler = _createHttpClientHandler(); + ConfigureHttpClientHandler(handler, clientOptions); - var httpClient = new HttpClient(_httpClientHandler, false); + var httpClient = new HttpClient(handler, _disposeHttpClientHandler); httpClient.ConfigureForSteeltoe(clientOptions.HttpTimeout); foreach ((string headerName, string headerValue) in clientOptions.Headers) @@ -817,6 +894,19 @@ internal HttpClient CreateHttpClient(ConfigServerClientOptions clientOptions) return httpClient; } + private static void ConfigureHttpClientHandler(HttpClientHandler httpClientHandler, ConfigServerClientOptions optionsSnapshot) + { + httpClientHandler.ClientCertificates.Clear(); + + var clientCertificateConfigurer = new ClientCertificateHttpClientHandlerConfigurer(OptionsMonitorWrapper.Create(optionsSnapshot.ClientCertificate)); + clientCertificateConfigurer.Configure("ConfigServer", httpClientHandler); + + var validateCertificatesHandler = + new ValidateCertificatesHttpClientHandlerConfigurer(OptionsMonitorWrapper.Create(optionsSnapshot)); + + validateCertificatesHandler.Configure(Options.DefaultName, httpClientHandler); + } + private static bool IsSocketError(Exception exception) { return exception is HttpRequestException && exception.InnerException is SocketException; @@ -824,65 +914,107 @@ private static bool IsSocketError(Exception exception) public void Dispose() { - _refreshTimer?.Dispose(); - _refreshTimer = null; + lock (_lifecycleLock) + { + if (_shutdownToken.IsCancellationRequested) + { + return; + } + + LogDisposing(); + _shutdownTokenSource.Cancel(); + _changeTokenRegistration.Dispose(); + ShutdownTimers(); + _shutdownTokenSource.Dispose(); + } + } + + private void ShutdownTimers() + { + // This is fast because in-flight timer callbacks terminate quickly: outstanding HTTP requests are canceled via shutdown token. - _timerTickLock?.Dispose(); - _timerTickLock = null; + using var reloadTimerStopped = new ManualResetEvent(false); + using var vaultTimerStopped = new ManualResetEvent(false); + + if (_configurationReloadTimer == null || !_configurationReloadTimer.Dispose(reloadTimerStopped)) + { + reloadTimerStopped.Set(); + } - if (_ownsHttpClientHandler) + if (_vaultRenewTimer == null || !_vaultRenewTimer.Dispose(vaultTimerStopped)) { - _httpClientHandler?.Dispose(); + vaultTimerStopped.Set(); } - _httpClientHandler = null; + WaitHandle.WaitAll([ + reloadTimerStopped, + vaultTimerStopped + ]); + + _configurationReloadTimer = null; + _vaultRenewTimer = null; } - [LoggerMessage(Level = LogLevel.Trace, Message = "Entering timer cycle.")] - private partial void LogEnteringTimerCycle(); + [LoggerMessage(Level = LogLevel.Trace, Message = "Rebinding options after outer configuration change.")] + private partial void LogEnteringOnSettingsChanged(); - [LoggerMessage(Level = LogLevel.Trace, Message = "Exclusive lock obtained.")] - private partial void LogExclusiveLockObtained(); + [LoggerMessage(Level = LogLevel.Warning, Message = "Failed to bind Config Server options from configuration.")] + private partial void LogBindSettingsFailed(Exception exception); - [LoggerMessage(Level = LogLevel.Trace, Message = "Previous cycle is still running, or already disposed; skipping this cycle.")] - private partial void LogSkippingCycle(); + [LoggerMessage(Level = LogLevel.Debug, Message = "Entering remote configuration reload polling cycle.")] + private partial void LogEnteringConfigurationReloadCycle(); - [LoggerMessage(Level = LogLevel.Warning, Message = "Could not reload configuration during polling.")] - private partial void LogCouldNotReloadDuringPolling(Exception exception); + [LoggerMessage(Level = LogLevel.Trace, Message = "Remote configuration reload polling lock obtained.")] + private partial void LogConfigurationReloadCycleLockObtained(); - [LoggerMessage(Level = LogLevel.Trace, Message = "Timer cycle completed, releasing exclusive lock.")] - private partial void LogTimerCycleCompleted(); + [LoggerMessage(Level = LogLevel.Trace, Message = "Previous remote configuration reload cycle is still running, or already disposed; skipping this cycle.")] + private partial void LogSkippingConfigurationReloadCycle(); - [LoggerMessage(Level = LogLevel.Information, Message = "Config Server client disabled, not fetching configuration.")] - private partial void LogConfigServerClientDisabled(); + [LoggerMessage(Level = LogLevel.Warning, Message = "Failed to reload remote configuration during polling.")] + private partial void LogConfigurationReloadCycleFailed(Exception exception); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Remote configuration reload polling cycle completed, releasing lock.")] + private partial void LogConfigurationReloadCycleCompleted(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Entering Vault token renewal cycle.")] + private partial void LogEnteringVaultRenewCycle(); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Vault token renewal lock obtained.")] + private partial void LogVaultRenewCycleLockObtained(); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Previous Vault token renewal cycle is still running, or already disposed; skipping this cycle.")] + private partial void LogSkippingVaultRenewCycle(); - [LoggerMessage(Level = LogLevel.Debug, Message = "Fetching configuration from server(s).")] - private partial void LogFetchingConfiguration(); + [LoggerMessage(Level = LogLevel.Warning, Message = "Failed to renew Vault token.")] + private partial void LogVaultRenewCycleFailed(Exception exception); - [LoggerMessage(Level = LogLevel.Warning, Message = "Failed fetching configuration from server(s).")] - private partial void LogFailedFetchingConfiguration(Exception exception); + [LoggerMessage(Level = LogLevel.Trace, Message = "Vault token renewal cycle completed, releasing lock.")] + private partial void LogVaultRenewCycleCompleted(); + + [LoggerMessage(Level = LogLevel.Information, Message = "Config Server client disabled, not fetching remote configuration.")] + private partial void LogConfigServerClientDisabled(); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Fetching remote configuration from server(s).")] + private partial void LogFetchingRemoteConfiguration(); [LoggerMessage(Level = LogLevel.Trace, Message = "Processing label '{Label}'.")] private partial void LogProcessingLabel(string? label); - [LoggerMessage(Level = LogLevel.Debug, Message = "Multiple Config Server Uris listed.")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Multiple Config Server uris listed.")] private partial void LogMultipleConfigServerUris(); [LoggerMessage(Level = LogLevel.Debug, Message = "Located environment with name {Name}, profiles {Profiles}, label {Label}, version {Version} and state {State}.")] private partial void LogEnvironmentLocated(string? name, string profiles, string? label, string? version, string? state); - [LoggerMessage(Level = LogLevel.Trace, Message = "Data has changed, raising configuration reload.")] + [LoggerMessage(Level = LogLevel.Trace, Message = "Remote data has changed, raising configuration reload.")] private partial void LogDataChanged(); - [LoggerMessage(Level = LogLevel.Trace, Message = "Data has not changed.")] + [LoggerMessage(Level = LogLevel.Trace, Message = "Remote data has not changed.")] private partial void LogDataNotChanged(); - [LoggerMessage(Level = LogLevel.Warning, Message = "Could not locate property source.")] - private partial void LogCouldNotLocatePropertySource(Exception? error); - - [LoggerMessage(Level = LogLevel.Trace, Message = "Failure with FailFast enabled, throwing ConfigServerException.")] - private partial void LogFailFastEnabled(Exception? error); + [LoggerMessage(Level = LogLevel.Warning, Message = "Failed fetching remote configuration from server(s).")] + private partial void LogFetchingRemoteConfigurationFailed(Exception? error); [LoggerMessage(Level = LogLevel.Debug, Message = "Adding credentials from '{RequestUri}' to Authorization header.")] private partial void LogAddingCredentials(string requestUri); @@ -922,4 +1054,9 @@ public void Dispose() [LoggerMessage(Level = LogLevel.Error, Message = "Unable to renew Vault token {Token}. The token is likely invalid or has expired.")] private partial void LogUnableToRenewVaultToken(Exception exception, string token); + + [LoggerMessage(Level = LogLevel.Trace, Message = "Disposing Config Server configuration provider.")] + private partial void LogDisposing(); + + private sealed record DiscoveryLookupResult(string ConfigServerUri, string? Username, string? Password); } diff --git a/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs b/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs index 02d2d15f0a..711a9c0796 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Steeltoe.Common.Certificates; namespace Steeltoe.Configuration.ConfigServer; @@ -13,74 +12,68 @@ internal sealed class ConfigServerConfigurationSource : IConfigurationSource { private readonly ILoggerFactory _loggerFactory; - internal List Sources { get; } = []; - internal Dictionary Properties { get; } = []; + internal List Sources { get; } + internal Dictionary Properties { get; } /// - /// Gets the default settings the Config Server client uses to contact the Config Server. + /// Gets the default options the client uses to contact Config Server. /// - internal ConfigServerClientOptions DefaultOptions { get; } + public ConfigServerClientOptions DefaultOptions { get; } /// - /// Gets the configuration the Config Server client uses to contact the Config Server. Values returned override the default values provided in - /// . + /// Gets the configuration the client uses to contact Config Server. Entries override . /// - internal IConfiguration? Configuration { get; private set; } + public IConfiguration? Configuration { get; private set; } /// - /// Initializes a new instance of the class. + /// Gets an optional delegate that further configures options from code, after settings from have been applied. /// - /// - /// the default settings used by the Config Server client. - /// - /// - /// configuration used by the Config Server client. Values will override those found in default settings. - /// - /// - /// Used for internal logging. Pass to disable logging. - /// - public ConfigServerConfigurationSource(ConfigServerClientOptions defaultOptions, IConfiguration configuration, ILoggerFactory loggerFactory) - { - ArgumentNullException.ThrowIfNull(configuration); - ArgumentNullException.ThrowIfNull(defaultOptions); - ArgumentNullException.ThrowIfNull(loggerFactory); + public Action? Configure { get; } - Configuration = configuration; - DefaultOptions = defaultOptions; - _loggerFactory = loggerFactory; - } + /// + /// Gets an optional factory to create the HTTP client handler, used to mock HTTP requests to Config Server in tests. When provided, the caller is + /// responsible for handler disposal. + /// + public Func? CreateHttpClientHandler { get; } /// /// Initializes a new instance of the class. /// /// - /// the default settings used by the Config Server client. + /// The default options the client uses to contact Config Server. /// /// - /// configuration sources used by the Config Server client. The will be built from these sources and the values will - /// override those found in . + /// Configuration sources the client uses to contact Config Server. The will be built from these, whose entries override + /// . /// /// - /// properties to be used when sources are built. + /// Configuration properties the client uses to contact Config Server. The will be built from these, whose entries override + /// . + /// + /// + /// An optional delegate that further configures options from code, after settings from the built have been applied. + /// + /// + /// An optional factory to create the HTTP client handler, used to mock HTTP requests to Config Server in tests. When provided, the caller is responsible + /// for handler disposal. /// /// /// Used for internal logging. Pass to disable logging. /// public ConfigServerConfigurationSource(ConfigServerClientOptions defaultOptions, IList sources, - IDictionary? properties, ILoggerFactory loggerFactory) + IDictionary? properties, Action? configure, Func? createHttpClientHandler, + ILoggerFactory loggerFactory) { ArgumentNullException.ThrowIfNull(defaultOptions); ArgumentNullException.ThrowIfNull(sources); ArgumentNullException.ThrowIfNull(loggerFactory); Sources = sources.ToList(); - - if (properties != null) - { - Properties = new Dictionary(properties); - } + Properties = properties != null ? new Dictionary(properties) : []; DefaultOptions = defaultOptions; + Configure = configure; + CreateHttpClientHandler = createHttpClientHandler; _loggerFactory = loggerFactory; } @@ -95,37 +88,19 @@ public ConfigServerConfigurationSource(ConfigServerClientOptions defaultOptions, /// public IConfigurationProvider Build(IConfigurationBuilder builder) { - if (Configuration == null) - { - // Create our own builder to build sources - var configurationBuilder = new ConfigurationBuilder(); - - foreach (IConfigurationSource source in Sources) - { - configurationBuilder.Add(source); - } - - // Use properties provided - foreach (KeyValuePair pair in Properties) - { - configurationBuilder.Properties.Add(pair); - } + var configurationBuilder = new ConfigurationBuilder(); - // Create configuration - Configuration = configurationBuilder.Build(); + foreach (IConfigurationSource source in Sources) + { + configurationBuilder.Add(source); } - string? clientCertificatePath = Configuration.GetValue($"{CertificateOptions.ConfigurationKeyPrefix}:ConfigServer:CertificateFilePath"); - - if (!string.IsNullOrEmpty(clientCertificatePath) && DefaultOptions.ClientCertificate.Certificate == null) + foreach (KeyValuePair pair in Properties) { - var certificateConfigurer = new ConfigureCertificateOptions(Configuration); - - var options = new CertificateOptions(); - certificateConfigurer.Configure("ConfigServer", options); - DefaultOptions.ClientCertificate.Certificate = options.Certificate; + configurationBuilder.Properties.Add(pair); } + Configuration = configurationBuilder.Build(); return new ConfigServerConfigurationProvider(this, _loggerFactory); } } diff --git a/src/Configuration/src/ConfigServer/ConfigServerDiscoveryService.cs b/src/Configuration/src/ConfigServer/ConfigServerDiscoveryService.cs index a6b86ee428..595fa993cb 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerDiscoveryService.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerDiscoveryService.cs @@ -12,6 +12,13 @@ using Steeltoe.Discovery.Configuration; using Steeltoe.Discovery.Consul; using Steeltoe.Discovery.Eureka; +using LockPrimitive = +#if NET10_0_OR_GREATER + System.Threading.Lock +#else + object +#endif + ; namespace Steeltoe.Configuration.ConfigServer; @@ -19,29 +26,46 @@ internal sealed partial class ConfigServerDiscoveryService { private static readonly AssemblyLoader AssemblyLoader = new(); private readonly IConfiguration _configuration; - private readonly ConfigServerClientOptions _options; + private readonly ILoggerFactory _loggerFactory; private readonly ILogger _logger; + private readonly LockPrimitive _initLock = new(); private ServiceProvider? _temporaryServiceProviderForDiscoveryClients; + private volatile ICollection? _discoveryClients; - internal ICollection DiscoveryClients { get; private set; } + internal ICollection? DiscoveryClients + { + get => _discoveryClients; + private set => _discoveryClients = value; + } - public ConfigServerDiscoveryService(IConfiguration configuration, ConfigServerClientOptions options, ILoggerFactory loggerFactory) + public ConfigServerDiscoveryService(IConfiguration configuration, ILoggerFactory loggerFactory) { ArgumentNullException.ThrowIfNull(configuration); - ArgumentNullException.ThrowIfNull(options); ArgumentNullException.ThrowIfNull(loggerFactory); _configuration = configuration; - _options = options; + _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); - DiscoveryClients = SetupDiscoveryClients(loggerFactory); } - // Create discovery clients to be used (hopefully only) during startup - private IDiscoveryClient[] SetupDiscoveryClients(ILoggerFactory loggerFactory) + private void EnsureInitialized() + { + if (_discoveryClients == null) + { + lock (_initLock) + { + if (_discoveryClients == null) + { + SetupDiscoveryClients(); + } + } + } + } + + private void SetupDiscoveryClients() { var tempServices = new ServiceCollection(); - tempServices.AddSingleton(loggerFactory); + tempServices.AddSingleton(_loggerFactory); tempServices.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); // force settings to make sure we don't register the app here @@ -70,7 +94,7 @@ private IDiscoveryClient[] SetupDiscoveryClients(ILoggerFactory loggerFactory) WireEurekaDiscoveryClient(tempServices); } - return GetDiscoveryClientsFromServiceCollection(tempServices); + _discoveryClients = GetDiscoveryClientsFromServiceCollection(tempServices); } [MethodImpl(MethodImplOptions.NoInlining)] @@ -105,23 +129,29 @@ private IDiscoveryClient[] GetDiscoveryClientsFromServiceCollection(ServiceColle return discoveryClients; } - internal async Task> GetConfigServerInstancesAsync(CancellationToken cancellationToken) + internal async Task> GetConfigServerInstancesAsync(ConfigServerClientOptions optionsSnapshot, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(optionsSnapshot); + + EnsureInitialized(); + int attempts = 0; - int backOff = _options.Retry.InitialInterval; + int backOff = optionsSnapshot.Retry.InitialInterval; List instances = []; do { - LogLocatingConfigServer(_options.Discovery.ServiceId); + LogLocatingConfigServer(optionsSnapshot.Discovery.ServiceId); - if (_options.Discovery.ServiceId != null) + if (optionsSnapshot.Discovery.ServiceId != null) { - foreach (IDiscoveryClient discoveryClient in DiscoveryClients) + foreach (IDiscoveryClient discoveryClient in _discoveryClients ?? []) { try { - IList serviceInstances = await discoveryClient.GetInstancesAsync(_options.Discovery.ServiceId, cancellationToken); + IList serviceInstances = + await discoveryClient.GetInstancesAsync(optionsSnapshot.Discovery.ServiceId, cancellationToken); + instances.AddRange(serviceInstances); } catch (Exception exception) when (!exception.IsCancellation()) @@ -131,18 +161,18 @@ internal async Task> GetConfigServerInstancesAsync } } - if (!_options.Retry.Enabled || instances.Count > 0) + if (!optionsSnapshot.Retry.Enabled || instances.Count > 0) { break; } attempts++; - if (attempts <= _options.Retry.MaxAttempts) + if (attempts <= optionsSnapshot.Retry.MaxAttempts) { - Thread.CurrentThread.Join(backOff); - int nextBackOff = (int)(backOff * _options.Retry.Multiplier); - backOff = Math.Min(nextBackOff, _options.Retry.MaxInterval); + await Task.Delay(backOff, cancellationToken); + int nextBackOff = (int)(backOff * optionsSnapshot.Retry.Multiplier); + backOff = Math.Min(nextBackOff, optionsSnapshot.Retry.MaxInterval); } else { @@ -169,7 +199,7 @@ internal async Task ShutdownAsync(CancellationToken cancellationToken) { if (_temporaryServiceProviderForDiscoveryClients != null) { - foreach (IDiscoveryClient discoveryClient in DiscoveryClients) + foreach (IDiscoveryClient discoveryClient in _discoveryClients ?? []) { await discoveryClient.ShutdownAsync(cancellationToken); } diff --git a/src/Configuration/src/ConfigServer/ConfigServerHealthContributor.cs b/src/Configuration/src/ConfigServer/ConfigServerHealthContributor.cs index 34b6b2f0c2..5517cb3b11 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerHealthContributor.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerHealthContributor.cs @@ -46,12 +46,14 @@ public ConfigServerHealthContributor(IConfiguration configuration, TimeProvider return health; } - if (!IsEnabled()) + ConfigServerClientOptions optionsSnapshot = Provider.ClientOptions; + + if (!optionsSnapshot.Health.Enabled) { return null; } - IList? sources = await GetPropertySourcesAsync(Provider, cancellationToken); + IList? sources = await GetPropertySourcesAsync(Provider, optionsSnapshot, cancellationToken); if (sources == null || sources.Count == 0) { @@ -74,45 +76,46 @@ internal void UpdateHealth(HealthCheckResult health, IList sourc foreach (PropertySource source in sources) { - LogReturningPropertySource(source.Name); names.Add(source.Name); } + LogReturningPropertySources(string.Join(", ", names)); health.Details.Add("propertySources", names); } - internal async Task?> GetPropertySourcesAsync(ConfigServerConfigurationProvider provider, CancellationToken cancellationToken) + internal async Task?> GetPropertySourcesAsync(ConfigServerConfigurationProvider provider, ConfigServerClientOptions optionsSnapshot, + CancellationToken cancellationToken) { long currentTime = _timeProvider.GetUtcNow().ToUnixTimeMilliseconds(); - if (IsCacheStale(currentTime)) + if (IsCacheStale(currentTime, optionsSnapshot)) { LastAccess = currentTime; LogCacheStale(); - Cached = await provider.LoadInternalAsync(false, cancellationToken); + + try + { + Cached = await provider.LoadInternalAsync(optionsSnapshot, false, cancellationToken); + } + catch (ConfigServerException exception) + { + LogFetchFailed(exception); + Cached = null; + return null; + } } return Cached?.PropertySources; } - internal bool IsCacheStale(long accessTime) + internal bool IsCacheStale(long accessTime, ConfigServerClientOptions optionsSnapshot) { if (Cached == null) { return true; } - return accessTime - LastAccess >= GetTimeToLive(); - } - - internal bool IsEnabled() - { - return Provider is { ClientOptions.Health.Enabled: true }; - } - - internal long GetTimeToLive() - { - return Provider != null ? Provider.ClientOptions.Health.TimeToLive : long.MaxValue; + return accessTime - LastAccess >= optionsSnapshot.Health.TimeToLive; } [LoggerMessage(Level = LogLevel.Warning, Message = "No Config Server provider found, health check disabled.")] @@ -121,14 +124,17 @@ internal long GetTimeToLive() [LoggerMessage(Level = LogLevel.Debug, Message = "No Config Server provider found.")] private partial void LogNoProviderFound(); + [LoggerMessage(Level = LogLevel.Debug, Message = "Failed fetching remote configuration from server(s).")] + private partial void LogFetchFailed(Exception exception); + [LoggerMessage(Level = LogLevel.Debug, Message = "No property sources found.")] private partial void LogNoPropertySourcesFound(); [LoggerMessage(Level = LogLevel.Debug, Message = "Config Server health check returning UP.")] private partial void LogHealthCheckReturningUp(); - [LoggerMessage(Level = LogLevel.Debug, Message = "Returning property source {PropertySource}.")] - private partial void LogReturningPropertySource(string? propertySource); + [LoggerMessage(Level = LogLevel.Debug, Message = "Returning property sources: {PropertySources}.")] + private partial void LogReturningPropertySources(string propertySources); [LoggerMessage(Level = LogLevel.Debug, Message = "Cache stale, fetching config server health.")] private partial void LogCacheStale(); diff --git a/src/Configuration/src/ConfigServer/ConfigServerHostBuilderExtensions.cs b/src/Configuration/src/ConfigServer/ConfigServerHostBuilderExtensions.cs index bf3e0d0337..ab3dde3d9a 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerHostBuilderExtensions.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerHostBuilderExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -23,7 +24,7 @@ public static class ConfigServerHostBuilderExtensions /// public static IWebHostBuilder AddConfigServer(this IWebHostBuilder builder) { - return AddConfigServer(builder, NullLoggerFactory.Instance); + return AddConfigServer(builder, null, NullLoggerFactory.Instance); } /// @@ -39,12 +40,49 @@ public static IWebHostBuilder AddConfigServer(this IWebHostBuilder builder) /// The incoming so that additional calls can be chained. /// public static IWebHostBuilder AddConfigServer(this IWebHostBuilder builder, ILoggerFactory loggerFactory) + { + return AddConfigServer(builder, null, loggerFactory); + } + + /// + /// Adds Config Server and Cloud Foundry as application configuration sources. Adds Config Server health check contributor to the service container. + /// + /// + /// The to configure. + /// + /// + /// An optional delegate that further configures options from code, after settings from have been applied. + /// + /// + /// The incoming so that additional calls can be chained. + /// + public static IWebHostBuilder AddConfigServer(this IWebHostBuilder builder, Action? configure) + { + return AddConfigServer(builder, configure, NullLoggerFactory.Instance); + } + + /// + /// Adds Config Server and Cloud Foundry as application configuration sources. Adds Config Server health check contributor to the service container. + /// + /// + /// The to configure. + /// + /// + /// An optional delegate that further configures options from code, after settings from have been applied. + /// + /// + /// Used for internal logging. Pass to disable logging. + /// + /// + /// The incoming so that additional calls can be chained. + /// + public static IWebHostBuilder AddConfigServer(this IWebHostBuilder builder, Action? configure, ILoggerFactory loggerFactory) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(loggerFactory); HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); - wrapper.AddConfigServer(loggerFactory); + wrapper.AddConfigServer(configure, loggerFactory); return builder; } @@ -60,7 +98,7 @@ public static IWebHostBuilder AddConfigServer(this IWebHostBuilder builder, ILog /// public static IHostBuilder AddConfigServer(this IHostBuilder builder) { - return AddConfigServer(builder, NullLoggerFactory.Instance); + return AddConfigServer(builder, null, NullLoggerFactory.Instance); } /// @@ -76,12 +114,49 @@ public static IHostBuilder AddConfigServer(this IHostBuilder builder) /// The incoming so that additional calls can be chained. /// public static IHostBuilder AddConfigServer(this IHostBuilder builder, ILoggerFactory loggerFactory) + { + return AddConfigServer(builder, null, loggerFactory); + } + + /// + /// Adds Config Server and Cloud Foundry as application configuration sources. Adds Config Server health check contributor to the service container. + /// + /// + /// The to configure. + /// + /// + /// An optional delegate that further configures options from code, after settings from have been applied. + /// + /// + /// The incoming so that additional calls can be chained. + /// + public static IHostBuilder AddConfigServer(this IHostBuilder builder, Action? configure) + { + return AddConfigServer(builder, configure, NullLoggerFactory.Instance); + } + + /// + /// Adds Config Server and Cloud Foundry as application configuration sources. Adds Config Server health check contributor to the service container. + /// + /// + /// The to configure. + /// + /// + /// An optional delegate that further configures options from code, after settings from have been applied. + /// + /// + /// Used for internal logging. Pass to disable logging. + /// + /// + /// The incoming so that additional calls can be chained. + /// + public static IHostBuilder AddConfigServer(this IHostBuilder builder, Action? configure, ILoggerFactory loggerFactory) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(loggerFactory); HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); - wrapper.AddConfigServer(loggerFactory); + wrapper.AddConfigServer(configure, loggerFactory); return builder; } @@ -98,7 +173,7 @@ public static IHostBuilder AddConfigServer(this IHostBuilder builder, ILoggerFac /// public static IHostApplicationBuilder AddConfigServer(this IHostApplicationBuilder builder) { - return AddConfigServer(builder, NullLoggerFactory.Instance); + return AddConfigServer(builder, null, NullLoggerFactory.Instance); } /// @@ -115,12 +190,52 @@ public static IHostApplicationBuilder AddConfigServer(this IHostApplicationBuild /// The incoming so that additional calls can be chained. /// public static IHostApplicationBuilder AddConfigServer(this IHostApplicationBuilder builder, ILoggerFactory loggerFactory) + { + return AddConfigServer(builder, null, loggerFactory); + } + + /// + /// Adds Config Server and Cloud Foundry as application configuration sources. Also adds Config Server health check contributor and related services to + /// the service container. + /// + /// + /// The to configure. + /// + /// + /// An optional delegate that further configures options from code, after settings from have been applied. + /// + /// + /// The incoming so that additional calls can be chained. + /// + public static IHostApplicationBuilder AddConfigServer(this IHostApplicationBuilder builder, Action? configure) + { + return AddConfigServer(builder, configure, NullLoggerFactory.Instance); + } + + /// + /// Adds Config Server and Cloud Foundry as application configuration sources. Also adds Config Server health check contributor and related services to + /// the service container. + /// + /// + /// The to configure. + /// + /// + /// An optional delegate that further configures options from code, after settings from have been applied. + /// + /// + /// Used for internal logging. Pass to disable logging. + /// + /// + /// The incoming so that additional calls can be chained. + /// + public static IHostApplicationBuilder AddConfigServer(this IHostApplicationBuilder builder, Action? configure, + ILoggerFactory loggerFactory) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(loggerFactory); HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); - wrapper.AddConfigServer(loggerFactory); + wrapper.AddConfigServer(configure, loggerFactory); return builder; } diff --git a/src/Configuration/src/ConfigServer/ConfigServerServiceCollectionExtensions.cs b/src/Configuration/src/ConfigServer/ConfigServerServiceCollectionExtensions.cs index 624aa54ee4..8c891be1cd 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerServiceCollectionExtensions.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerServiceCollectionExtensions.cs @@ -25,11 +25,40 @@ public static class ConfigServerServiceCollectionExtensions /// The incoming so that additional calls can be chained. /// public static IServiceCollection ConfigureConfigServerClientOptions(this IServiceCollection services) + { + return ConfigureConfigServerClientOptions(services, null); + } + + /// + /// Adds for use with the options pattern. + /// + /// + /// The to add services to. + /// + /// + /// An optional delegate that further configures options from code, after settings from have been applied. + /// + /// + /// The incoming so that additional calls can be chained. + /// + public static IServiceCollection ConfigureConfigServerClientOptions(this IServiceCollection services, Action? configure) { ArgumentNullException.ThrowIfNull(services); services.AddOptions(); - services.TryAddEnumerable(ServiceDescriptor.Singleton, ConfigureConfigServerClientOptions>()); + + services.AddSingleton(serviceProvider => + { + var configuration = serviceProvider.GetRequiredService(); + return new ConfigureConfigServerClientOptions(configuration, configure); + }); + + services.TryAddEnumerable( + ServiceDescriptor.Singleton, ConfigureConfigServerClientOptions>(serviceProvider => + serviceProvider.GetRequiredService())); + + services.TryAddEnumerable(ServiceDescriptor + .Singleton, ConfigurationChangeTokenSource>()); return services; } @@ -64,10 +93,28 @@ public static IServiceCollection AddConfigServerHealthContributor(this IServiceC /// The incoming so that additional calls can be chained. /// public static IServiceCollection AddConfigServerServices(this IServiceCollection services) + { + return AddConfigServerServices(services, null); + } + + /// + /// Configures , hosted service and health contributor, and ensures is + /// available. + /// + /// + /// The to add services to. + /// + /// + /// An optional delegate that further configures options from code, after settings from have been applied. + /// + /// + /// The incoming so that additional calls can be chained. + /// + public static IServiceCollection AddConfigServerServices(this IServiceCollection services, Action? configure) { ArgumentNullException.ThrowIfNull(services); - services.ConfigureConfigServerClientOptions(); + services.ConfigureConfigServerClientOptions(configure); services.TryAddSingleton(serviceProvider => (IConfigurationRoot)serviceProvider.GetRequiredService()); services.AddHostedService(); services.AddConfigServerHealthContributor(); diff --git a/src/Configuration/src/ConfigServer/ConfigurationSchema.json b/src/Configuration/src/ConfigServer/ConfigurationSchema.json index 8f9e677fc0..0bfa06a7a1 100644 --- a/src/Configuration/src/ConfigServer/ConfigurationSchema.json +++ b/src/Configuration/src/ConfigServer/ConfigurationSchema.json @@ -173,7 +173,7 @@ }, "TokenRenewRate": { "type": "integer", - "description": "Gets or sets the vault token renew rate (in milliseconds). Default value: 60_000 (1 minute)." + "description": "Gets or sets the Vault token renew rate (in milliseconds). Default value: 60_000 (1 minute)." }, "TokenTtl": { "type": "integer", diff --git a/src/Configuration/src/ConfigServer/ConfigureConfigServerClientOptions.cs b/src/Configuration/src/ConfigServer/ConfigureConfigServerClientOptions.cs index 4a7ae64ffe..d1855d5d15 100644 --- a/src/Configuration/src/ConfigServer/ConfigureConfigServerClientOptions.cs +++ b/src/Configuration/src/ConfigServer/ConfigureConfigServerClientOptions.cs @@ -5,23 +5,26 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using Steeltoe.Common; +using Steeltoe.Common.Certificates; using Steeltoe.Configuration.CloudFoundry; namespace Steeltoe.Configuration.ConfigServer; internal sealed class ConfigureConfigServerClientOptions : IConfigureOptions { - private const string VcapServicesConfigServerCredentialsPrefix = "vcap:services:p-config-server:0:credentials"; - private const string VcapServicesConfigServer30CredentialsPrefix = "vcap:services:p.config-server:0:credentials"; + private const string VcapServicesConfigServerVersion2CredentialsPrefix = "vcap:services:p-config-server:0:credentials"; + private const string VcapServicesConfigServerVersion3CredentialsPrefix = "vcap:services:p.config-server:0:credentials"; private const string VcapServicesConfigServerCredentialsAltPrefix = "vcap:services:config-server:0:credentials"; private readonly IConfiguration _configuration; + private readonly Action? _configure; - public ConfigureConfigServerClientOptions(IConfiguration configuration) + public ConfigureConfigServerClientOptions(IConfiguration configuration, Action? configure) { ArgumentNullException.ThrowIfNull(configuration); _configuration = configuration; + _configure = configure; } public void Configure(ConfigServerClientOptions options) @@ -29,17 +32,21 @@ public void Configure(ConfigServerClientOptions options) ArgumentNullException.ThrowIfNull(options); _configuration.GetSection(ConfigServerClientOptions.ConfigurationPrefix).Bind(options); + _configure?.Invoke(options); + OverrideFromVcapServicesCredentials(options); + ConfigureClientCertificate(options); options.Name ??= GetApplicationName(); + options.Environment ??= "Production"; } private void OverrideFromVcapServicesCredentials(ConfigServerClientOptions options) { VcapServicesConfigServerCredentialsOptions credentialsOptions = new(); _configuration.GetSection(VcapServicesConfigServerCredentialsAltPrefix).Bind(credentialsOptions); - _configuration.GetSection(VcapServicesConfigServer30CredentialsPrefix).Bind(credentialsOptions); - _configuration.GetSection(VcapServicesConfigServerCredentialsPrefix).Bind(credentialsOptions); + _configuration.GetSection(VcapServicesConfigServerVersion2CredentialsPrefix).Bind(credentialsOptions); + _configuration.GetSection(VcapServicesConfigServerVersion3CredentialsPrefix).Bind(credentialsOptions); options.Uri = credentialsOptions.Uri ?? options.Uri; options.ClientId = credentialsOptions.ClientId ?? options.ClientId; @@ -47,6 +54,26 @@ private void OverrideFromVcapServicesCredentials(ConfigServerClientOptions optio options.AccessTokenUri = credentialsOptions.AccessTokenUri ?? options.AccessTokenUri; } + private void ConfigureClientCertificate(ConfigServerClientOptions options) + { + if (options.ClientCertificate.Certificate != null) + { + return; + } + + var certificateConfigurer = new ConfigureCertificateOptions(_configuration); + + var certificateOptions = new CertificateOptions(); + certificateConfigurer.Configure("ConfigServer", certificateOptions); + + if (certificateOptions.Certificate == null) + { + certificateConfigurer.Configure(certificateOptions); + } + + options.ClientCertificate = certificateOptions.Clone(); + } + private string? GetApplicationName() { var vcapOptions = new CloudFoundryApplicationOptions(); diff --git a/src/Configuration/src/ConfigServer/HostBuilderWrapperExtensions.cs b/src/Configuration/src/ConfigServer/HostBuilderWrapperExtensions.cs index 4709af0345..7c1c699ae2 100644 --- a/src/Configuration/src/ConfigServer/HostBuilderWrapperExtensions.cs +++ b/src/Configuration/src/ConfigServer/HostBuilderWrapperExtensions.cs @@ -10,36 +10,37 @@ namespace Steeltoe.Configuration.ConfigServer; internal static class HostBuilderWrapperExtensions { - public static HostBuilderWrapper AddConfigServer(this HostBuilderWrapper wrapper, ILoggerFactory loggerFactory) + public static HostBuilderWrapper AddConfigServer(this HostBuilderWrapper wrapper, Action? configure, + ILoggerFactory loggerFactory) { ArgumentNullException.ThrowIfNull(wrapper); ArgumentNullException.ThrowIfNull(loggerFactory); wrapper.ConfigureAppConfiguration((context, builder) => { - ConfigServerClientOptions options = CreateOptions(context.HostEnvironment); - builder.AddConfigServer(options, loggerFactory); + Action configureOptions = CreateOptionsConfigurer(configure, context.HostEnvironment); + builder.AddConfigServer(configureOptions, loggerFactory); }); - wrapper.ConfigureServices(services => services.AddConfigServerServices()); + wrapper.ConfigureServices((context, services) => + { + Action configureOptions = CreateOptionsConfigurer(configure, context.HostEnvironment); + services.AddConfigServerServices(configureOptions); + }); return wrapper; } - private static ConfigServerClientOptions CreateOptions(IHostEnvironment hostEnvironment) + private static Action CreateOptionsConfigurer(Action? configure, IHostEnvironment hostEnvironment) { - var options = new ConfigServerClientOptions(); - - if (!string.IsNullOrEmpty(hostEnvironment.EnvironmentName) && hostEnvironment.EnvironmentName != "Production") + return options => { - // Only take IHostEnvironment.EnvironmentName when it was explicitly set (it defaults to "Production"). - // In the default case, we want the various other ways of setting the environment name to kick in. - options.Environment = hostEnvironment.EnvironmentName; - } - - // Intentionally NOT taking hostEnvironment.ApplicationName here, because that would disable the various other ways of setting the application name. - // Ultimately, its value ends up in configuration key "applicationName", whose value is used if nothing else is configured. + configure?.Invoke(options); - return options; + if (!string.IsNullOrEmpty(hostEnvironment.EnvironmentName)) + { + options.Environment ??= hostEnvironment.EnvironmentName; + } + }; } } diff --git a/src/Configuration/src/ConfigServer/PublicAPI.Unshipped.txt b/src/Configuration/src/ConfigServer/PublicAPI.Unshipped.txt index 7dc5c58110..ee4a75b906 100644 --- a/src/Configuration/src/ConfigServer/PublicAPI.Unshipped.txt +++ b/src/Configuration/src/ConfigServer/PublicAPI.Unshipped.txt @@ -1 +1,12 @@ #nullable enable +static Steeltoe.Configuration.ConfigServer.ConfigServerConfigurationBuilderExtensions.AddConfigServer(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder, Steeltoe.Configuration.ConfigServer.ConfigServerClientOptions! options) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! +static Steeltoe.Configuration.ConfigServer.ConfigServerConfigurationBuilderExtensions.AddConfigServer(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder, System.Action? configure) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! +static Steeltoe.Configuration.ConfigServer.ConfigServerConfigurationBuilderExtensions.AddConfigServer(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder, System.Action? configure, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! +static Steeltoe.Configuration.ConfigServer.ConfigServerHostBuilderExtensions.AddConfigServer(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, System.Action? configure) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder! +static Steeltoe.Configuration.ConfigServer.ConfigServerHostBuilderExtensions.AddConfigServer(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, System.Action? configure, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder! +static Steeltoe.Configuration.ConfigServer.ConfigServerHostBuilderExtensions.AddConfigServer(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, System.Action? configure) -> Microsoft.Extensions.Hosting.IHostApplicationBuilder! +static Steeltoe.Configuration.ConfigServer.ConfigServerHostBuilderExtensions.AddConfigServer(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, System.Action? configure, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> Microsoft.Extensions.Hosting.IHostApplicationBuilder! +static Steeltoe.Configuration.ConfigServer.ConfigServerHostBuilderExtensions.AddConfigServer(this Microsoft.Extensions.Hosting.IHostBuilder! builder, System.Action? configure) -> Microsoft.Extensions.Hosting.IHostBuilder! +static Steeltoe.Configuration.ConfigServer.ConfigServerHostBuilderExtensions.AddConfigServer(this Microsoft.Extensions.Hosting.IHostBuilder! builder, System.Action? configure, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> Microsoft.Extensions.Hosting.IHostBuilder! +static Steeltoe.Configuration.ConfigServer.ConfigServerServiceCollectionExtensions.AddConfigServerServices(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action? configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Steeltoe.Configuration.ConfigServer.ConfigServerServiceCollectionExtensions.ConfigureConfigServerClientOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action? configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! diff --git a/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerClientOptionsTest.cs b/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerClientOptionsTest.cs new file mode 100644 index 0000000000..5ca2eab3c0 --- /dev/null +++ b/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerClientOptionsTest.cs @@ -0,0 +1,430 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using RichardSzalay.MockHttp; +using Steeltoe.Common.TestResources; + +namespace Steeltoe.Configuration.ConfigServer.Discovery.Test; + +public sealed class ConfigServerClientOptionsTest +{ + [Fact] + public void Config_Server_URI_is_resolved_from_discovery_and_survives_changes_in_IConfiguration() + { + const string configServerResponseJson = """ + { + "name": "example-app-name", + "profiles": [ + "example-profile" + ], + "label": "example-label", + "version": "1", + "propertySources": [ + { + "name": "example-source", + "source": { + "example-server-key": "example-server-value" + } + } + ] + } + """; + + var fileProvider = new MemoryFileProvider(); + + fileProvider.IncludeAppSettingsJsonFile(""" + { + "spring": { + "cloud": { + "config": { + "discovery": { + "enabled": true + }, + "uri": "http://overridden-by-discovery", + "name": "example-app-name", + "env": "example-profile", + "timeout": 30000, + "label": "example-label" + } + } + }, + "discovery": { + "services": [ + { + "serviceId": "configserver", + "host": "discovered-server.com", + "port": 9999, + "isSecure": true, + "metadata": { + "user": "example-user", + "password": "example-password", + "configPath": "internal" + } + } + ] + }, + "eureka": { + "client": { + "enabled": false + } + }, + "consul": { + "discovery": { + "enabled": false + } + } + } + """); + + using var handler = new DelegateToMockHttpClientHandler(); + + handler.Mock.Expect(HttpMethod.Get, "https://discovered-server.com:9999/internal/example-app-name/example-profile/example-label") + .Respond("application/json", configServerResponseJson); + + Action configureOptions = options => options.ValidateCertificates = false; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + // ReSharper disable once AccessToDisposedClosure + configurationBuilder.AddConfigServer(new ConfigServerClientOptions(), configureOptions, () => handler, NullLoggerFactory.Instance); + IConfigurationRoot configuration = configurationBuilder.Build(); + + handler.Mock.VerifyNoOutstandingExpectation(); + + ConfigServerConfigurationProvider provider = configuration.Providers.OfType().Single(); + + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(configuration); + services.ConfigureConfigServerClientOptions(configureOptions); + + using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + var optionsMonitor = serviceProvider.GetRequiredService>(); + + provider.ClientOptions.Uri.Should().Be("http://overridden-by-discovery"); + provider.ClientOptions.Username.Should().BeNull(); + provider.ClientOptions.Password.Should().BeNull(); + provider.ClientOptions.Name.Should().Be("example-app-name"); + provider.ClientOptions.Environment.Should().Be("example-profile"); + provider.ClientOptions.Timeout.Should().Be(30_000); + provider.ClientOptions.Label.Should().Be("example-label"); + provider.ClientOptions.ValidateCertificates.Should().BeFalse(); + + optionsMonitor.CurrentValue.Uri.Should().Be("https://discovered-server.com:9999/internal"); + optionsMonitor.CurrentValue.Username.Should().Be("example-user"); + optionsMonitor.CurrentValue.Password.Should().Be("example-password"); + optionsMonitor.CurrentValue.Name.Should().Be(provider.ClientOptions.Name); + optionsMonitor.CurrentValue.Environment.Should().Be(provider.ClientOptions.Environment); + optionsMonitor.CurrentValue.Timeout.Should().Be(provider.ClientOptions.Timeout); + optionsMonitor.CurrentValue.Label.Should().Be(provider.ClientOptions.Label); + optionsMonitor.CurrentValue.ValidateCertificates.Should().BeFalse(); + + configuration["example-server-key"].Should().Be("example-server-value"); + + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "spring": { + "cloud": { + "config": { + "discovery": { + "enabled": true + }, + "uri": "http://overridden-by-discovery", + "name": "alternate-name-1", + "env": "example-profile", + "timeout": 15000, + "label": "example-label" + } + } + }, + "discovery": { + "services": [ + { + "serviceId": "configserver", + "host": "ignored-other-discovered-server.com", + "port": 3333, + "isSecure": true, + "metadata": { + "user": "ignored-other-example-user", + "password": "ignored-other-example-password", + "configPath": "ignored-other-internal" + } + } + ] + }, + "eureka": { + "client": { + "enabled": false + } + }, + "consul": { + "discovery": { + "enabled": false + } + } + } + """); + + fileProvider.NotifyChanged(); + + provider.ClientOptions.Uri.Should().Be("http://overridden-by-discovery"); + provider.ClientOptions.Username.Should().BeNull(); + provider.ClientOptions.Password.Should().BeNull(); + provider.ClientOptions.Name.Should().Be("alternate-name-1"); + provider.ClientOptions.Environment.Should().Be("example-profile"); + provider.ClientOptions.Timeout.Should().Be(15_000); + provider.ClientOptions.Label.Should().Be("example-label"); + provider.ClientOptions.ValidateCertificates.Should().BeFalse(); + + // Discovery changes don't propagate until the provider reloads. + optionsMonitor.CurrentValue.Uri.Should().Be("https://discovered-server.com:9999/internal"); + optionsMonitor.CurrentValue.Username.Should().Be("example-user"); + optionsMonitor.CurrentValue.Password.Should().Be("example-password"); + optionsMonitor.CurrentValue.Name.Should().Be(provider.ClientOptions.Name); + optionsMonitor.CurrentValue.Environment.Should().Be(provider.ClientOptions.Environment); + optionsMonitor.CurrentValue.Timeout.Should().Be(provider.ClientOptions.Timeout); + optionsMonitor.CurrentValue.Label.Should().Be(provider.ClientOptions.Label); + optionsMonitor.CurrentValue.ValidateCertificates.Should().BeFalse(); + + configuration["example-server-key"].Should().Be("example-server-value"); + + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "spring": { + "cloud": { + "config": { + "discovery": { + "enabled": false + }, + "uri": "https://explicit-server:7777", + "name": "alternate-name-2", + "env": "example-profile", + "timeout": 10000 + } + } + } + } + """); + + fileProvider.NotifyChanged(); + + provider.ClientOptions.Uri.Should().Be("https://explicit-server:7777"); + provider.ClientOptions.Name.Should().Be("alternate-name-2"); + provider.ClientOptions.Environment.Should().Be("example-profile"); + provider.ClientOptions.Timeout.Should().Be(10_000); + provider.ClientOptions.Label.Should().BeNull(); + provider.ClientOptions.ValidateCertificates.Should().BeFalse(); + + // Discovery changes don't propagate until the provider reloads. + optionsMonitor.CurrentValue.Uri.Should().Be("https://discovered-server.com:9999/internal"); + optionsMonitor.CurrentValue.Name.Should().Be(provider.ClientOptions.Name); + optionsMonitor.CurrentValue.Environment.Should().Be(provider.ClientOptions.Environment); + optionsMonitor.CurrentValue.Timeout.Should().Be(provider.ClientOptions.Timeout); + optionsMonitor.CurrentValue.Label.Should().Be(provider.ClientOptions.Label); + optionsMonitor.CurrentValue.ValidateCertificates.Should().BeFalse(); + + configuration["example-server-key"].Should().Be("example-server-value"); + } + + [Fact] + public void Updates_discovered_Config_Server_URI_on_provider_reload() + { + const string configServerResponseJson = """ + { + "name": "example-app-name", + "profiles": [ + "example-profile" + ], + "label": "example-label", + "version": "1", + "propertySources": [ + { + "name": "example-source", + "source": { + "example-server-key": "example-server-value" + } + } + ] + } + """; + + var fileProvider = new MemoryFileProvider(); + + fileProvider.IncludeAppSettingsJsonFile(""" + { + "spring": { + "cloud": { + "config": { + "discovery": { + "enabled": true + }, + "uri": "http://overridden-by-discovery", + "name": "example-app-name", + "env": "example-profile", + "label": "example-label" + } + } + }, + "discovery": { + "services": [ + { + "serviceId": "configserver", + "host": "discovered-server.com", + "port": 9999, + "isSecure": true, + "metadata": { + "user": "example-user", + "password": "example-password", + "configPath": "internal" + } + } + ] + }, + "eureka": { + "client": { + "enabled": false + } + }, + "consul": { + "discovery": { + "enabled": false + } + } + } + """); + + using var handler = new DelegateToMockHttpClientHandler(); + + handler.Mock.Expect(HttpMethod.Get, "https://discovered-server.com:9999/internal/example-app-name/example-profile/example-label") + .Respond("application/json", configServerResponseJson); + + Action configureOptions = options => options.ValidateCertificates = false; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + // ReSharper disable once AccessToDisposedClosure + configurationBuilder.AddConfigServer(new ConfigServerClientOptions(), configureOptions, () => handler, NullLoggerFactory.Instance); + IConfigurationRoot configuration = configurationBuilder.Build(); + + handler.Mock.VerifyNoOutstandingExpectation(); + handler.Mock.Clear(); + + ConfigServerConfigurationProvider provider = configuration.Providers.OfType().Single(); + + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(configuration); + services.ConfigureConfigServerClientOptions(configureOptions); + + using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + var optionsMonitor = serviceProvider.GetRequiredService>(); + + provider.ClientOptions.Uri.Should().Be("http://overridden-by-discovery"); + provider.ClientOptions.Username.Should().BeNull(); + provider.ClientOptions.Password.Should().BeNull(); + provider.ClientOptions.Name.Should().Be("example-app-name"); + provider.ClientOptions.Environment.Should().Be("example-profile"); + provider.ClientOptions.Label.Should().Be("example-label"); + provider.ClientOptions.ValidateCertificates.Should().BeFalse(); + + optionsMonitor.CurrentValue.Uri.Should().Be("https://discovered-server.com:9999/internal"); + optionsMonitor.CurrentValue.Username.Should().Be("example-user"); + optionsMonitor.CurrentValue.Password.Should().Be("example-password"); + optionsMonitor.CurrentValue.Name.Should().Be(provider.ClientOptions.Name); + optionsMonitor.CurrentValue.Environment.Should().Be(provider.ClientOptions.Environment); + optionsMonitor.CurrentValue.Label.Should().Be(provider.ClientOptions.Label); + optionsMonitor.CurrentValue.ValidateCertificates.Should().BeFalse(); + + configuration["example-server-key"].Should().Be("example-server-value"); + + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "spring": { + "cloud": { + "config": { + "discovery": { + "enabled": true + }, + "uri": "http://overridden-again-by-discovery", + "name": "alternate-name", + "env": "alternate-profile", + "label": "alternate-label" + } + } + }, + "discovery": { + "services": [ + { + "serviceId": "configserver", + "host": "alternate-discovered-server.com", + "port": 7777, + "isSecure": true, + "metadata": { + "user": "alternate-user", + "password": "alternate-password", + "configPath": "internal" + } + } + ] + }, + "eureka": { + "client": { + "enabled": false + } + }, + "consul": { + "discovery": { + "enabled": false + } + } + } + """); + + fileProvider.NotifyChanged(); + + provider.ClientOptions.Uri.Should().Be("http://overridden-again-by-discovery"); + provider.ClientOptions.Username.Should().BeNull(); + provider.ClientOptions.Password.Should().BeNull(); + provider.ClientOptions.Name.Should().Be("alternate-name"); + provider.ClientOptions.Environment.Should().Be("alternate-profile"); + provider.ClientOptions.Label.Should().Be("alternate-label"); + provider.ClientOptions.ValidateCertificates.Should().BeFalse(); + + optionsMonitor.CurrentValue.Uri.Should().Be("https://discovered-server.com:9999/internal"); + optionsMonitor.CurrentValue.Username.Should().Be("example-user"); + optionsMonitor.CurrentValue.Password.Should().Be("example-password"); + optionsMonitor.CurrentValue.Name.Should().Be(provider.ClientOptions.Name); + optionsMonitor.CurrentValue.Environment.Should().Be(provider.ClientOptions.Environment); + optionsMonitor.CurrentValue.Label.Should().Be(provider.ClientOptions.Label); + optionsMonitor.CurrentValue.ValidateCertificates.Should().BeFalse(); + + configuration["example-server-key"].Should().Be("example-server-value"); + + handler.Mock.Expect(HttpMethod.Get, "https://alternate-discovered-server.com:7777/internal/alternate-name/alternate-profile/alternate-label") + .Respond("application/json", configServerResponseJson); + + provider.Load(); + handler.Mock.VerifyNoOutstandingExpectation(); + + provider.ClientOptions.Uri.Should().Be("http://overridden-again-by-discovery"); + provider.ClientOptions.Username.Should().BeNull(); + provider.ClientOptions.Password.Should().BeNull(); + provider.ClientOptions.Name.Should().Be("alternate-name"); + provider.ClientOptions.Environment.Should().Be("alternate-profile"); + provider.ClientOptions.Label.Should().Be("alternate-label"); + provider.ClientOptions.ValidateCertificates.Should().BeFalse(); + + optionsMonitor.CurrentValue.Uri.Should().Be("https://alternate-discovered-server.com:7777/internal"); + optionsMonitor.CurrentValue.Username.Should().Be("alternate-user"); + optionsMonitor.CurrentValue.Password.Should().Be("alternate-password"); + optionsMonitor.CurrentValue.Name.Should().Be(provider.ClientOptions.Name); + optionsMonitor.CurrentValue.Environment.Should().Be(provider.ClientOptions.Environment); + optionsMonitor.CurrentValue.Label.Should().Be(provider.ClientOptions.Label); + optionsMonitor.CurrentValue.ValidateCertificates.Should().BeFalse(); + + configuration["example-server-key"].Should().Be("example-server-value"); + } +} diff --git a/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerDiscoveryServiceTest.cs b/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerDiscoveryServiceTest.cs index 73770ae0a3..2b193cad38 100644 --- a/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerDiscoveryServiceTest.cs +++ b/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerDiscoveryServiceTest.cs @@ -15,12 +15,12 @@ namespace Steeltoe.Configuration.ConfigServer.Discovery.Test; public sealed class ConfigServerDiscoveryServiceTest { [Fact] - public void ConfigServerDiscoveryService_FindsDiscoveryClients() + public async Task ConfigServerDiscoveryService_FindsDiscoveryClients() { IConfiguration configuration = new ConfigurationBuilder().Add(FastTestConfigurations.ConfigServer | FastTestConfigurations.Discovery).Build(); - var options = new ConfigServerClientOptions(); - var service = new ConfigServerDiscoveryService(configuration, options, NullLoggerFactory.Instance); + var service = new ConfigServerDiscoveryService(configuration, NullLoggerFactory.Instance); + await service.GetConfigServerInstancesAsync(new ConfigServerClientOptions(), TestContext.Current.CancellationToken); service.DiscoveryClients.Should().HaveCount(3); service.DiscoveryClients.OfType().Should().ContainSingle(); @@ -41,8 +41,8 @@ public async Task InvokeGetInstances_ReturnsExpected() IConfigurationRoot configurationRoot = builder.Build(); var options = new ConfigServerClientOptions(); - var service = new ConfigServerDiscoveryService(configurationRoot, options, NullLoggerFactory.Instance); - IEnumerable result = await service.GetConfigServerInstancesAsync(TestContext.Current.CancellationToken); + var service = new ConfigServerDiscoveryService(configurationRoot, NullLoggerFactory.Instance); + IEnumerable result = await service.GetConfigServerInstancesAsync(options, TestContext.Current.CancellationToken); result.Should().BeEmpty(); } @@ -66,8 +66,8 @@ public async Task InvokeGetInstances_RetryEnabled_ReturnsExpected() Timeout = 10 }; - var service = new ConfigServerDiscoveryService(configurationRoot, options, NullLoggerFactory.Instance); - IEnumerable result = await service.GetConfigServerInstancesAsync(TestContext.Current.CancellationToken); + var service = new ConfigServerDiscoveryService(configurationRoot, NullLoggerFactory.Instance); + IEnumerable result = await service.GetConfigServerInstancesAsync(options, TestContext.Current.CancellationToken); result.Should().BeEmpty(); } @@ -92,8 +92,8 @@ public async Task GetConfigServerInstances_ReturnsExpected() Timeout = 10 }; - var service = new ConfigServerDiscoveryService(configurationRoot, options, NullLoggerFactory.Instance); - IEnumerable result = await service.GetConfigServerInstancesAsync(TestContext.Current.CancellationToken); + var service = new ConfigServerDiscoveryService(configurationRoot, NullLoggerFactory.Instance); + IEnumerable result = await service.GetConfigServerInstancesAsync(options, TestContext.Current.CancellationToken); result.Should().BeEmpty(); } @@ -103,7 +103,7 @@ public async Task RuntimeReplacementsCanBeProvided() IConfigurationRoot configurationRoot = new ConfigurationBuilder().Add(FastTestConfigurations.ConfigServer | FastTestConfigurations.Discovery).Build(); var testDiscoveryClient = new TestDiscoveryClient(); - var service = new ConfigServerDiscoveryService(configurationRoot, new ConfigServerClientOptions(), NullLoggerFactory.Instance); + var service = new ConfigServerDiscoveryService(configurationRoot, NullLoggerFactory.Instance); await service.ProvideRuntimeReplacementsAsync([testDiscoveryClient], TestContext.Current.CancellationToken); diff --git a/src/Configuration/test/ConfigServer.Integration.Test/ConfigServerConfigurationExtensionsIntegrationTest.cs b/src/Configuration/test/ConfigServer.Integration.Test/ConfigServerConfigurationExtensionsIntegrationTest.cs index f06a592b64..f8457950fe 100644 --- a/src/Configuration/test/ConfigServer.Integration.Test/ConfigServerConfigurationExtensionsIntegrationTest.cs +++ b/src/Configuration/test/ConfigServer.Integration.Test/ConfigServerConfigurationExtensionsIntegrationTest.cs @@ -6,7 +6,6 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Steeltoe.Common.TestResources; -using Steeltoe.Common.TestResources.IO; namespace Steeltoe.Configuration.ConfigServer.Integration.Test; @@ -41,15 +40,11 @@ public void SpringCloudConfigServer_ReturnsExpectedDefaultData() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); - var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(directory); - - configurationBuilder.AddJsonFile(fileName); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); configurationBuilder.AddConfigServer(); IConfigurationRoot root = configurationBuilder.Build(); @@ -85,21 +80,13 @@ public async Task SpringCloudConfigServer_ReturnsExpectedDefaultData_AsInjectedO } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); WebHostBuilder builder = TestWebHostBuilderFactory.Create(); builder.UseEnvironment("development"); builder.UseStartup(); - - builder.ConfigureAppConfiguration(configurationBuilder => - { - configurationBuilder.SetBasePath(directory); - configurationBuilder.AddJsonFile(fileName); - }); - + builder.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider)); builder.AddConfigServer(); using IWebHost host = builder.Build(); @@ -176,21 +163,13 @@ public async Task SpringCloudConfigServer_ConfiguredViaCloudfoundryEnv_ReturnsEx } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); WebHostBuilder builder = TestWebHostBuilderFactory.Create(); builder.UseEnvironment("development"); builder.UseStartup(); - - builder.ConfigureAppConfiguration(configurationBuilder => - { - configurationBuilder.SetBasePath(directory); - configurationBuilder.AddJsonFile(fileName); - }); - + builder.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider)); builder.AddConfigServer(); using IWebHost host = builder.Build(); @@ -231,15 +210,12 @@ public void SpringCloudConfigServer_DiscoveryFirst_ReturnsExpectedDefaultData() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.Add(FastTestConfigurations.Discovery); - configurationBuilder.SetBasePath(directory); - configurationBuilder.AddJsonFile(fileName); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); configurationBuilder.AddConfigServer(); IConfigurationRoot root = configurationBuilder.Build(); @@ -275,20 +251,12 @@ public async Task SpringCloudConfigServer_WithHealthEnabled_ReturnsHealth() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); WebHostBuilder builder = TestWebHostBuilderFactory.Create(); builder.UseStartup(); - - builder.ConfigureAppConfiguration(configurationBuilder => - { - configurationBuilder.SetBasePath(directory); - configurationBuilder.AddJsonFile(fileName); - }); - + builder.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider)); builder.AddConfigServer(); using IWebHost host = builder.Build(); diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerClientOptionsTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerClientOptionsTest.cs index 828b6dd46f..2c648dc0b3 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerClientOptionsTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerClientOptionsTest.cs @@ -3,11 +3,14 @@ // See the LICENSE file in the project root for more information. using System.Reflection; +using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; +using RichardSzalay.MockHttp; using Steeltoe.Common.TestResources; -using Steeltoe.Common.TestResources.IO; +using Steeltoe.Configuration.Placeholder; namespace Steeltoe.Configuration.ConfigServer.Test; @@ -18,7 +21,7 @@ public void DefaultConstructor_InitializedWithDefaults() { var options = new ConfigServerClientOptions(); - TestHelper.VerifyDefaults(options, null); + TestHelper.VerifyDefaults(options, null, null); } [Fact] @@ -36,7 +39,7 @@ public async Task ConfigureConfigServerClientOptions_WithDefaults() var optionsMonitor = serviceProvider.GetRequiredService>(); string? expectedAppName = Assembly.GetEntryAssembly()!.GetName().Name; - TestHelper.VerifyDefaults(optionsMonitor.CurrentValue, expectedAppName); + TestHelper.VerifyDefaults(optionsMonitor.CurrentValue, expectedAppName, "Production"); } [Fact] @@ -69,17 +72,15 @@ public async Task ConfigureConfigServerClientOptions_WithValues() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); + var builder = new ConfigurationBuilder(); - builder.SetBasePath(directory); - builder.AddJsonFile(fileName); + builder.AddInMemoryAppSettingsJsonFile(fileProvider); IConfiguration configuration = builder.Build(); services.AddSingleton(configuration); - services.ConfigureConfigServerClientOptions(); + services.ConfigureConfigServerClientOptions(options => options.Environment = "staging"); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); var service = serviceProvider.GetRequiredService>(); @@ -88,7 +89,7 @@ public async Task ConfigureConfigServerClientOptions_WithValues() options.Enabled.Should().BeTrue(); options.FailFast.Should().BeTrue(); options.Uri.Should().Be("http://localhost:8888"); - options.Environment.Should().Be("development"); + options.Environment.Should().Be("staging"); options.AccessTokenUri.Should().BeNull(); options.ClientId.Should().BeNull(); options.ClientSecret.Should().BeNull(); @@ -114,4 +115,339 @@ public async Task ConfigureConfigServerClientOptions_WithValues() options.Headers.Should().ContainKey("bar").WhoseValue.Should().Be("foo"); options.Headers.Should().ContainKey("foo").WhoseValue.Should().Be("bar"); } + + [Fact] + public void Clone_preserves_all_properties_and_produces_independent_nested_objects() + { + using var certificate = X509Certificate2.CreateFromPemFile("instance.crt", "instance.key"); + using var issuerCertificate = X509Certificate2.CreateFromPemFile("instance.crt", "instance.key"); + + var original = new ConfigServerClientOptions + { + ClientCertificate = + { + Certificate = certificate, + IssuerChain = + { + issuerCertificate + } + }, + Enabled = false, + FailFast = true, + Environment = "staging", + Label = "feature/x", + Name = "my-app", + Uri = "https://config.example.com:9999", + Username = "user", + Password = "pass", + Token = "vault-token-123", + Timeout = 42_000, + PollingInterval = TimeSpan.FromSeconds(15), + ValidateCertificates = false, + Retry = + { + Enabled = true, + InitialInterval = 500, + MaxInterval = 5000, + Multiplier = 2.0, + MaxAttempts = 10 + }, + Discovery = + { + Enabled = true, + ServiceId = "my-config-server" + }, + Health = + { + Enabled = false, + TimeToLive = 999 + }, + AccessTokenUri = "https://uaa.example.com/oauth/token", + ClientSecret = "secret", + ClientId = "client-id", + TokenTtl = 600_000, + TokenRenewRate = 120_000, + DisableTokenRenewal = true, + Headers = + { + ["X-Custom"] = "value" + } + }; + + ConfigServerClientOptions clone = original.Clone(); + + clone.ClientCertificate.Should().NotBeSameAs(original.ClientCertificate); + clone.ClientCertificate.Certificate.Should().BeSameAs(original.ClientCertificate.Certificate); + clone.ClientCertificate.IssuerChain.Should().NotBeSameAs(original.ClientCertificate.IssuerChain); + clone.ClientCertificate.IssuerChain.Should().ContainSingle().Which.Should().BeSameAs(issuerCertificate); + + original.ClientCertificate.IssuerChain.Clear(); + clone.ClientCertificate.IssuerChain.Should().ContainSingle(); + + clone.Enabled.Should().Be(original.Enabled); + clone.FailFast.Should().Be(original.FailFast); + clone.Environment.Should().Be(original.Environment); + clone.Label.Should().Be(original.Label); + clone.Name.Should().Be(original.Name); + clone.Uri.Should().Be(original.Uri); + clone.Username.Should().Be(original.Username); + clone.Password.Should().Be(original.Password); + clone.Token.Should().Be(original.Token); + clone.Timeout.Should().Be(original.Timeout); + clone.PollingInterval.Should().Be(original.PollingInterval); + clone.ValidateCertificates.Should().Be(original.ValidateCertificates); + + clone.Retry.Should().NotBeSameAs(original.Retry); + clone.Retry.Enabled.Should().Be(original.Retry.Enabled); + clone.Retry.InitialInterval.Should().Be(original.Retry.InitialInterval); + clone.Retry.MaxInterval.Should().Be(original.Retry.MaxInterval); + clone.Retry.Multiplier.Should().Be(original.Retry.Multiplier); + clone.Retry.MaxAttempts.Should().Be(original.Retry.MaxAttempts); + + clone.Discovery.Should().NotBeSameAs(original.Discovery); + clone.Discovery.Enabled.Should().Be(original.Discovery.Enabled); + clone.Discovery.ServiceId.Should().Be(original.Discovery.ServiceId); + + clone.Health.Should().NotBeSameAs(original.Health); + clone.Health.Enabled.Should().Be(original.Health.Enabled); + clone.Health.TimeToLive.Should().Be(original.Health.TimeToLive); + + clone.AccessTokenUri.Should().Be(original.AccessTokenUri); + clone.ClientSecret.Should().Be(original.ClientSecret); + clone.ClientId.Should().Be(original.ClientId); + clone.TokenTtl.Should().Be(original.TokenTtl); + clone.TokenRenewRate.Should().Be(original.TokenRenewRate); + clone.DisableTokenRenewal.Should().Be(original.DisableTokenRenewal); + + clone.Headers.Should().NotBeSameAs(original.Headers); + clone.Headers.Should().BeEquivalentTo(original.Headers); + } + + [Fact] + public void Certificate_configuration_survives_options_reload() + { + const string configServerResponseJson = """ + { + "name": "myName", + "profiles": [ + "Production" + ], + "label": "test-label", + "version": "test-version", + "propertySources": [] + } + """; + + var fileProvider = new MemoryFileProvider(); + + fileProvider.IncludeAppSettingsJsonFile(""" + { + "spring": { + "cloud": { + "config": { + "name": "myName", + "timeout": 30000 + } + } + }, + "Certificates": { + "ConfigServer": { + "CertificateFilePath": "instance.crt", + "PrivateKeyFilePath": "instance.key" + } + } + } + """); + + using var handler = new DelegateToMockHttpClientHandler(); + + handler.Mock.Expect(HttpMethod.Get, "http://localhost:8888/myName/Production").Respond("application/json", configServerResponseJson); + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + // ReSharper disable once AccessToDisposedClosure + configurationBuilder.AddConfigServer(new ConfigServerClientOptions(), null, () => handler, NullLoggerFactory.Instance); + IConfigurationRoot configuration = configurationBuilder.Build(); + + handler.Mock.VerifyNoOutstandingExpectation(); + + ConfigServerConfigurationProvider provider = configuration.Providers.OfType().Single(); + + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(configuration); + services.ConfigureConfigServerClientOptions(); + + using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + var optionsMonitor = serviceProvider.GetRequiredService>(); + + provider.ClientOptions.ClientCertificate.Certificate.Should().NotBeNull(); + optionsMonitor.CurrentValue.ClientCertificate.Certificate.Should().NotBeNull(); + + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "spring": { + "cloud": { + "config": { + "name": "myName", + "timeout": 15000 + } + } + }, + "Certificates": { + "ConfigServer": { + "CertificateFilePath": "instance.crt", + "PrivateKeyFilePath": "instance.key" + } + } + } + """); + + fileProvider.NotifyChanged(); + + provider.ClientOptions.Timeout.Should().Be(15_000); + provider.ClientOptions.ClientCertificate.Certificate.Should().NotBeNull(); + optionsMonitor.CurrentValue.Timeout.Should().Be(15_000); + optionsMonitor.CurrentValue.ClientCertificate.Certificate.Should().NotBeNull(); + } + + [Fact] + public void Changes_in_IConfiguration_update_provider_options_and_injected_options() + { + const string configServerResponseJson = """ + { + "name": "example-app-name", + "profiles": [ + "example-profile" + ], + "label": "example-label", + "version": "1", + "propertySources": [ + { + "name": "example-source", + "source": { + "example-server-key": "example-server-value" + } + } + ] + } + """; + + var fileProvider = new MemoryFileProvider(); + + fileProvider.IncludeAppSettingsJsonFile(""" + { + "custom": { + "profileName": "example-profile" + }, + "spring": { + "cloud": { + "config": { + "uri": "https://config.server.com:9999", + "name": "example-app-name", + "env": "${custom:profileName}", + "timeout": 30000 + } + } + } + } + """); + + using var handler = new DelegateToMockHttpClientHandler(); + + handler.Mock.Expect(HttpMethod.Get, "https://config.server.com:9999/example-app-name/example-profile/example-label") + .Respond("application/json", configServerResponseJson); + + var defaultOptions = new ConfigServerClientOptions + { + Name = "ignored-because-overridden-from-appsettings", + Label = "example-label" // used, but missing in IConfiguration and injected options + }; + + Action configureOptions = options => options.FailFast = true; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + configurationBuilder.AddPlaceholderResolver(); + // ReSharper disable once AccessToDisposedClosure + configurationBuilder.AddConfigServer(defaultOptions, configureOptions, () => handler, NullLoggerFactory.Instance); + IConfigurationRoot configuration = configurationBuilder.Build(); + + handler.Mock.VerifyNoOutstandingExpectation(); + handler.Mock.Clear(); + + ConfigServerConfigurationProvider provider = configuration.Providers.OfType().Single(); + + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(configuration); + services.ConfigureConfigServerClientOptions(configureOptions); + + using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + var optionsMonitor = serviceProvider.GetRequiredService>(); + + provider.ClientOptions.Uri.Should().Be("https://config.server.com:9999"); + provider.ClientOptions.Name.Should().Be("example-app-name"); + provider.ClientOptions.Environment.Should().Be("example-profile"); + provider.ClientOptions.Timeout.Should().Be(30_000); + provider.ClientOptions.Label.Should().Be("example-label"); + provider.ClientOptions.FailFast.Should().BeTrue(); + + optionsMonitor.CurrentValue.Uri.Should().Be(provider.ClientOptions.Uri); + optionsMonitor.CurrentValue.Name.Should().Be(provider.ClientOptions.Name); + optionsMonitor.CurrentValue.Environment.Should().Be(provider.ClientOptions.Environment); + optionsMonitor.CurrentValue.Timeout.Should().Be(provider.ClientOptions.Timeout); + optionsMonitor.CurrentValue.Label.Should().BeNull(); + optionsMonitor.CurrentValue.FailFast.Should().BeTrue(); + + configuration["example-server-key"].Should().Be("example-server-value"); + + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "custom": { + "profileName": "example-profile" + }, + "spring": { + "cloud": { + "config": { + "uri": "https://alternate-config.server.com:7777", + "name": "alternate-name", + "env": "${custom:profileName}", + "timeout": 15000, + "label": "alternate-label" + } + } + } + } + """); + + fileProvider.NotifyChanged(); + + AssertFinal(); + + handler.Mock.Expect(HttpMethod.Get, "https://alternate-config.server.com:7777/alternate-name/example-profile/alternate-label") + .Respond("application/json", configServerResponseJson); + + provider.Load(); + handler.Mock.VerifyNoOutstandingExpectation(); + + AssertFinal(); + + void AssertFinal() + { + provider.ClientOptions.Uri.Should().Be("https://alternate-config.server.com:7777"); + provider.ClientOptions.Name.Should().Be("alternate-name"); + provider.ClientOptions.Environment.Should().Be("example-profile"); + provider.ClientOptions.Timeout.Should().Be(15_000); + provider.ClientOptions.Label.Should().Be("alternate-label"); + provider.ClientOptions.FailFast.Should().BeTrue(); + + optionsMonitor.CurrentValue.Uri.Should().Be(provider.ClientOptions.Uri); + optionsMonitor.CurrentValue.Name.Should().Be(provider.ClientOptions.Name); + optionsMonitor.CurrentValue.Environment.Should().Be(provider.ClientOptions.Environment); + optionsMonitor.CurrentValue.Timeout.Should().Be(provider.ClientOptions.Timeout); + optionsMonitor.CurrentValue.Label.Should().Be(provider.ClientOptions.Label); + optionsMonitor.CurrentValue.FailFast.Should().BeTrue(); + + configuration["example-server-key"].Should().Be("example-server-value"); + } + } } diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationBuilderExtensionsCoreTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationBuilderExtensionsCoreTest.cs index b78ec586fa..f299b94b3a 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationBuilderExtensionsCoreTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationBuilderExtensionsCoreTest.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Steeltoe.Common.TestResources; -using Steeltoe.Common.TestResources.IO; using Steeltoe.Configuration.Placeholder; namespace Steeltoe.Configuration.ConfigServer.Test; @@ -43,7 +42,8 @@ public void AddConfigServer_WithLoggerFactorySucceeds() IList logMessages = loggerProvider.GetAll(); - logMessages.Should().Contain("DBUG Steeltoe.Configuration.ConfigServer.ConfigServerConfigurationProvider: Fetching configuration from server(s)."); + logMessages.Should().Contain( + "DBUG Steeltoe.Configuration.ConfigServer.ConfigServerConfigurationProvider: Fetching remote configuration from server(s)."); } [Fact] @@ -81,14 +81,11 @@ public void AddConfigServer_JsonAppSettingsConfiguresClient() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(directory); - configurationBuilder.AddJsonFile(fileName); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); configurationBuilder.AddConfigServer(); IConfigurationRoot configurationRoot = configurationBuilder.Build(); @@ -135,14 +132,11 @@ public void AddConfigServer_ValidateCertificates_DisablesCertValidation() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(directory); - configurationBuilder.AddJsonFile(fileName); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); configurationBuilder.AddConfigServer(); IConfigurationRoot configurationRoot = configurationBuilder.Build(); @@ -169,14 +163,11 @@ public void AddConfigServer_Validate_Certificates_DisablesCertValidation() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(directory); - configurationBuilder.AddJsonFile(fileName); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); configurationBuilder.AddConfigServer(); IConfigurationRoot configurationRoot = configurationBuilder.Build(); @@ -208,14 +199,11 @@ public void AddConfigServer_XmlAppSettingsConfiguresClient() """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile("appsettings.xml", appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsXmlFile(appSettings); var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(directory); - configurationBuilder.AddXmlFile(fileName); + configurationBuilder.AddInMemoryAppSettingsXmlFile(fileProvider); configurationBuilder.AddConfigServer(); IConfigurationRoot configurationRoot = configurationBuilder.Build(); @@ -250,14 +238,11 @@ public void AddConfigServer_IniAppSettingsConfiguresClient() password=myPassword """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile("appsettings.ini", appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsIniFile(appSettings); var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(directory); - configurationBuilder.AddIniFile(fileName); + configurationBuilder.AddInMemoryAppSettingsIniFile(fileProvider); configurationBuilder.AddConfigServer(); IConfigurationRoot configurationRoot = configurationBuilder.Build(); @@ -347,15 +332,11 @@ public void AddConfigServer_SubstitutesPlaceholders() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(directory); - configurationBuilder.AddJsonFile(fileName); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); configurationBuilder.AddPlaceholderResolver(); configurationBuilder.AddConfigServer(); IConfigurationRoot configurationRoot = configurationBuilder.Build(); @@ -442,21 +423,15 @@ public void AddConfigServer_WithCloudfoundryEnvironment_ConfiguresClientCorrectl } """; - using var sandbox = new Sandbox(); - string appSettingsPath = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string appSettingsFileName = Path.GetFileName(appSettingsPath); - - string vcapAppPath = sandbox.CreateFile("vcapapp.json", vcapApplication); - string vcapAppFileName = Path.GetFileName(vcapAppPath); - - string vcapServicesPath = sandbox.CreateFile("vcapservices.json", vcapServices); - string vcapServicesFileName = Path.GetFileName(vcapServicesPath); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); + fileProvider.IncludeFile("vcapapp.json", vcapApplication); + fileProvider.IncludeFile("vcapservices.json", vcapServices); var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(sandbox.FullPath); - configurationBuilder.AddJsonFile(appSettingsFileName); - configurationBuilder.AddJsonFile(vcapAppFileName); - configurationBuilder.AddJsonFile(vcapServicesFileName); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + configurationBuilder.AddInMemoryJsonFile(fileProvider, "vcapapp.json"); + configurationBuilder.AddInMemoryJsonFile(fileProvider, "vcapservices.json"); configurationBuilder.AddConfigServer(); IConfigurationRoot configurationRoot = configurationBuilder.Build(); @@ -550,21 +525,15 @@ public void AddConfigServer_WithCloudfoundryEnvironmentSCS3_ConfiguresClientCorr } """; - using var sandbox = new Sandbox(); - string appSettingsPath = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string appSettingsFileName = Path.GetFileName(appSettingsPath); - - string vcapAppPath = sandbox.CreateFile("vcapapp.json", vcapApplication); - string vcapAppFileName = Path.GetFileName(vcapAppPath); - - string vcapServicesPath = sandbox.CreateFile("vcapservices.json", vcapServices); - string vcapServicesFileName = Path.GetFileName(vcapServicesPath); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); + fileProvider.IncludeFile("vcapapp.json", vcapApplication); + fileProvider.IncludeFile("vcapservices.json", vcapServices); var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(sandbox.FullPath); - configurationBuilder.AddJsonFile(appSettingsFileName); - configurationBuilder.AddJsonFile(vcapAppFileName); - configurationBuilder.AddJsonFile(vcapServicesFileName); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + configurationBuilder.AddInMemoryJsonFile(fileProvider, "vcapapp.json"); + configurationBuilder.AddInMemoryJsonFile(fileProvider, "vcapservices.json"); configurationBuilder.AddConfigServer(); IConfigurationRoot configurationRoot = configurationBuilder.Build(); diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationBuilderExtensionsTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationBuilderExtensionsTest.cs index 57ed14e112..cdfeb6bbb4 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationBuilderExtensionsTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationBuilderExtensionsTest.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Steeltoe.Common.TestResources; using Steeltoe.Configuration.CloudFoundry; @@ -130,12 +129,11 @@ public void AddConfigServer_WithConfigServerCertificate_AddsConfigServerSourceWi var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddInMemoryCollection(appSettings); - configurationBuilder.AddConfigServer(options, NullLoggerFactory.Instance); - _ = configurationBuilder.Build(); + configurationBuilder.AddConfigServer(options); + IConfigurationRoot configurationRoot = configurationBuilder.Build(); - ConfigServerConfigurationSource? source = configurationBuilder.EnumerateSources().SingleOrDefault(); - source.Should().NotBeNull(); - source.DefaultOptions.ClientCertificate.Should().NotBeNull(); + ConfigServerConfigurationProvider provider = configurationRoot.EnumerateProviders().Single(); + provider.ClientOptions.ClientCertificate.Certificate.Should().NotBeNull(); } [Fact] @@ -154,12 +152,11 @@ public void AddConfigServer_WithGlobalCertificate_AddsConfigServerSourceWithCert var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddInMemoryCollection(appSettings); - configurationBuilder.AddConfigServer(options, NullLoggerFactory.Instance); - _ = configurationBuilder.Build(); + configurationBuilder.AddConfigServer(options); + IConfigurationRoot configurationRoot = configurationBuilder.Build(); - ConfigServerConfigurationSource? source = configurationBuilder.EnumerateSources().SingleOrDefault(); - source.Should().NotBeNull(); - source.DefaultOptions.ClientCertificate.Should().NotBeNull(); + ConfigServerConfigurationProvider provider = configurationRoot.EnumerateProviders().Single(); + provider.ClientOptions.ClientCertificate.Certificate.Should().NotBeNull(); } [Fact] @@ -172,21 +169,6 @@ public void AddConfigServer_AddsConfigServerSourceToList() source.Should().NotBeNull(); } - [Fact] - public void AddConfigServer_WithLoggerFactorySucceeds() - { - CapturingLoggerProvider loggerProvider = new(); - using var loggerFactory = new LoggerFactory([loggerProvider]); - - var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.AddConfigServer(loggerFactory); - _ = configurationBuilder.Build(); - - IList logMessages = loggerProvider.GetAll(); - - logMessages.Should().Contain("DBUG Steeltoe.Configuration.ConfigServer.ConfigServerConfigurationProvider: Fetching configuration from server(s)."); - } - [Theory] [InlineData(VcapServicesV2)] [InlineData(VcapServicesV3)] @@ -217,37 +199,94 @@ public void AddConfigServer_VCAP_SERVICES_Override_Defaults(string vcapServices) provider.Should().BeOfType(); provider.ClientOptions.Uri.Should().NotBe("https://uri-from-settings"); provider.ClientOptions.Uri.Should().Be("https://uri-from-vcap-services"); + provider.ClientOptions.ClientId.Should().Be("some-client-id"); + provider.ClientOptions.ClientSecret.Should().Be("some-secret"); + provider.ClientOptions.AccessTokenUri.Should().Be("https://uaa-uri-from-vcap-services/oauth/token"); } [Fact] - public void AddConfigServer_PaysAttentionToSettings() + public void AddConfigServer_CallbackOverridesConfigurationAndDefaultOptions() { var options = new ConfigServerClientOptions { - Name = "testConfigName", - Label = "testConfigLabel", - Environment = "testEnv", - Username = "testUser", - Password = "testPassword", + Name = "nameInOptions", + Label = "labelInOptions", + Environment = "environmentInOptions", + Username = "usernameInOptions", + Password = "passwordInOptions", Timeout = 10, Retry = { - Enabled = false + InitialInterval = 5, + MaxInterval = 15, + MaxAttempts = 12 } }; + var fileProvider = new MemoryFileProvider(); + + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Spring": { + "Cloud": { + "Config": { + "Name": "nameInAppSettings", + "Label": "labelInAppSettings", + "Timeout": 50, + "Retry": { + "MaxInterval": 100, + "MaxAttempts": 9 + } + } + } + } + } + """); + + Action configureOptions = clientOptions => clientOptions.Retry.MaxAttempts = 2; + var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.AddConfigServer(options, NullLoggerFactory.Instance); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + configurationBuilder.AddConfigServer(options, configureOptions, null, NullLoggerFactory.Instance); IConfigurationRoot configurationRoot = configurationBuilder.Build(); ConfigServerConfigurationProvider? provider = configurationRoot.EnumerateProviders().FirstOrDefault(); provider.Should().NotBeNull(); - provider.ClientOptions.Label.Should().Be("testConfigLabel"); - provider.ClientOptions.Name.Should().Be("testConfigName"); - provider.ClientOptions.Environment.Should().Be("testEnv"); - provider.ClientOptions.Username.Should().Be("testUser"); - provider.ClientOptions.Password.Should().Be("testPassword"); + provider.ClientOptions.Name.Should().Be("nameInAppSettings"); + provider.ClientOptions.Label.Should().Be("labelInAppSettings"); + provider.ClientOptions.Environment.Should().Be("environmentInOptions"); + provider.ClientOptions.Username.Should().Be("usernameInOptions"); + provider.ClientOptions.Password.Should().Be("passwordInOptions"); + provider.ClientOptions.Timeout.Should().Be(50); + provider.ClientOptions.Retry.InitialInterval.Should().Be(5); + provider.ClientOptions.Retry.MaxInterval.Should().Be(100); + provider.ClientOptions.Retry.MaxAttempts.Should().Be(2); + + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Spring": { + "Cloud": { + "Config": { + "Name": "alternateNameInAppSettings", + "Username": "alternateUsernameInAppSettings" + } + } + } + } + """); + + fileProvider.NotifyChanged(); + + provider.ClientOptions.Name.Should().Be("alternateNameInAppSettings"); + provider.ClientOptions.Label.Should().Be("labelInOptions"); + provider.ClientOptions.Environment.Should().Be("environmentInOptions"); + provider.ClientOptions.Username.Should().Be("alternateUsernameInAppSettings"); + provider.ClientOptions.Password.Should().Be("passwordInOptions"); + provider.ClientOptions.Timeout.Should().Be(10); + provider.ClientOptions.Retry.InitialInterval.Should().Be(5); + provider.ClientOptions.Retry.MaxInterval.Should().Be(15); + provider.ClientOptions.Retry.MaxAttempts.Should().Be(2); } [Fact] diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Loading.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Loading.cs index e5f9f669ac..257fdb3821 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Loading.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Loading.cs @@ -2,13 +2,18 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Text; using FluentAssertions.Extensions; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; +using RichardSzalay.MockHttp; using Steeltoe.Common.TestResources; +// ReSharper disable AccessToDisposedClosure + namespace Steeltoe.Configuration.ConfigServer.Test; public sealed partial class ConfigServerConfigurationProviderTest @@ -22,11 +27,12 @@ public async Task RemoteLoadAsync_HostTimesOut() }; var httpClientHandler = new SlowHttpClientHandler(1.Seconds(), new HttpResponseMessage()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => httpClientHandler, NullLoggerFactory.Instance); + provider.Load(); + List requestUris = [new("http://localhost:9999/app/profile")]; - // ReSharper disable once AccessToDisposedClosure - Func action = async () => await provider.RemoteLoadAsync(requestUris, null, TestContext.Current.CancellationToken); + Func action = async () => await provider.RemoteLoadAsync(provider.ClientOptions, requestUris, null, TestContext.Current.CancellationToken); (await action.Should().ThrowExactlyAsync()).WithInnerExceptionExactly(); } @@ -34,59 +40,42 @@ public async Task RemoteLoadAsync_HostTimesOut() [Fact] public async Task RemoteLoadAsync_ConfigServerReturnsGreaterThanEqualBadRequest() { - using var startup = new TestConfigServerStartup(); - startup.ReturnStatus = [500]; + ConfigServerClientOptions options = GetCommonOptions(); - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.Expect(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}").Respond(HttpStatusCode.InternalServerError); - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); - ConfigServerClientOptions options = GetCommonOptions(); - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); - - // ReSharper disable once AccessToDisposedClosure - Func action = async () => await provider.RemoteLoadAsync(options.GetUris(), null, TestContext.Current.CancellationToken); + Func action = async () => await provider.RemoteLoadAsync(provider.ClientOptions, options.GetUris(), null, TestContext.Current.CancellationToken); await action.Should().ThrowExactlyAsync(); - startup.LastRequest.Should().NotBeNull(); - startup.LastRequest.Path.Value.Should().Be($"/{options.Name}/{options.Environment}"); + handler.Mock.VerifyNoOutstandingExpectation(); } [Fact] public async Task RemoteLoadAsync_ConfigServerReturnsLessThanBadRequest() { - using var startup = new TestConfigServerStartup(); - startup.ReturnStatus = [204]; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); + ConfigServerClientOptions options = GetCommonOptions(); - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.Expect(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}").Respond(HttpStatusCode.NoContent); - ConfigServerClientOptions options = GetCommonOptions(); - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); - ConfigEnvironment? result = await provider.RemoteLoadAsync(options.GetUris(), null, TestContext.Current.CancellationToken); + ConfigEnvironment? result = await provider.RemoteLoadAsync(provider.ClientOptions, options.GetUris(), null, TestContext.Current.CancellationToken); - startup.LastRequest.Should().NotBeNull(); - startup.LastRequest.Path.Value.Should().Be($"/{options.Name}/{options.Environment}"); + handler.Mock.VerifyNoOutstandingExpectation(); result.Should().BeNull(); } [Fact] - public async Task Create_WithPollingTimer() + public async Task Create_WithConfigurationReloadTimer() { await TestFailureTracer.CaptureAsync(async tracer => { - const string environment = """ + const string responseJson = """ { "name": "test-name", "profiles": [ @@ -98,18 +87,6 @@ await TestFailureTracer.CaptureAsync(async tracer => } """; - using var startup = new TestConfigServerStartup(); - startup.Response = environment; - startup.ReturnStatus = [.. Enumerable.Repeat(200, 100)]; - startup.Label = "test-label"; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); - var options = new ConfigServerClientOptions { Name = "myName", @@ -117,28 +94,45 @@ await TestFailureTracer.CaptureAsync(async tracer => Label = "label,test-label" }; - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, tracer.LoggerFactory); + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.When(HttpMethod.Get, "http://localhost:8888/myName/Production/label").Respond(HttpStatusCode.NotFound); + + using var firstRequestCountdownEvent = new CountdownEvent(1); + + MockedRequest testLabelRequest = handler.Mock.When(HttpMethod.Get, "http://localhost:8888/myName/Production/test-label").Respond(_ => + { + if (!firstRequestCountdownEvent.IsSet) + { + firstRequestCountdownEvent.Signal(); + } + + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(responseJson, Encoding.UTF8, "application/json") + }; + }); + + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, tracer.LoggerFactory); + provider.Load(); - bool firstRequestCompleted = startup.WaitForFirstRequest(2.Seconds()); + bool firstRequestCompleted = firstRequestCountdownEvent.Wait(2.Seconds(), TestContext.Current.CancellationToken); firstRequestCompleted.Should().BeTrue(); - startup.RequestCount.Should().BeGreaterThanOrEqualTo(1); - startup.LastRequest.Should().NotBeNull(); + handler.Mock.GetMatchCount(testLabelRequest).Should().BeGreaterThanOrEqualTo(1); await Task.Delay(2.Seconds(), TestContext.Current.CancellationToken); - startup.RequestCount.Should().BeGreaterThanOrEqualTo(2); + handler.Mock.GetMatchCount(testLabelRequest).Should().BeGreaterThanOrEqualTo(2); provider.GetReloadToken().HasChanged.Should().BeFalse(); }); } [Fact] - public async Task Create_FailFastEnabledAndExceptionThrownDuringPolling_DoesNotCrash() + public async Task Create_FailFastEnabledAndExceptionThrownDuringPolledConfigurationReload_DoesNotCrash() { await TestFailureTracer.CaptureAsync(async tracer => { - const string environment = """ + const string responseJson = """ { "name": "test-name", "profiles": [ @@ -150,20 +144,6 @@ await TestFailureTracer.CaptureAsync(async tracer => } """; - using var startup = new TestConfigServerStartup(); - startup.Response = environment; - - // Initial requests succeed, but later requests return 400 status code so that an exception is thrown during polling - startup.ReturnStatus = [.. Enumerable.Repeat(200, 2).Concat(Enumerable.Repeat(400, 100))]; - startup.Label = "test-label"; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); - var options = new ConfigServerClientOptions { Name = "myName", @@ -172,28 +152,139 @@ await TestFailureTracer.CaptureAsync(async tracer => Label = "test-label" }; - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, tracer.LoggerFactory); + using var handler = new DelegateToMockHttpClientHandler(); + using var firstRequestCountdownEvent = new CountdownEvent(1); + int requestCount = 0; + + MockedRequest testLabelRequest = handler.Mock.When(HttpMethod.Get, "http://localhost:8888/myName/Production/test-label").Respond(_ => + { + int currentCount = Interlocked.Increment(ref requestCount); + + if (!firstRequestCountdownEvent.IsSet) + { + firstRequestCountdownEvent.Signal(); + } + + if (currentCount <= 2) + { + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(responseJson, Encoding.UTF8, "application/json") + }; + } - bool firstRequestCompleted = startup.WaitForFirstRequest(2.Seconds()); + return new HttpResponseMessage(HttpStatusCode.BadRequest); + }); + + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, tracer.LoggerFactory); + provider.Load(); + + bool firstRequestCompleted = firstRequestCountdownEvent.Wait(2.Seconds(), TestContext.Current.CancellationToken); firstRequestCompleted.Should().BeTrue(); - startup.RequestCount.Should().BeGreaterThanOrEqualTo(1); - startup.LastRequest.Should().NotBeNull(); + handler.Mock.GetMatchCount(testLabelRequest).Should().BeGreaterThanOrEqualTo(1); await Task.Delay(2.Seconds(), TestContext.Current.CancellationToken); - startup.RequestCount.Should().BeGreaterThanOrEqualTo(2); + handler.Mock.GetMatchCount(testLabelRequest).Should().BeGreaterThanOrEqualTo(2); provider.GetReloadToken().HasChanged.Should().BeFalse(); }); } [Fact] - public async Task Create_WithNonZeroPollingIntervalAndClientDisabled_PollingDisabled() + public async Task Create_WithNonZeroPollingIntervalAndClientDisabled_PollingConfigurationReloadDisabled() { - const string environment = """ + var options = new ConfigServerClientOptions + { + Name = "myName", + Enabled = false, + PollingInterval = 300.Milliseconds(), + Label = "label,test-label" + }; + + using var handler = new DelegateToMockHttpClientHandler(); + MockedRequest request = handler.Mock.When(HttpMethod.Get, "http://localhost:8888/myName/Production/label").Respond(HttpStatusCode.OK); + + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); + provider.Load(); + + await Task.Delay(2.Seconds(), TestContext.Current.CancellationToken); + handler.Mock.GetMatchCount(request).Should().Be(0); + } + + [Theory] + [InlineData(false, "00:00:01")] + [InlineData(true, "00:00:00")] + public void OnSettingsChanged_stops_reload_timer_when_polling_no_longer_enabled(bool enabled, string pollingInterval) + { + const string responseJson = """ { - "name": "test-name", + "name": "myName", + "profiles": [ + "Production" + ], + "label": "test-label", + "version": "test-version", + "propertySources": [] + } + """; + + var fileProvider = new MemoryFileProvider(); + + fileProvider.IncludeAppSettingsJsonFile(""" + { + "spring": { + "cloud": { + "config": { + "name": "myName", + "enabled": true, + "pollingInterval": "00:00:01" + } + } + } + } + """); + + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.When(HttpMethod.Get, "http://localhost:8888/myName/Production").Respond("application/json", responseJson); + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + configurationBuilder.AddConfigServer(new ConfigServerClientOptions(), null, () => handler, NullLoggerFactory.Instance); + IConfigurationRoot configuration = configurationBuilder.Build(); + + ConfigServerConfigurationProvider provider = configuration.Providers.OfType().Single(); + + FieldInfo reloadTimerField = + typeof(ConfigServerConfigurationProvider).GetField("_configurationReloadTimer", BindingFlags.NonPublic | BindingFlags.Instance)!; + + reloadTimerField.GetValue(provider).Should().NotBeNull(); + + fileProvider.ReplaceAppSettingsJsonFile($$""" + { + "spring": { + "cloud": { + "config": { + "name": "myName", + "enabled": {{(enabled ? "true" : "false")}}, + "pollingInterval": "{{pollingInterval}}" + } + } + } + } + """); + + fileProvider.NotifyChanged(); + + reloadTimerField.GetValue(provider).Should().BeNull(); + } + + [Fact] + public void OnSettingsChanged_reschedules_reload_timer_when_polling_interval_changes() + { + const string responseJson = """ + { + "name": "myName", "profiles": [ "Production" ], @@ -203,37 +294,164 @@ public async Task Create_WithNonZeroPollingIntervalAndClientDisabled_PollingDisa } """; - using var startup = new TestConfigServerStartup(); - startup.Response = environment; - startup.ReturnStatus = [.. Enumerable.Repeat(200, 100)]; - startup.Label = "test-label"; + var fileProvider = new MemoryFileProvider(); + + fileProvider.IncludeAppSettingsJsonFile(""" + { + "spring": { + "cloud": { + "config": { + "name": "myName", + "enabled": true, + "pollingInterval": "00:00:05" + } + } + } + } + """); + + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.When(HttpMethod.Get, "http://localhost:8888/myName/Production").Respond("application/json", responseJson); + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + configurationBuilder.AddConfigServer(new ConfigServerClientOptions(), null, () => handler, NullLoggerFactory.Instance); + IConfigurationRoot configuration = configurationBuilder.Build(); + + ConfigServerConfigurationProvider provider = configuration.Providers.OfType().Single(); + + FieldInfo reloadTimerField = + typeof(ConfigServerConfigurationProvider).GetField("_configurationReloadTimer", BindingFlags.NonPublic | BindingFlags.Instance)!; + + reloadTimerField.GetValue(provider).Should().NotBeNull(); + + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "spring": { + "cloud": { + "config": { + "name": "myName", + "enabled": true, + "pollingInterval": "00:00:10" + } + } + } + } + """); - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); + fileProvider.NotifyChanged(); - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); + reloadTimerField.GetValue(provider).Should().NotBeNull("timer should be rescheduled, not stopped"); + } + + [Fact] + public void OnSettingsChanged_stops_vault_renew_timer_when_renewal_becomes_disabled() + { + const string responseJson = """ + { + "name": "myName", + "profiles": [ + "Production" + ], + "label": "test-label", + "version": "test-version", + "propertySources": [] + } + """; + + var fileProvider = new MemoryFileProvider(); + + fileProvider.IncludeAppSettingsJsonFile(""" + { + "spring": { + "cloud": { + "config": { + "name": "myName", + "token": "MyVaultToken" + } + } + } + } + """); + + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.When(HttpMethod.Get, "http://localhost:8888/myName/Production").Respond("application/json", responseJson); + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + configurationBuilder.AddConfigServer(new ConfigServerClientOptions(), null, () => handler, NullLoggerFactory.Instance); + IConfigurationRoot configuration = configurationBuilder.Build(); + + ConfigServerConfigurationProvider provider = configuration.Providers.OfType().Single(); + FieldInfo vaultTimerField = typeof(ConfigServerConfigurationProvider).GetField("_vaultRenewTimer", BindingFlags.NonPublic | BindingFlags.Instance)!; + + vaultTimerField.GetValue(provider).Should().NotBeNull(); + + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "spring": { + "cloud": { + "config": { + "name": "myName", + "token": "MyVaultToken", + "disableTokenRenewal": true + } + } + } + } + """); + + fileProvider.NotifyChanged(); + + vaultTimerField.GetValue(provider).Should().BeNull(); + } + + [Fact] + public void Load_MultipleConfigServers_SocketError_FallsBackToNextServer() + { + const string responseJson = """ + { + "name": "test-name", + "profiles": [ + "Production" + ], + "label": "test-label", + "version": "test-version", + "propertySources": [ + { + "name": "source", + "source": { + "key1": "value1" + } + } + ] + } + """; + + using var handler = new DelegateToMockHttpClientHandler(); + + handler.Mock.When(HttpMethod.Get, "http://server1:8888/myName/Production") + .Throw(new HttpRequestException("Connection refused", new SocketException((int)SocketError.ConnectionRefused))); + + handler.Mock.When(HttpMethod.Get, "http://server2:8888/myName/Production").Respond("application/json", responseJson); var options = new ConfigServerClientOptions { Name = "myName", - Enabled = false, - PollingInterval = 300.Milliseconds(), - Label = "label,test-label" + Uri = "http://server1:8888, http://server2:8888" }; - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); + provider.Load(); - startup.WaitForFirstRequest(2.Seconds()).Should().BeFalse(); + provider.TryGet("key1", out string? value).Should().BeTrue(); + value.Should().Be("value1"); } [Fact] - public async Task DoLoad_MultipleLabels_ChecksAllLabels() + public void Load_IdenticalData_DoesNotTriggerReload() { - const string environment = """ + const string responseJson = """ { "name": "test-name", "profiles": [ @@ -245,49 +463,77 @@ public async Task DoLoad_MultipleLabels_ChecksAllLabels() { "name": "source", "source": { - "key1": "value1", - "key2": 10 + "key1": "value1" } } ] } """; - using var startup = new TestConfigServerStartup(); - startup.Response = environment; + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.When(HttpMethod.Get, "http://localhost:8888/myName/Production").Respond("application/json", responseJson); + + var options = new ConfigServerClientOptions + { + Name = "myName" + }; - startup.ReturnStatus = - [ - 404, - 200 - ]; + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); + provider.Load(); - startup.Label = "test-label"; + provider.TryGet("key1", out string? value).Should().BeTrue(); + value.Should().Be("value1"); - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); + bool reloadFired = false; + provider.GetReloadToken().RegisterChangeCallback(_ => reloadFired = true, null); - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); + provider.Load(); + + reloadFired.Should().BeFalse("identical data should not trigger OnReload"); + provider.TryGet("key1", out value).Should().BeTrue(); + value.Should().Be("value1"); + } + + [Fact] + public void Load_MultipleLabels_ChecksAllLabels() + { + const string responseJson = """ + { + "name": "test-name", + "profiles": [ + "Production" + ], + "label": "test-label", + "version": "test-version", + "propertySources": [ + { + "name": "source", + "source": { + "key1": "value1", + "key2": 10 + } + } + ] + } + """; ConfigServerClientOptions options = GetCommonOptions(); options.Label = "label,test-label"; - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.Expect(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}/label").Respond(HttpStatusCode.NotFound); + handler.Mock.Expect(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}/test-label").Respond("application/json", responseJson); - await provider.DoLoadAsync(true, TestContext.Current.CancellationToken); + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); + provider.Load(); - startup.LastRequest.Should().NotBeNull(); - startup.RequestCount.Should().Be(2); - startup.LastRequest.Path.Value.Should().Be($"/{options.Name}/{options.Environment}/test-label"); + handler.Mock.VerifyNoOutstandingExpectation(); } [Fact] public async Task RemoteLoadAsync_ConfigServerReturnsGood() { - const string environment = """ + const string responseJson = """ { "name": "test-name", "profiles": [ @@ -307,24 +553,16 @@ public async Task RemoteLoadAsync_ConfigServerReturnsGood() } """; - using var startup = new TestConfigServerStartup(); - startup.Response = environment; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); + ConfigServerClientOptions options = GetCommonOptions(); - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.Expect(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}").Respond("application/json", responseJson); - ConfigServerClientOptions options = GetCommonOptions(); - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); - ConfigEnvironment? env = await provider.RemoteLoadAsync(options.GetUris(), null, TestContext.Current.CancellationToken); + ConfigEnvironment? env = await provider.RemoteLoadAsync(provider.ClientOptions, options.GetUris(), null, TestContext.Current.CancellationToken); - startup.LastRequest.Should().NotBeNull(); - startup.LastRequest.Path.Value.Should().Be($"/{options.Name}/{options.Environment}"); + handler.Mock.VerifyNoOutstandingExpectation(); env.Should().NotBeNull(); env.Name.Should().Be("test-name"); @@ -342,245 +580,156 @@ public async Task RemoteLoadAsync_ConfigServerReturnsGood() [Fact] public async Task Load_MultipleConfigServers_ReturnsGreaterThanEqualBadRequest_StopsChecking() { - using var startup = new TestConfigServerStartup(); - - startup.ReturnStatus = - [ - 500, - 200 - ]; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); - ConfigServerClientOptions options = GetCommonOptions(); options.Uri = "http://localhost:8888, http://localhost:8888"; - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); - await provider.LoadInternalAsync(true, TestContext.Current.CancellationToken); + using var handler = new DelegateToMockHttpClientHandler(); + + MockedRequest request = handler.Mock.When(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}") + .Respond(HttpStatusCode.InternalServerError); - startup.LastRequest.Should().NotBeNull(); - startup.LastRequest.Path.Value.Should().Be($"/{options.Name}/{options.Environment}"); - startup.RequestCount.Should().Be(1); + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); + provider.Load(); + + handler.Mock.GetMatchCount(request).Should().Be(1); await Task.Delay(2.Seconds(), TestContext.Current.CancellationToken); - startup.RequestCount.Should().Be(1); + handler.Mock.GetMatchCount(request).Should().Be(1); } [Fact] public async Task Load_MultipleConfigServers_ReturnsNotFoundStatus_DoesNotContinueChecking() { - using var startup = new TestConfigServerStartup(); - - startup.ReturnStatus = - [ - 404, - 200 - ]; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); - ConfigServerClientOptions options = GetCommonOptions(); options.Uri = "http://localhost:8888, http://localhost:8888"; - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); + using var handler = new DelegateToMockHttpClientHandler(); - await provider.LoadInternalAsync(true, TestContext.Current.CancellationToken); + MockedRequest request = handler.Mock.When(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}") + .Respond(HttpStatusCode.NotFound); - startup.LastRequest.Should().NotBeNull(); - startup.LastRequest.Path.Value.Should().Be($"/{options.Name}/{options.Environment}"); - startup.RequestCount.Should().Be(1); + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); + provider.Load(); + + handler.Mock.GetMatchCount(request).Should().Be(1); await Task.Delay(2.Seconds(), TestContext.Current.CancellationToken); - startup.RequestCount.Should().Be(1); + handler.Mock.GetMatchCount(request).Should().Be(1); } [Fact] - public async Task Load_ConfigServerReturnsNotFoundStatus() + public void Load_ConfigServerReturnsNotFoundStatus() { - using var startup = new TestConfigServerStartup(); - startup.ReturnStatus = [404]; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); - ConfigServerClientOptions options = GetCommonOptions(); - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); - await provider.LoadInternalAsync(true, TestContext.Current.CancellationToken); + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.Expect(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}").Respond(HttpStatusCode.NotFound); + + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); + provider.Load(); - startup.LastRequest.Should().NotBeNull(); - startup.LastRequest.Path.Value.Should().Be($"/{options.Name}/{options.Environment}"); - provider.Properties.Should().HaveCount(27); + handler.Mock.VerifyNoOutstandingExpectation(); + provider.InnerData.Should().BeEmpty(); } [Fact] - public async Task Load_ConfigServerReturnsNotFoundStatus_FailFastEnabled() + public void Load_ConfigServerReturnsNotFoundStatus_FailFastEnabled() { - using var startup = new TestConfigServerStartup(); - startup.ReturnStatus = [404]; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); - ConfigServerClientOptions options = GetCommonOptions(); options.FailFast = true; - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.Expect(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}").Respond(HttpStatusCode.NotFound); - // ReSharper disable once AccessToDisposedClosure - Func action = async () => await provider.LoadInternalAsync(true, TestContext.Current.CancellationToken); + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); - await action.Should().ThrowExactlyAsync(); + Action action = provider.Load; + + action.Should().ThrowExactly(); + handler.Mock.VerifyNoOutstandingExpectation(); } [Fact] public async Task Load_MultipleConfigServers_ReturnsNotFoundStatus__DoesNotContinueChecking_FailFastEnabled() { - using var startup = new TestConfigServerStartup(); - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); - ConfigServerClientOptions options = GetCommonOptions(); options.FailFast = true; options.Uri = "http://localhost:8888,http://localhost:8888"; - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); + using var handler = new DelegateToMockHttpClientHandler(); - startup.Reset(); + MockedRequest request = handler.Mock.When(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}") + .Respond(HttpStatusCode.NotFound); - startup.ReturnStatus = - [ - 404, - 200 - ]; + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); - // ReSharper disable once AccessToDisposedClosure - Func action = async () => await provider.LoadInternalAsync(true, TestContext.Current.CancellationToken); + Action action = provider.Load; - await action.Should().ThrowExactlyAsync(); - startup.RequestCount.Should().Be(1); + action.Should().ThrowExactly(); + handler.Mock.GetMatchCount(request).Should().Be(1); await Task.Delay(2.Seconds(), TestContext.Current.CancellationToken); - startup.RequestCount.Should().Be(1); + handler.Mock.GetMatchCount(request).Should().Be(1); } [Fact] - public async Task Load_UriInvalid_FailFastEnabled() + public void Load_UriInvalid_FailFastEnabled() { - using var startup = new TestConfigServerStartup(); - startup.ReturnStatus = [500]; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); - ConfigServerClientOptions options = GetCommonOptions(); options.Uri = "http://username:p@ssword@localhost:8888"; options.FailFast = true; - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); + using var handler = new DelegateToMockHttpClientHandler(); + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); - // ReSharper disable once AccessToDisposedClosure - Func action = async () => await provider.LoadInternalAsync(true, TestContext.Current.CancellationToken); + Action action = provider.Load; - await action.Should().ThrowExactlyAsync().WithMessage("One or more Config Server URIs in configuration are invalid."); + action.Should().ThrowExactly().WithMessage("One or more Config Server URIs in configuration are invalid."); + handler.Mock.VerifyNoOutstandingExpectation(); } [Fact] - public async Task Load_ConfigServerReturnsBadStatus_FailFastEnabled() + public void Load_ConfigServerReturnsBadStatus_FailFastEnabled() { - using var startup = new TestConfigServerStartup(); - startup.ReturnStatus = [500]; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); - ConfigServerClientOptions options = GetCommonOptions(); options.FailFast = true; - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.Expect(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}").Respond(HttpStatusCode.InternalServerError); + + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); - // ReSharper disable once AccessToDisposedClosure - Func action = async () => await provider.LoadInternalAsync(true, TestContext.Current.CancellationToken); + Action action = provider.Load; - await action.Should().ThrowExactlyAsync(); + action.Should().ThrowExactly(); + handler.Mock.VerifyNoOutstandingExpectation(); } [Fact] public async Task Load_MultipleConfigServers_ReturnsBadStatus_StopsChecking_FailFastEnabled() { - using var startup = new TestConfigServerStartup(); - - startup.ReturnStatus = - [ - 500, - 500, - 500 - ]; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); - ConfigServerClientOptions options = GetCommonOptions(); options.FailFast = true; options.Uri = "http://localhost:8888, http://localhost:8888, http://localhost:8888"; - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); + using var handler = new DelegateToMockHttpClientHandler(); - // ReSharper disable once AccessToDisposedClosure - Func action = async () => await provider.LoadInternalAsync(true, TestContext.Current.CancellationToken); + MockedRequest request = handler.Mock.When(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}") + .Respond(HttpStatusCode.InternalServerError); - await action.Should().ThrowExactlyAsync(); - startup.RequestCount.Should().Be(1); + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); + + Action action = provider.Load; + + action.Should().ThrowExactly(); + handler.Mock.GetMatchCount(request).Should().Be(1); await Task.Delay(2.Seconds(), TestContext.Current.CancellationToken); - startup.RequestCount.Should().Be(1); + handler.Mock.GetMatchCount(request).Should().Be(1); } [Fact] @@ -588,16 +737,6 @@ public async Task Load_ConfigServerReturnsBadStatus_FailFastEnabled_RetryEnabled { await TestFailureTracer.CaptureAsync(async tracer => { - using var startup = new TestConfigServerStartup(); - startup.ReturnStatus = [.. Enumerable.Repeat(500, 100)]; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); - var options = new ConfigServerClientOptions { Name = "myName", @@ -610,24 +749,25 @@ await TestFailureTracer.CaptureAsync(async tracer => Timeout = 1000 }; - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, tracer.LoggerFactory); + using var handler = new DelegateToMockHttpClientHandler(); + MockedRequest request = handler.Mock.When(HttpMethod.Get, "http://localhost:8888/myName/Production").Respond(HttpStatusCode.InternalServerError); + + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, tracer.LoggerFactory); - // ReSharper disable once AccessToDisposedClosure - Func action = async () => await provider.LoadInternalAsync(true, TestContext.Current.CancellationToken); + Action action = () => provider.Load(); - await action.Should().ThrowExactlyAsync(); + action.Should().ThrowExactly(); await Task.Delay(2.Seconds(), TestContext.Current.CancellationToken); - startup.RequestCount.Should().BeGreaterThan(3); + handler.Mock.GetMatchCount(request).Should().BeGreaterThan(3); }); } [Fact] - public async Task Load_ChangesDataDictionary() + public void Load_ChangesDataDictionary() { - const string environment = """ + const string responseJson = """ { "name": "test-name", "profiles": [ @@ -635,6 +775,7 @@ public async Task Load_ChangesDataDictionary() ], "label": "test-label", "version": "test-version", + "state": "test-state", "propertySources": [ { "name": "source", @@ -647,35 +788,30 @@ public async Task Load_ChangesDataDictionary() } """; - using var startup = new TestConfigServerStartup(); - startup.Response = environment; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); - ConfigServerClientOptions options = GetCommonOptions(); - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); - await provider.LoadInternalAsync(true, TestContext.Current.CancellationToken); + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.Expect(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}").Respond("application/json", responseJson); - startup.LastRequest.Should().NotBeNull(); - startup.LastRequest.Path.Value.Should().Be($"/{options.Name}/{options.Environment}"); + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); + provider.Load(); + handler.Mock.VerifyNoOutstandingExpectation(); provider.TryGet("key1", out string? value).Should().BeTrue(); value.Should().Be("value1"); provider.TryGet("key2", out value).Should().BeTrue(); value.Should().Be("10"); + + provider.TryGet("spring:cloud:config:client:version", out value).Should().BeTrue(); + value.Should().Be("test-version"); + provider.TryGet("spring:cloud:config:client:state", out value).Should().BeTrue(); + value.Should().Be("test-state"); } [Fact] - public async Task ReLoad_DataDictionary_With_New_Configurations() + public void ReLoad_DataDictionary_With_New_Configurations() { - const string environment = """ + const string responseJson = """ { "name": "test-name", "profiles": [ @@ -697,23 +833,15 @@ public async Task ReLoad_DataDictionary_With_New_Configurations() } """; - using var startup = new TestConfigServerStartup(); - startup.Response = environment; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri("http://localhost:8888"); - ConfigServerClientOptions options = GetCommonOptions(); - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(options, null, httpClientHandler, NullLoggerFactory.Instance); + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.Expect(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}").Respond("application/json", responseJson); + + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); provider.Load(); + handler.Mock.VerifyNoOutstandingExpectation(); - startup.LastRequest.Should().NotBeNull(); provider.TryGet("featureToggles:ShowModule:0", out string? value).Should().BeTrue(); value.Should().Be("FT1"); provider.TryGet("featureToggles:ShowModule:1", out value).Should().BeTrue(); @@ -723,28 +851,30 @@ public async Task ReLoad_DataDictionary_With_New_Configurations() provider.TryGet("enableSettings", out value).Should().BeTrue(); value.Should().Be("true"); - startup.Reset(); + handler.Mock.Clear(); - startup.Response = """ - { - "name": "test-name", - "profiles": [ - "Production" - ], - "label": "test-label", - "version": "test-version", - "propertySources": [ + const string newResponseJson = """ { - "name": "source", - "source": { - "featureToggles.ShowModule[0]": "none" - } + "name": "test-name", + "profiles": [ + "Production" + ], + "label": "test-label", + "version": "test-version", + "propertySources": [ + { + "name": "source", + "source": { + "featureToggles.ShowModule[0]": "none" + } + } + ] } - ] - } - """; + """; + handler.Mock.Expect(HttpMethod.Get, $"http://localhost:8888/{options.Name}/{options.Environment}").Respond("application/json", newResponseJson); provider.Load(); + handler.Mock.VerifyNoOutstandingExpectation(); provider.TryGet("featureToggles:ShowModule:0", out value).Should().BeTrue(); value.Should().Be("none"); @@ -754,35 +884,29 @@ public async Task ReLoad_DataDictionary_With_New_Configurations() } [Fact] - public void AddConfigServerClientSettings_ChangesDataDictionary() + public void DataDictionary_DoesNotContainRedundantClientSettings() { var options = new ConfigServerClientOptions { Enabled = false, FailFast = true, Environment = "environment", - Label = "label", + Label = "main", Name = "name", Uri = "https://foo.bar/", - Username = "username", - Password = "password", - Token = "vaultToken", + Username = "user", + Password = "pass", + Token = "vault-token", Timeout = 75_000, - PollingInterval = 35.5.Seconds(), + PollingInterval = TimeSpan.FromSeconds(30), ValidateCertificates = false, - AccessTokenUri = "https://token.server.com/", - ClientSecret = "client_secret", - ClientId = "client_id", - TokenTtl = 2, - TokenRenewRate = 1, - DisableTokenRenewal = true, Retry = { Enabled = true, - InitialInterval = 8, - MaxInterval = 16, - Multiplier = 1.1, - MaxAttempts = 7 + InitialInterval = 500, + MaxInterval = 5000, + Multiplier = 2.0, + MaxAttempts = 10 }, Discovery = { @@ -792,59 +916,63 @@ public void AddConfigServerClientSettings_ChangesDataDictionary() Health = { Enabled = false, - TimeToLive = 9 + TimeToLive = 999 }, + AccessTokenUri = "https://uaa.example.com/oauth/token", + ClientSecret = "secret", + ClientId = "client-id", + TokenTtl = 600_000, + TokenRenewRate = 120_000, + DisableTokenRenewal = true, Headers = { - ["headerName1"] = "headerValue1", - ["headerName2"] = "headerValue2" + ["X-Custom"] = "value" } }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); - provider.AddConfigServerClientOptions(); - - AssertDataValue("spring:cloud:config:enabled", "False"); - AssertDataValue("spring:cloud:config:failFast", "True"); - AssertDataValue("spring:cloud:config:env", "environment"); - AssertDataValue("spring:cloud:config:label", "label"); - AssertDataValue("spring:cloud:config:name", "name"); - AssertDataValue("spring:cloud:config:uri", "https://foo.bar/"); - AssertDataValue("spring:cloud:config:username", "username"); - AssertDataValue("spring:cloud:config:password", "password"); - AssertDataValue("spring:cloud:config:token", "vaultToken"); - AssertDataValue("spring:cloud:config:timeout", "75000"); - AssertDataValue("spring:cloud:config:pollingInterval", "00:00:35.5000000"); - AssertDataValue("spring:cloud:config:validateCertificates", "False"); - AssertDataValue("spring:cloud:config:accessTokenUri", "https://token.server.com/"); - AssertDataValue("spring:cloud:config:clientSecret", "client_secret"); - AssertDataValue("spring:cloud:config:clientId", "client_id"); - AssertDataValue("spring:cloud:config:tokenTtl", "2"); - AssertDataValue("spring:cloud:config:tokenRenewRate", "1"); - AssertDataValue("spring:cloud:config:disableTokenRenewal", "True"); - AssertDataValue("spring:cloud:config:retry:enabled", "True"); - AssertDataValue("spring:cloud:config:retry:initialInterval", "8"); - AssertDataValue("spring:cloud:config:retry:maxInterval", "16"); - AssertDataValue("spring:cloud:config:retry:multiplier", "1.1"); - AssertDataValue("spring:cloud:config:retry:maxAttempts", "7"); - AssertDataValue("spring:cloud:config:discovery:enabled", "True"); - AssertDataValue("spring:cloud:config:discovery:serviceId", "my-config-server"); - AssertDataValue("spring:cloud:config:health:enabled", "False"); - AssertDataValue("spring:cloud:config:health:timeToLive", "9"); - AssertDataValue("spring:cloud:config:headers:headerName1", "headerValue1"); - AssertDataValue("spring:cloud:config:headers:headerName2", "headerValue2"); - - void AssertDataValue(string key, string expected) - { - provider.TryGet(key, out string? value).Should().BeTrue(); - value.Should().Be(expected); - } + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); + + provider.TryGet("spring:cloud:config:enabled", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:failFast", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:env", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:label", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:name", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:uri", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:username", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:password", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:token", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:timeout", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:pollingInterval", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:validateCertificates", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:validate_Certificates", out _).Should().BeFalse(); + + provider.TryGet("spring:cloud:config:retry:enabled", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:retry:initialInterval", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:retry:maxInterval", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:retry:multiplier", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:retry:maxAttempts", out _).Should().BeFalse(); + + provider.TryGet("spring:cloud:config:discovery:enabled", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:discovery:serviceId", out _).Should().BeFalse(); + + provider.TryGet("spring:cloud:config:health:enabled", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:health:timeToLive", out _).Should().BeFalse(); + + provider.TryGet("spring:cloud:config:accessTokenUri", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:clientSecret", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:clientId", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:tokenTtl", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:tokenRenewRate", out _).Should().BeFalse(); + provider.TryGet("spring:cloud:config:disableTokenRenewal", out _).Should().BeFalse(); + + provider.TryGet("spring:cloud:config:headers:X-Custom", out _).Should().BeFalse(); } [Fact] - public async Task Reload_And_Bind_Without_Throwing_Exception() + public void Reload_And_Bind_Without_Throwing_Exception() { - const string environment = """ + const string responseJson = """ { "name": "test-name", "profiles": [ @@ -864,30 +992,22 @@ public async Task Reload_And_Bind_Without_Throwing_Exception() } """; - using var startup = new TestConfigServerStartup(); - startup.Response = environment; - - await using WebApplication app = TestWebApplicationBuilderFactory.Create().Build(); - startup.Configure(app); - await app.StartAsync(TestContext.Current.CancellationToken); - ConfigServerClientOptions clientOptions = GetCommonOptions(); - using TestServer server = app.GetTestServer(); - server.BaseAddress = new Uri(clientOptions.Uri!); - using var httpClientHandler = new ForwardingHttpClientHandler(server.CreateHandler()); - using var provider = new ConfigServerConfigurationProvider(clientOptions, null, httpClientHandler, NullLoggerFactory.Instance); + using var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.When(HttpMethod.Get, $"http://localhost:8888/{clientOptions.Name}/{clientOptions.Environment}").Respond("application/json", responseJson); var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.Add(new TestConfigServerConfigurationSource(provider)); + configurationBuilder.AddConfigServer(clientOptions, null, () => handler, NullLoggerFactory.Instance); IConfigurationRoot configurationRoot = configurationBuilder.Build(); + using ConfigServerConfigurationProvider provider = configurationRoot.EnumerateProviders().Single(); + TestOptions? testOptions = null; using var tokenSource = new CancellationTokenSource(250.Milliseconds()); _ = Task.Run(() => { - // ReSharper disable once AccessToDisposedClosure while (!tokenSource.IsCancellationRequested) { configurationRoot.Reload(); @@ -908,7 +1028,8 @@ private static ConfigServerClientOptions GetCommonOptions() { return new ConfigServerClientOptions { - Name = "myName" + Name = "myName", + Environment = "Staging" }; } diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Settings.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Settings.cs index 8380debc6b..1cd6f664f6 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Settings.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Settings.cs @@ -15,22 +15,23 @@ public sealed partial class ConfigServerConfigurationProviderTest public void DefaultConstructor_InitializedWithDefaultSettings() { var options = new ConfigServerClientOptions(); - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); string? expectedAppName = Assembly.GetEntryAssembly()!.GetName().Name; - TestHelper.VerifyDefaults(provider.ClientOptions, expectedAppName); + TestHelper.VerifyDefaults(provider.ClientOptions, expectedAppName, "Production"); } [Fact] public void SourceConstructor_WithDefaults_InitializesWithDefaultSettings() { - IConfiguration configuration = new ConfigurationBuilder().Build(); var options = new ConfigServerClientOptions(); - var source = new ConfigServerConfigurationSource(options, configuration, NullLoggerFactory.Instance); + var source = new ConfigServerConfigurationSource(options, [], null, null, null, NullLoggerFactory.Instance); using var provider = new ConfigServerConfigurationProvider(source, NullLoggerFactory.Instance); + provider.Load(); string? expectedAppName = Assembly.GetEntryAssembly()!.GetName().Name; - TestHelper.VerifyDefaults(provider.ClientOptions, expectedAppName); + TestHelper.VerifyDefaults(provider.ClientOptions, expectedAppName, "Production"); } [Fact] @@ -41,12 +42,13 @@ public void SourceConstructor_WithTimeoutConfigured_InitializesHttpClientWithCon ["spring:cloud:config:timeout"] = "30000" }; - IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(appSettings).Build(); + var builder = new ConfigurationBuilder(); + builder.AddInMemoryCollection(appSettings); + builder.AddConfigServer(); + IConfigurationRoot configuration = builder.Build(); - var options = new ConfigServerClientOptions(); - var source = new ConfigServerConfigurationSource(options, configuration, NullLoggerFactory.Instance); - using var provider = new ConfigServerConfigurationProvider(source, NullLoggerFactory.Instance); - using HttpClient httpClient = provider.CreateHttpClient(options); + ConfigServerConfigurationProvider provider = configuration.EnumerateProviders().Single(); + using HttpClient httpClient = provider.CreateHttpClient(provider.ClientOptions); httpClient.Should().NotBeNull(); httpClient.Timeout.Should().Be(30.Seconds()); @@ -61,10 +63,11 @@ public void GetConfigServerUri_NoBaseUri_Throws() Environment = "Production" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); - // ReSharper disable once AccessToDisposedClosure - Action action = () => provider.BuildConfigServerUri(null!, null); + // ReSharper disable AccessToDisposedClosure + Action action = () => provider.BuildConfigServerUri(provider.ClientOptions, null!, null); + // ReSharper restore AccessToDisposedClosure action.Should().ThrowExactly(); } @@ -78,8 +81,10 @@ public void GetConfigServerUri_NoLabel() Environment = "Production" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); - string uri = provider.BuildConfigServerUri(new Uri(options.Uri!), null).ToString(); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); + + string uri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri!), null).ToString(); uri.Should().Be($"{options.Uri}/{options.Name}/{options.Environment}"); } @@ -94,8 +99,10 @@ public void GetConfigServerUri_WithLabel() Label = "myLabel" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); - string uri = provider.BuildConfigServerUri(new Uri(options.Uri!), options.Label).ToString(); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); + + string uri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri!), options.Label).ToString(); uri.Should().Be($"{options.Uri}/{options.Name}/{options.Environment}/{options.Label}"); } @@ -110,8 +117,10 @@ public void GetConfigServerUri_WithLabelContainingSlash() Label = "myLabel/version" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); - string uri = provider.BuildConfigServerUri(new Uri(options.Uri!), options.Label).ToString(); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); + + string uri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri!), options.Label).ToString(); uri.Should().Be($"{options.Uri}/{options.Name}/{options.Environment}/myLabel(_)version"); } @@ -126,8 +135,10 @@ public void GetConfigServerUri_WithExtraPathInfo() Environment = "Production" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); - string uri = provider.BuildConfigServerUri(new Uri(options.Uri), null).ToString(); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); + + string uri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null).ToString(); uri.Should().Be($"http://localhost:9999/myPath/path/{options.Name}/{options.Environment}"); } @@ -142,8 +153,10 @@ public void GetConfigServerUri_WithExtraPathInfo_NoEndingSlash() Environment = "Production" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); - string uri = provider.BuildConfigServerUri(new Uri(options.Uri), null).ToString(); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); + + string uri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null).ToString(); uri.Should().Be($"http://localhost:9999/myPath/path/{options.Name}/{options.Environment}"); } @@ -158,8 +171,10 @@ public void GetConfigServerUri_NoEndingSlash() Environment = "Production" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); - string uri = provider.BuildConfigServerUri(new Uri(options.Uri), null).ToString(); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); + + string uri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null).ToString(); uri.Should().Be($"http://localhost:9999/{options.Name}/{options.Environment}"); } @@ -174,8 +189,10 @@ public void GetConfigServerUri_WithEndingSlash() Environment = "Production" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); - string uri = provider.BuildConfigServerUri(new Uri(options.Uri), null).ToString(); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); + + string uri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null).ToString(); uri.Should().Be($"http://localhost:9999/{options.Name}/{options.Environment}"); } @@ -190,8 +207,10 @@ public void GetConfigServerUri_MultipleEnvironments_EncodesComma() Label = "demo" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); - string uri = provider.BuildConfigServerUri(new Uri(options.Uri!), options.Label).ToString(); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); + + string uri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri!), options.Label).ToString(); uri.Should().Be("http://localhost:8888/myName/one%2Ctwo/demo"); } @@ -208,8 +227,10 @@ public void GetConfigServerUri_EncodesSpecialCharacters() Password = "#&:$<>'/so,\"me" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); - string uri = provider.BuildConfigServerUri(new Uri(options.Uri!), options.Label).ToString(); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); + + string uri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri!), options.Label).ToString(); uri.Should().Be( "http://a%22%3A%3F%27*%2Cb%2F%5C%26:%23%26%3A%24%3C%3E%27%2Fso%2C%22me@localhost:8888/my%24<>%3A%2C\"%27Name/%40%2F%26%3F%3A\"%27/^%26%24%40%23*<>%2B"); diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs index 1f13886305..5cccbdb791 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs @@ -65,9 +65,10 @@ public async Task Deserialize_GoodJson() public void GetLabels_Null() { var options = new ConfigServerClientOptions(); - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); - string[] result = provider.GetLabels(); + string[] result = provider.GetLabels(provider.ClientOptions); result.Should().ContainSingle().Which.Should().BeEmpty(); } @@ -79,9 +80,10 @@ public void GetLabels_Empty() Label = string.Empty }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); - string[] result = provider.GetLabels(); + string[] result = provider.GetLabels(provider.ClientOptions); result.Should().ContainSingle().Which.Should().BeEmpty(); } @@ -93,9 +95,10 @@ public void GetLabels_SingleString() Label = "foobar" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); - string[] result = provider.GetLabels(); + string[] result = provider.GetLabels(provider.ClientOptions); result.Should().ContainSingle().Which.Should().Be("foobar"); } @@ -107,9 +110,10 @@ public void GetLabels_MultiString() Label = "1,2,3," }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); - string[] result = provider.GetLabels(); + string[] result = provider.GetLabels(provider.ClientOptions); result.Should().HaveCount(3); result.Should().HaveElementAt(0, "1"); result.Should().HaveElementAt(1, "2"); @@ -124,9 +128,10 @@ public void GetLabels_MultiStringHoles() Label = "1,,2,3," }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); - string[] result = provider.GetLabels(); + string[] result = provider.GetLabels(provider.ClientOptions); result.Should().HaveCount(3); result.Should().HaveElementAt(0, "1"); result.Should().HaveElementAt(1, "2"); @@ -143,10 +148,11 @@ public async Task GetRequestMessage_AddsBasicAuthIfUserNameAndPasswordInURL() Environment = "development" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); - Uri requestUri = provider.BuildConfigServerUri(new Uri(options.Uri), null); - HttpRequestMessage request = await provider.GetRequestMessageAsync(requestUri, TestContext.Current.CancellationToken); + Uri requestUri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null); + HttpRequestMessage request = await provider.GetRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); request.Method.Should().Be(HttpMethod.Get); request.RequestUri.Should().Be(requestUri); @@ -167,10 +173,11 @@ public async Task GetRequestMessage_AddsBasicAuthIfUserNameAndPasswordInSettings Password = "password" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); - Uri requestUri = provider.BuildConfigServerUri(new Uri(options.Uri), null); - HttpRequestMessage request = await provider.GetRequestMessageAsync(requestUri, TestContext.Current.CancellationToken); + Uri requestUri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null); + HttpRequestMessage request = await provider.GetRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); request.Method.Should().Be(HttpMethod.Get); request.RequestUri.Should().Be(requestUri); @@ -191,10 +198,11 @@ public async Task GetRequestMessage_BasicAuthInSettingsOverridesUserNameAndPassw Password = "password" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); - Uri requestUri = provider.BuildConfigServerUri(new Uri(options.Uri), null); - HttpRequestMessage request = await provider.GetRequestMessageAsync(requestUri, TestContext.Current.CancellationToken); + Uri requestUri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null); + HttpRequestMessage request = await provider.GetRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); request.Method.Should().Be(HttpMethod.Get); request.RequestUri.Should().Be(requestUri); @@ -213,10 +221,11 @@ public async Task GetRequestMessage_AddsVaultToken_IfNeeded() Token = "MyVaultToken" }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); - Uri requestUri = provider.BuildConfigServerUri(new Uri(options.Uri!), null); - HttpRequestMessage request = await provider.GetRequestMessageAsync(requestUri, TestContext.Current.CancellationToken); + Uri requestUri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri!), null); + HttpRequestMessage request = await provider.GetRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); request.Method.Should().Be(HttpMethod.Get); request.RequestUri.Should().Be(requestUri); @@ -225,6 +234,41 @@ public async Task GetRequestMessage_AddsVaultToken_IfNeeded() headerValues.Should().Contain("MyVaultToken"); } + [Fact] + public async Task GetRequestMessage_AddsBearerToken_WhenAccessTokenUriIsSet() + { + var options = new ConfigServerClientOptions + { + Uri = "http://localhost:8888/", + Name = "foo", + Environment = "development", + AccessTokenUri = "https://auth.server.com", + ClientId = "test-client-id", + ClientSecret = "test-client-secret" + }; + + using var handler = new DelegateToMockHttpClientHandler(); + + handler.Mock.Expect(HttpMethod.Post, "https://auth.server.com/").WithHeaders("Authorization", "Basic dGVzdC1jbGllbnQtaWQ6dGVzdC1jbGllbnQtc2VjcmV0") + .WithFormData("grant_type=client_credentials").Respond("application/json", """ + { + "access_token": "my-bearer-token" + } + """); + + // ReSharper disable once AccessToDisposedClosure + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); + + Uri requestUri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null); + HttpRequestMessage request = await provider.GetRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); + + handler.Mock.VerifyNoOutstandingExpectation(); + + request.Headers.Authorization.Should().NotBeNull(); + request.Headers.Authorization.Scheme.Should().Be("Bearer"); + request.Headers.Authorization.Parameter.Should().Be("my-bearer-token"); + } + [Fact] public async Task RefreshVaultToken_Succeeds() { @@ -240,8 +284,11 @@ public async Task RefreshVaultToken_Succeeds() handler.Mock.Expect(HttpMethod.Post, "http://localhost:8888/vault/v1/auth/token/renew-self").WithHeaders("X-Vault-Token", "MyVaultToken") .WithContent("{\"increment\":300}").Respond(HttpStatusCode.NoContent); - using var provider = new ConfigServerConfigurationProvider(options, null, handler, NullLoggerFactory.Instance); - await provider.RefreshVaultTokenAsync(TestContext.Current.CancellationToken); + // ReSharper disable once AccessToDisposedClosure + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); + provider.Load(); + + await provider.RefreshVaultTokenAsync(provider.ClientOptions, TestContext.Current.CancellationToken); handler.Mock.VerifyNoOutstandingExpectation(); } @@ -267,8 +314,10 @@ public async Task RefreshVaultToken_With_AccessTokenUri_Succeeds() handler.Mock.Expect(HttpMethod.Post, "http://localhost:8888/vault/v1/auth/token/renew-self").WithHeaders("X-Vault-Token", "MyVaultToken") .WithHeaders("Authorization", "Bearer secret").WithContent("{\"increment\":300}").Respond(HttpStatusCode.NoContent); - using var provider = new ConfigServerConfigurationProvider(options, null, handler, NullLoggerFactory.Instance); - await provider.RefreshVaultTokenAsync(TestContext.Current.CancellationToken); + // ReSharper disable once AccessToDisposedClosure + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); + + await provider.RefreshVaultTokenAsync(provider.ClientOptions, TestContext.Current.CancellationToken); handler.Mock.VerifyNoOutstandingExpectation(); } @@ -287,8 +336,10 @@ public void GetHttpClient_AddsHeaders_IfConfigured() } }; - using var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance); - using HttpClient httpClient = provider.CreateHttpClient(options); + using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + provider.Load(); + + using HttpClient httpClient = provider.CreateHttpClient(provider.ClientOptions); httpClient.Should().NotBeNull(); httpClient.DefaultRequestHeaders.GetValues("foo").SingleOrDefault().Should().Be("bar"); @@ -308,42 +359,42 @@ public void IsDiscoveryFirstEnabled_ReturnsExpected() } }; - using (var provider = new ConfigServerConfigurationProvider(options, null, null, NullLoggerFactory.Instance)) + using (var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance)) { - provider.IsDiscoveryFirstEnabled().Should().BeTrue(); + provider.Load(); + provider.ClientOptions.Discovery.Enabled.Should().BeTrue(); } - var values = new Dictionary + var appSettings = new Dictionary { ["spring:cloud:config:discovery:enabled"] = "True" }; - IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(values).Build(); - options = new ConfigServerClientOptions { Name = "foo", Environment = "development" }; - var source = new ConfigServerConfigurationSource(options, configuration, NullLoggerFactory.Instance); + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(appSettings); + configurationBuilder.AddConfigServer(options); + IConfigurationRoot configuration = configurationBuilder.Build(); - using (var provider = new ConfigServerConfigurationProvider(source, NullLoggerFactory.Instance)) + using (ConfigServerConfigurationProvider provider = configuration.EnumerateProviders().Single()) { - provider.IsDiscoveryFirstEnabled().Should().BeTrue(); + provider.ClientOptions.Discovery.Enabled.Should().BeTrue(); } } [Fact] public void UpdateSettingsFromDiscovery_UpdatesSettingsCorrectly() { - var values = new Dictionary + var appSettings = new Dictionary { ["spring:cloud:config:discovery:enabled"] = "True" }; - IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(values).Build(); - var options = new ConfigServerClientOptions { Uri = "http://localhost:8888/", @@ -351,13 +402,19 @@ public void UpdateSettingsFromDiscovery_UpdatesSettingsCorrectly() Environment = "development" }; - var source = new ConfigServerConfigurationSource(options, configuration, NullLoggerFactory.Instance); - using var provider = new ConfigServerConfigurationProvider(source, NullLoggerFactory.Instance); + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(appSettings); + configurationBuilder.AddConfigServer(options); + IConfigurationRoot configuration = configurationBuilder.Build(); - provider.UpdateSettingsFromDiscovery(new List(), options); - options.Username.Should().BeNull(); - options.Password.Should().BeNull(); - options.Uri.Should().Be("http://localhost:8888/"); + using ConfigServerConfigurationProvider provider = configuration.EnumerateProviders().Single(); + + ConfigServerClientOptions optionsSnapshot = provider.ClientOptions; + provider.SetLastDiscoveryLookupResult(new List()); + provider.ApplyLastDiscoveryLookupResultToClientOptions(optionsSnapshot); + optionsSnapshot.Username.Should().BeNull(); + optionsSnapshot.Password.Should().BeNull(); + optionsSnapshot.Uri.Should().Be("http://localhost:8888/"); var metadata1 = new Dictionary { @@ -377,24 +434,24 @@ public void UpdateSettingsFromDiscovery_UpdatesSettingsCorrectly() new TestServiceInstance("s", "i2", new Uri("https://foo.bar.baz:9999/"), metadata2) ]; - provider.UpdateSettingsFromDiscovery(instances, options); - options.Username.Should().Be("secondUser"); - options.Password.Should().Be("secondPassword"); - options.Uri.Should().Be("https://foo.bar:8888/,https://foo.bar.baz:9999/configPath"); + optionsSnapshot = provider.ClientOptions; + provider.SetLastDiscoveryLookupResult(instances); + provider.ApplyLastDiscoveryLookupResultToClientOptions(optionsSnapshot); + optionsSnapshot.Username.Should().Be("secondUser"); + optionsSnapshot.Password.Should().Be("secondPassword"); + optionsSnapshot.Uri.Should().Be("https://foo.bar:8888/,https://foo.bar.baz:9999/configPath"); } [Fact] - public async Task DiscoverServerInstances_FailsFast() + public void DiscoverServerInstances_FailsFast() { - var values = new Dictionary + var appSettings = new Dictionary { ["spring:cloud:config:discovery:enabled"] = "True", ["spring:cloud:config:failFast"] = "True", ["eureka:client:eurekaServer:retryCount"] = "0" }; - IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(values).Build(); - var options = new ConfigServerClientOptions { Name = "foo", @@ -402,30 +459,17 @@ public async Task DiscoverServerInstances_FailsFast() Timeout = 10 }; - var source = new ConfigServerConfigurationSource(options, configuration, NullLoggerFactory.Instance); - using var provider = new ConfigServerConfigurationProvider(source, NullLoggerFactory.Instance); + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(appSettings); + configurationBuilder.AddConfigServer(options); - // ReSharper disable once AccessToDisposedClosure - Func action = async () => await provider.LoadInternalAsync(true, TestContext.Current.CancellationToken); + Action action = () => _ = configurationBuilder.Build(); - await action.Should().ThrowExactlyAsync().WithMessage("Could not locate Config Server via discovery*"); + action.Should().ThrowExactly().WithMessage("Could not locate Config Server via discovery*"); } private static string GetEncodedUserPassword(string user, string password) { return Convert.ToBase64String(Encoding.ASCII.GetBytes($"{user}:{password}")); } - - private sealed class TestServiceInstance(string serviceId, string instanceId, Uri uri, IReadOnlyDictionary metadata) : IServiceInstance - { - public string ServiceId { get; } = serviceId; - public string InstanceId { get; } = instanceId; - public string Host { get; } = uri.Host; - public int Port { get; } = uri.Port; - public bool IsSecure { get; } = uri.Scheme == Uri.UriSchemeHttps; - public Uri Uri { get; } = uri; - public Uri? NonSecureUri => IsSecure ? null : Uri; - public Uri? SecureUri => IsSecure ? Uri : null; - public IReadOnlyDictionary Metadata { get; } = metadata; - } } diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationSourceTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationSourceTest.cs index 1be77f49a0..a66b415c5c 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationSourceTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationSourceTest.cs @@ -21,7 +21,7 @@ public void Constructors_InitializesProperties() var source = new ConfigServerConfigurationSource(options, sources, new Dictionary { ["foo"] = "bar" - }, NullLoggerFactory.Instance); + }, null, null, NullLoggerFactory.Instance); source.DefaultOptions.Should().Be(options); source.Configuration.Should().BeNull(); @@ -29,14 +29,6 @@ public void Constructors_InitializesProperties() source.Sources.Should().ContainSingle().Which.Should().Be(memSource); source.Properties.Should().ContainSingle(); source.Properties.Should().ContainKey("foo").WhoseValue.Should().Be("bar"); - - IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection().Build(); - source = new ConfigServerConfigurationSource(options, configurationRoot, NullLoggerFactory.Instance); - source.DefaultOptions.Should().Be(options); - - ConfigurationRoot? root = source.Configuration.Should().BeOfType().Subject; - - root.Should().BeSameAs(configurationRoot); } [Fact] @@ -46,7 +38,7 @@ public void Build_ReturnsProvider() var memSource = new MemoryConfigurationSource(); List sources = [memSource]; - var source = new ConfigServerConfigurationSource(options, sources, null, NullLoggerFactory.Instance); + var source = new ConfigServerConfigurationSource(options, sources, null, null, null, NullLoggerFactory.Instance); IConfigurationProvider provider = source.Build(new ConfigurationBuilder()); provider.Should().BeOfType(); diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerDiscoveryServiceTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerDiscoveryServiceTest.cs index 55d238660e..647f657180 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerDiscoveryServiceTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerDiscoveryServiceTest.cs @@ -11,12 +11,12 @@ namespace Steeltoe.Configuration.ConfigServer.Test; public sealed class ConfigServerDiscoveryServiceTest { [Fact] - public void ConfigServerDiscoveryService_FindsNoDiscoveryClients() + public async Task ConfigServerDiscoveryService_FindsNoDiscoveryClients() { IConfiguration configuration = new ConfigurationBuilder().Add(FastTestConfigurations.ConfigServer).Build(); - var options = new ConfigServerClientOptions(); - var service = new ConfigServerDiscoveryService(configuration, options, NullLoggerFactory.Instance); + var service = new ConfigServerDiscoveryService(configuration, NullLoggerFactory.Instance); + await service.GetConfigServerInstancesAsync(new ConfigServerClientOptions(), TestContext.Current.CancellationToken); service.DiscoveryClients.Should().BeEmpty(); } diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerHealthContributorTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerHealthContributorTest.cs index f0a408d663..8d6f046141 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerHealthContributorTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerHealthContributorTest.cs @@ -27,7 +27,6 @@ public void Constructor_FindsConfigServerProviderInsidePlaceholderProvider() builder.AddInMemoryCollection(values); builder.AddConfigServer(); builder.AddPlaceholderResolver(); - IConfigurationRoot configurationRoot = builder.Build(); var contributor = new ConfigServerHealthContributor(configurationRoot, TimeProvider.System, NullLogger.Instance); @@ -48,57 +47,12 @@ public void FindProvider_FindsProvider() var builder = new ConfigurationBuilder(); builder.AddInMemoryCollection(values); builder.AddConfigServer(); - IConfigurationRoot configurationRoot = builder.Build(); var contributor = new ConfigServerHealthContributor(configurationRoot, TimeProvider.System, NullLogger.Instance); contributor.Provider.Should().NotBeNull(); } - [Fact] - public void GetTimeToLive_ReturnsExpected() - { - var values = new Dictionary(TestSettingsFactory.Get(FastTestConfigurations.ConfigServer)) - { - ["spring:cloud:config:uri"] = "http://localhost:8888/", - ["spring:cloud:config:name"] = "myName", - ["spring:cloud:config:label"] = "myLabel", - ["spring:cloud:config:health:timeToLive"] = "100000", - ["spring:cloud:config:timeout"] = "10" - }; - - var builder = new ConfigurationBuilder(); - builder.AddInMemoryCollection(values); - builder.AddConfigServer(); - - IConfigurationRoot configurationRoot = builder.Build(); - - var contributor = new ConfigServerHealthContributor(configurationRoot, TimeProvider.System, NullLogger.Instance); - contributor.GetTimeToLive().Should().Be(100_000); - } - - [Fact] - public void IsEnabled_ReturnsExpected() - { - var values = new Dictionary(TestSettingsFactory.Get(FastTestConfigurations.ConfigServer)) - { - ["spring:cloud:config:uri"] = "http://localhost:8888/", - ["spring:cloud:config:name"] = "myName", - ["spring:cloud:config:label"] = "myLabel", - ["spring:cloud:config:health:enabled"] = "true", - ["spring:cloud:config:timeout"] = "10" - }; - - var builder = new ConfigurationBuilder(); - builder.AddInMemoryCollection(values); - builder.AddConfigServer(); - - IConfigurationRoot configurationRoot = builder.Build(); - - var contributor = new ConfigServerHealthContributor(configurationRoot, TimeProvider.System, NullLogger.Instance); - contributor.IsEnabled().Should().BeTrue(); - } - [Fact] public void IsCacheStale_ReturnsExpected() { @@ -115,15 +69,16 @@ public void IsCacheStale_ReturnsExpected() var builder = new ConfigurationBuilder(); builder.AddInMemoryCollection(values); builder.AddConfigServer(); - IConfigurationRoot configurationRoot = builder.Build(); var contributor = new ConfigServerHealthContributor(configurationRoot, TimeProvider.System, NullLogger.Instance); - contributor.IsCacheStale(0).Should().BeTrue(); // No cache established yet + ConfigServerClientOptions optionsSnapshot = contributor.Provider!.ClientOptions; + + contributor.IsCacheStale(0, optionsSnapshot).Should().BeTrue(); // No cache established yet contributor.Cached = new ConfigEnvironment(); contributor.LastAccess = 9; - contributor.IsCacheStale(10).Should().BeTrue(); - contributor.IsCacheStale(8).Should().BeFalse(); + contributor.IsCacheStale(10, optionsSnapshot).Should().BeTrue(); + contributor.IsCacheStale(8, optionsSnapshot).Should().BeFalse(); } [Fact] @@ -143,7 +98,6 @@ public async Task GetPropertySources_ReturnsExpected() var builder = new ConfigurationBuilder(); builder.AddInMemoryCollection(values); builder.AddConfigServer(); - IConfigurationRoot configurationRoot = builder.Build(); var contributor = new ConfigServerHealthContributor(configurationRoot, TimeProvider.System, NullLogger.Instance) @@ -152,7 +106,9 @@ public async Task GetPropertySources_ReturnsExpected() }; long lastAccess = contributor.LastAccess = DateTimeOffset.Now.ToUnixTimeMilliseconds() - 100; - IList? sources = await contributor.GetPropertySourcesAsync(contributor.Provider!, TestContext.Current.CancellationToken); + + IList? sources = + await contributor.GetPropertySourcesAsync(contributor.Provider!, contributor.Provider!.ClientOptions, TestContext.Current.CancellationToken); contributor.LastAccess.Should().NotBe(lastAccess); sources.Should().BeNull(); diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerHostBuilderExtensionsTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerHostBuilderExtensionsTest.cs index 776dcc8631..ad06440291 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerHostBuilderExtensionsTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerHostBuilderExtensionsTest.cs @@ -102,8 +102,8 @@ public void AddConfigServer_HostBuilder_DisposesTimer() provider = configurationRoot.EnumerateProviders().Single(); } - FieldInfo refreshTimerField = provider.GetType().GetField("_refreshTimer", BindingFlags.NonPublic | BindingFlags.Instance)!; - refreshTimerField.GetValue(provider).Should().BeNull(); + FieldInfo reloadTimerField = provider.GetType().GetField("_configurationReloadTimer", BindingFlags.NonPublic | BindingFlags.Instance)!; + reloadTimerField.GetValue(provider).Should().BeNull(); } [Fact] @@ -114,20 +114,20 @@ public void AddConfigServer_WebHostBuilder_DisposesTimer() ["spring:cloud:config:pollingInterval"] = 1.Seconds().ToString() }; - WebHostBuilder builder = TestWebHostBuilderFactory.Create(); - builder.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); - builder.AddConfigServer(); + WebHostBuilder hostBuilder = TestWebHostBuilderFactory.Create(); + hostBuilder.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); + hostBuilder.AddConfigServer(); ConfigServerConfigurationProvider provider; - using (IWebHost webHost = builder.Build()) + using (IWebHost webHost = hostBuilder.Build()) { var configurationRoot = (IConfigurationRoot)webHost.Services.GetRequiredService(); provider = configurationRoot.EnumerateProviders().Single(); } - FieldInfo refreshTimerField = provider.GetType().GetField("_refreshTimer", BindingFlags.NonPublic | BindingFlags.Instance)!; - refreshTimerField.GetValue(provider).Should().BeNull(); + FieldInfo reloadTimerField = provider.GetType().GetField("_configurationReloadTimer", BindingFlags.NonPublic | BindingFlags.Instance)!; + reloadTimerField.GetValue(provider).Should().BeNull(); } [Fact] @@ -150,8 +150,8 @@ public async Task AddConfigServer_WebApplicationBuilder_DisposesTimer() _ = host.Services.GetRequiredService(); } - FieldInfo refreshTimerField = provider.GetType().GetField("_refreshTimer", BindingFlags.NonPublic | BindingFlags.Instance)!; - refreshTimerField.GetValue(provider).Should().BeNull(); + FieldInfo reloadTimerField = provider.GetType().GetField("_configurationReloadTimer", BindingFlags.NonPublic | BindingFlags.Instance)!; + reloadTimerField.GetValue(provider).Should().BeNull(); } [Fact] @@ -174,8 +174,8 @@ public void AddConfigServer_HostApplicationBuilder_DisposesTimer() _ = host.Services.GetRequiredService(); } - FieldInfo refreshTimerField = provider.GetType().GetField("_refreshTimer", BindingFlags.NonPublic | BindingFlags.Instance)!; - refreshTimerField.GetValue(provider).Should().BeNull(); + FieldInfo reloadTimerField = provider.GetType().GetField("_configurationReloadTimer", BindingFlags.NonPublic | BindingFlags.Instance)!; + reloadTimerField.GetValue(provider).Should().BeNull(); } [Fact] diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerHostedServiceTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerHostedServiceTest.cs index ac140148b3..11de4911de 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerHostedServiceTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerHostedServiceTest.cs @@ -18,7 +18,9 @@ public async Task ServiceConstructsAndOperatesWithConfigurationRoot() var provider = new ConfigServerConfigurationProvider(new ConfigServerClientOptions { Enabled = false - }, null, null, NullLoggerFactory.Instance); + }, null, null, null, NullLoggerFactory.Instance); + + provider.Load(); var configurationRoot = new ConfigurationRoot([provider]); var service = new ConfigServerHostedService(configurationRoot, []); diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerServiceCollectionExtensionsTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerServiceCollectionExtensionsTest.cs index 05fe12d3a4..9bf40b414a 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerServiceCollectionExtensionsTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerServiceCollectionExtensionsTest.cs @@ -2,10 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using System.Reflection; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using Steeltoe.Configuration.Encryption; using Steeltoe.Configuration.Placeholder; @@ -13,24 +10,6 @@ namespace Steeltoe.Configuration.ConfigServer.Test; public sealed class ConfigServerServiceCollectionExtensionsTest { - [Fact] - public async Task ConfigureConfigServerClientOptions_ConfiguresConfigServerClientOptions_WithDefaults() - { - var builder = new ConfigurationBuilder(); - builder.AddConfigServer(); - IConfiguration configuration = builder.Build(); - - var services = new ServiceCollection(); - services.AddSingleton(configuration); - services.ConfigureConfigServerClientOptions(); - - await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - var optionsMonitor = serviceProvider.GetRequiredService>(); - - string? expectedAppName = Assembly.GetEntryAssembly()!.GetName().Name; - TestHelper.VerifyDefaults(optionsMonitor.CurrentValue, expectedAppName); - } - [Fact] public void DoesNotAddConfigServerMultipleTimes() { diff --git a/src/Configuration/test/ConfigServer.Test/ForwardingHttpClientHandler.cs b/src/Configuration/test/ConfigServer.Test/ForwardingHttpClientHandler.cs deleted file mode 100644 index 27100c24a7..0000000000 --- a/src/Configuration/test/ConfigServer.Test/ForwardingHttpClientHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -namespace Steeltoe.Configuration.ConfigServer.Test; - -internal sealed class ForwardingHttpClientHandler : HttpClientHandler -{ - private readonly HttpMessageInvoker _invoker; - - public ForwardingHttpClientHandler(HttpMessageHandler innerHandler) - { - ArgumentNullException.ThrowIfNull(innerHandler); - - _invoker = new HttpMessageInvoker(innerHandler, false); - } - - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - return _invoker.SendAsync(request, cancellationToken); - } -} diff --git a/src/Configuration/test/ConfigServer.Test/TestConfigServerConfigurationSource.cs b/src/Configuration/test/ConfigServer.Test/TestConfigServerConfigurationSource.cs deleted file mode 100644 index 23c99470bf..0000000000 --- a/src/Configuration/test/ConfigServer.Test/TestConfigServerConfigurationSource.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Configuration; - -namespace Steeltoe.Configuration.ConfigServer.Test; - -internal sealed class TestConfigServerConfigurationSource(IConfigurationProvider provider) : IConfigurationSource -{ - private readonly IConfigurationProvider _provider = provider; - - public IConfigurationProvider Build(IConfigurationBuilder builder) - { - return _provider; - } -} diff --git a/src/Configuration/test/ConfigServer.Test/TestConfigServerStartup.cs b/src/Configuration/test/ConfigServer.Test/TestConfigServerStartup.cs deleted file mode 100644 index e3947d75d1..0000000000 --- a/src/Configuration/test/ConfigServer.Test/TestConfigServerStartup.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; - -namespace Steeltoe.Configuration.ConfigServer.Test; - -internal sealed class TestConfigServerStartup : IDisposable -{ - private volatile CountdownEvent _firstRequestCountdownEvent = new(1); - private volatile string? _response; - private volatile int[] _returnStatus = [200]; - private volatile HttpRequestInfo? _lastRequest; - private volatile int _requestCount; - private volatile string _label = string.Empty; - private volatile string _appName = string.Empty; - private volatile string _env = string.Empty; - - public string? Response - { - get => _response; - set => _response = value; - } - - public int[] ReturnStatus - { - get => _returnStatus; - set => _returnStatus = value; - } - - public HttpRequestInfo? LastRequest - { - get => _lastRequest; - set => _lastRequest = value; - } - - public int RequestCount - { - get => _requestCount; - set => _requestCount = value; - } - - public string Label - { - get => _label; - set => _label = value; - } - - public string AppName - { - get => _appName; - set => _appName = value; - } - - public string Env - { - get => _env; - set => _env = value; - } - - public void Reset() - { - Response = null; - ReturnStatus = [200]; - LastRequest = null; - RequestCount = 0; - Label = AppName = Env = string.Empty; - - _firstRequestCountdownEvent.Dispose(); - _firstRequestCountdownEvent = new CountdownEvent(1); - } - - public void Configure(IApplicationBuilder app) - { - app.Run(async context => - { - LastRequest = await HttpRequestInfo.CopyFromAsync(context); - context.Response.StatusCode = GetStatusCode(context.Request.Path); - RequestCount++; - - if (context.Response.StatusCode == 200) - { - context.Response.Headers.Append("content-type", "application/json"); - - if (Response != null) - { - await context.Response.WriteAsync(Response, context.RequestAborted); - } - } - - if (RequestCount == 1) - { - _firstRequestCountdownEvent.Signal(); - } - }); - } - - private int GetStatusCode(string path) - { - if (!string.IsNullOrEmpty(Label) && !path.Contains(Label, StringComparison.Ordinal)) - { - return 404; - } - - if (!string.IsNullOrEmpty(Env) && !path.Contains(Env, StringComparison.Ordinal)) - { - return 404; - } - - if (!string.IsNullOrEmpty(AppName) && !path.Contains(AppName, StringComparison.Ordinal)) - { - return 404; - } - - return ReturnStatus[RequestCount]; - } - - public bool WaitForFirstRequest(TimeSpan timeout) - { - return _firstRequestCountdownEvent.Wait(timeout, TestContext.Current.CancellationToken); - } - - public void Dispose() - { - _firstRequestCountdownEvent.Dispose(); - } -} diff --git a/src/Configuration/test/ConfigServer.Test/TestHelper.cs b/src/Configuration/test/ConfigServer.Test/TestHelper.cs index 57db7c8e6f..6fd0ff93ac 100644 --- a/src/Configuration/test/ConfigServer.Test/TestHelper.cs +++ b/src/Configuration/test/ConfigServer.Test/TestHelper.cs @@ -6,12 +6,12 @@ namespace Steeltoe.Configuration.ConfigServer.Test; internal static class TestHelper { - public static void VerifyDefaults(ConfigServerClientOptions options, string? expectedAppName) + public static void VerifyDefaults(ConfigServerClientOptions options, string? expectedAppName, string? expectedEnvironment) { options.Enabled.Should().BeTrue(); options.FailFast.Should().BeFalse(); options.Uri.Should().Be("http://localhost:8888"); - options.Environment.Should().Be("Production"); + options.Environment.Should().Be(expectedEnvironment); options.AccessTokenUri.Should().BeNull(); options.ClientId.Should().BeNull(); options.ClientSecret.Should().BeNull(); diff --git a/src/Configuration/test/ConfigServer.Test/TestServiceInstance.cs b/src/Configuration/test/ConfigServer.Test/TestServiceInstance.cs new file mode 100644 index 0000000000..91167ccba5 --- /dev/null +++ b/src/Configuration/test/ConfigServer.Test/TestServiceInstance.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using Steeltoe.Common.Discovery; + +namespace Steeltoe.Configuration.ConfigServer.Test; + +internal sealed class TestServiceInstance(string serviceId, string instanceId, Uri uri, IReadOnlyDictionary metadata) : IServiceInstance +{ + public string ServiceId { get; } = serviceId; + public string InstanceId { get; } = instanceId; + public string Host { get; } = uri.Host; + public int Port { get; } = uri.Port; + public bool IsSecure { get; } = uri.Scheme == Uri.UriSchemeHttps; + public Uri Uri { get; } = uri; + public Uri? NonSecureUri => IsSecure ? null : Uri; + public Uri? SecureUri => IsSecure ? Uri : null; + public IReadOnlyDictionary Metadata { get; } = metadata; +} diff --git a/src/Configuration/test/Placeholder.Test/PlaceholderConfigurationTest.cs b/src/Configuration/test/Placeholder.Test/PlaceholderConfigurationTest.cs index 7869c46492..f9f74b68a2 100644 --- a/src/Configuration/test/Placeholder.Test/PlaceholderConfigurationTest.cs +++ b/src/Configuration/test/Placeholder.Test/PlaceholderConfigurationTest.cs @@ -336,16 +336,16 @@ public void Reloads_options_on_change(int placeholderCount) { MemoryFileProvider fileProvider = new(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "TestRoot": { - "Value": "valueA" - } - } - """); + fileProvider.IncludeAppSettingsJsonFile(""" + { + "TestRoot": { + "Value": "valueA" + } + } + """); var builder = new ConfigurationBuilder(); - builder.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.AddInMemoryAppSettingsJsonFile(fileProvider); #pragma warning disable SA1312 // Variable names should begin with lower-case letter foreach (int _ in Enumerable.Repeat(0, placeholderCount)) @@ -375,13 +375,13 @@ public void Reloads_options_on_change(int placeholderCount) _ = optionsMonitor.CurrentValue; configurer.ConfigureCount.Should().Be(1); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "TestRoot": { - "Value": "valueB" - } - } - """); + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "TestRoot": { + "Value": "valueB" + } + } + """); fileProvider.NotifyChanged(); diff --git a/src/Configuration/test/Placeholder.Test/PlaceholderWebApplicationTest.cs b/src/Configuration/test/Placeholder.Test/PlaceholderWebApplicationTest.cs index 544688e850..3dcb81fb43 100644 --- a/src/Configuration/test/Placeholder.Test/PlaceholderWebApplicationTest.cs +++ b/src/Configuration/test/Placeholder.Test/PlaceholderWebApplicationTest.cs @@ -2,14 +2,12 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using FluentAssertions.Extensions; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Steeltoe.Common.TestResources; -using Steeltoe.Common.TestResources.IO; namespace Steeltoe.Configuration.Placeholder.Test; @@ -26,16 +24,15 @@ public PlaceholderWebApplicationTest(ITestOutputHelper testOutputHelper) [Fact] public async Task Reloads_options_on_change() { - const string appSettings = """ + var fileProvider = new MemoryFileProvider(); + + fileProvider.IncludeAppSettingsJsonFile(""" { "TestRoot": { "AppName": "AppOne" } } - """; - - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); + """); var memorySettings = new Dictionary { @@ -44,9 +41,8 @@ public async Task Reloads_options_on_change() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Services.AddSingleton(_loggerFactory); - builder.Configuration.SetBasePath(sandbox.FullPath); builder.Configuration.AddInMemoryCollection(memorySettings); - builder.Configuration.AddJsonFile(MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Configuration.AddPlaceholderResolver(_loggerFactory); builder.Services.Configure(builder.Configuration.GetSection("TestRoot")); builder.Services.AddSingleton, ConfigureTestOptions>(); @@ -55,27 +51,27 @@ public async Task Reloads_options_on_change() var optionsMonitor = app.Services.GetRequiredService>(); optionsMonitor.CurrentValue.Value.Should().Be("AppOne"); - await File.WriteAllTextAsync(path, """ - { - "TestRoot": { - "AppName": "AppTwo" - } - } - """, TestContext.Current.CancellationToken); + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "TestRoot": { + "AppName": "AppTwo" + } + } + """); - await Task.Delay(2.Seconds(), TestContext.Current.CancellationToken); + fileProvider.NotifyChanged(); optionsMonitor.CurrentValue.Value.Should().Be("AppTwo"); - await File.WriteAllTextAsync(path, """ - { - "TestRoot": { - "AppName": "AppThree" - } - } - """, TestContext.Current.CancellationToken); + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "TestRoot": { + "AppName": "AppThree" + } + } + """); - await Task.Delay(2.Seconds(), TestContext.Current.CancellationToken); + fileProvider.NotifyChanged(); optionsMonitor.CurrentValue.Value.Should().Be("AppThree"); } @@ -121,11 +117,9 @@ public void Can_rebuild_sources() [Fact] public async Task Can_substitute_across_multiple_sources() { - const string appSettingsJsonFileName = "appsettings.json"; - const string appSettingsXmlFileName = "appsettings.xml"; - const string appSettingsIniFileName = "appsettings.ini"; + var fileProvider = new MemoryFileProvider(); - const string appSettingsJsonContent = """ + fileProvider.IncludeAppSettingsJsonFile(""" { "JsonTestRoot": { "JsonSubLevel": { @@ -134,9 +128,9 @@ public async Task Can_substitute_across_multiple_sources() } } } - """; + """); - const string appSettingsXmlContent = """ + fileProvider.IncludeAppSettingsXmlFile(""" @@ -145,13 +139,13 @@ public async Task Can_substitute_across_multiple_sources() - """; + """); - const string appSettingsIniContent = """ + fileProvider.IncludeAppSettingsIniFile(""" [IniTestRoot:IniSubLevel] IniKey=IniValue CmdSource=IniTo${CmdTestRoot:CmdSubLevel:CmdKey} - """; + """); string[] appSettingsCommandLine = [ @@ -159,16 +153,10 @@ public async Task Can_substitute_across_multiple_sources() "--CmdTestRoot:CmdSubLevel:JsonSource=CmdTo${JsonTestRoot:JsonSubLevel:JsonKey}" ]; - using var sandbox = new Sandbox(); - sandbox.CreateFile(appSettingsJsonFileName, appSettingsJsonContent); - sandbox.CreateFile(appSettingsXmlFileName, appSettingsXmlContent); - sandbox.CreateFile(appSettingsIniFileName, appSettingsIniContent); - WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.SetBasePath(sandbox.FullPath); - builder.Configuration.AddJsonFile(appSettingsJsonFileName); - builder.Configuration.AddXmlFile(appSettingsXmlFileName); - builder.Configuration.AddIniFile(appSettingsIniFileName); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); + builder.Configuration.AddInMemoryAppSettingsXmlFile(fileProvider); + builder.Configuration.AddInMemoryAppSettingsIniFile(fileProvider); builder.Configuration.AddCommandLine(appSettingsCommandLine); builder.Configuration.AddPlaceholderResolver(_loggerFactory); diff --git a/src/Connectors/test/Connectors.Test/ConfigurationChangeDetectionTest.cs b/src/Connectors/test/Connectors.Test/ConfigurationChangeDetectionTest.cs index 9d9aca9d3e..c16d6df3bb 100644 --- a/src/Connectors/test/Connectors.Test/ConfigurationChangeDetectionTest.cs +++ b/src/Connectors/test/Connectors.Test/ConfigurationChangeDetectionTest.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Npgsql; @@ -35,9 +34,9 @@ public async Task Applies_local_configuration_changes_using_WebApplicationBuilde """; var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, fileContents); + fileProvider.IncludeAppSettingsJsonFile(fileContents); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.AddPostgreSql(configureOptions => configureOptions.DetectConfigurationChanges = true, null); await using WebApplication app = builder.Build(); @@ -63,7 +62,7 @@ public async Task Applies_local_configuration_changes_using_WebApplicationBuilde } """; - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, fileContents); + fileProvider.ReplaceAppSettingsJsonFile(fileContents); fileProvider.NotifyChanged(); connectionString = connectorFactory.Get("examplePostgreSqlService").Options.ConnectionString; @@ -83,7 +82,7 @@ public async Task Applies_local_configuration_changes_using_WebApplicationBuilde } """; - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, fileContents); + fileProvider.ReplaceAppSettingsJsonFile(fileContents); fileProvider.NotifyChanged(); connectionString = connectorFactory.Get("examplePostgreSqlService").Options.ConnectionString; @@ -110,11 +109,11 @@ public void Applies_local_configuration_changes_using_WebHostBuilder() """; var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, fileContents); + fileProvider.IncludeAppSettingsJsonFile(fileContents); builder.ConfigureAppConfiguration(configurationBuilder => { - configurationBuilder.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); configurationBuilder.ConfigurePostgreSql(options => options.DetectConfigurationChanges = true); }); @@ -142,7 +141,7 @@ public void Applies_local_configuration_changes_using_WebHostBuilder() } """; - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, fileContents); + fileProvider.ReplaceAppSettingsJsonFile(fileContents); fileProvider.NotifyChanged(); connectionString = connectorFactory.Get("examplePostgreSqlService").Options.ConnectionString; @@ -162,7 +161,7 @@ public void Applies_local_configuration_changes_using_WebHostBuilder() } """; - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, fileContents); + fileProvider.ReplaceAppSettingsJsonFile(fileContents); fileProvider.NotifyChanged(); connectionString = connectorFactory.Get("examplePostgreSqlService").Options.ConnectionString; @@ -189,11 +188,11 @@ public void Applies_local_configuration_changes_using_HostBuilder() """; var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, fileContents); + fileProvider.IncludeAppSettingsJsonFile(fileContents); builder.ConfigureAppConfiguration(configurationBuilder => { - configurationBuilder.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); configurationBuilder.ConfigurePostgreSql(options => options.DetectConfigurationChanges = true); }); @@ -221,7 +220,7 @@ public void Applies_local_configuration_changes_using_HostBuilder() } """; - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, fileContents); + fileProvider.ReplaceAppSettingsJsonFile(fileContents); fileProvider.NotifyChanged(); connectionString = connectorFactory.Get("examplePostgreSqlService").Options.ConnectionString; @@ -241,7 +240,7 @@ public void Applies_local_configuration_changes_using_HostBuilder() } """; - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, fileContents); + fileProvider.ReplaceAppSettingsJsonFile(fileContents); fileProvider.NotifyChanged(); connectionString = connectorFactory.Get("examplePostgreSqlService").Options.ConnectionString; diff --git a/src/Discovery/src/Consul/Registry/ConsulServiceRegistrar.cs b/src/Discovery/src/Consul/Registry/ConsulServiceRegistrar.cs index ef0d9d392e..17b8c93391 100644 --- a/src/Discovery/src/Consul/Registry/ConsulServiceRegistrar.cs +++ b/src/Discovery/src/Consul/Registry/ConsulServiceRegistrar.cs @@ -136,7 +136,7 @@ private async Task DoWithRetryAsync(Func retryable, Con if (attempts < options.MaxAttempts) { LogStartingRetry(exception, attempts); - Thread.CurrentThread.Join(backOff); + await Task.Delay(backOff, cancellationToken); int nextBackOff = (int)(backOff * options.Multiplier); backOff = Math.Min(nextBackOff, options.MaxInterval); } diff --git a/src/Discovery/src/Eureka/EurekaApplicationInfoManager.cs b/src/Discovery/src/Eureka/EurekaApplicationInfoManager.cs index 3bf7b05b3c..dfb1702476 100644 --- a/src/Discovery/src/Eureka/EurekaApplicationInfoManager.cs +++ b/src/Discovery/src/Eureka/EurekaApplicationInfoManager.cs @@ -31,7 +31,7 @@ public sealed partial class EurekaApplicationInfoManager : IDisposable // Readers must never be blocked, as it may delay the periodic heartbeat. // Updates from user code must be synchronized with configuration changes. // After update, the readonly snapshot is replaced. Volatile prevents reading stale data. - // Once metadata has been set from user code, it overrules what's in configuration. + // Once metadata has been set from user code, it overrides what's in configuration. private volatile InstanceInfo _instance; private IReadOnlyDictionary? _explicitMetadata; @@ -125,7 +125,7 @@ private void InnerUpdateInstance(EurekaInstanceOptions newInstanceOptions, bool newInstance = previousInstance; } - // Status in configuration is the initial startup status. New or previous instance status always overrules it. + // Status in configuration is the initial startup status. New or previous instance status always overrides it. newInstance.ReplaceStatus(newStatus ?? previousInstance.Status); if (newOverriddenStatus != null) diff --git a/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs b/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs index 90f12d9797..dcbb000c47 100644 --- a/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs +++ b/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Hosting; using Steeltoe.Common.Discovery; using Steeltoe.Common.TestResources; -using Steeltoe.Common.TestResources.IO; namespace Steeltoe.Discovery.Configuration.Test; @@ -119,13 +118,11 @@ public async Task AddConfigurationDiscoveryClient_AddsClientWithOptions() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); + var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(directory); - configurationBuilder.AddJsonFile(fileName); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); IConfiguration configuration = configurationBuilder.Build(); var services = new ServiceCollection(); diff --git a/src/Discovery/test/Eureka.Test/EurekaClientOptionsTest.cs b/src/Discovery/test/Eureka.Test/EurekaClientOptionsTest.cs index 8194b53020..dabd6d4039 100644 --- a/src/Discovery/test/Eureka.Test/EurekaClientOptionsTest.cs +++ b/src/Discovery/test/Eureka.Test/EurekaClientOptionsTest.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Steeltoe.Common.TestResources; -using Steeltoe.Common.TestResources.IO; using Steeltoe.Discovery.Eureka.Configuration; namespace Steeltoe.Discovery.Eureka.Test; @@ -98,14 +97,11 @@ public void Constructor_ConfiguresEurekaDiscovery_Correctly() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); - var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(directory); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); - configurationBuilder.AddJsonFile(fileName); + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); IConfiguration configuration = configurationBuilder.Build(); IConfigurationSection clientSection = configuration.GetSection(EurekaClientOptions.ConfigurationPrefix); diff --git a/src/Discovery/test/Eureka.Test/EurekaInstanceOptionsTest.cs b/src/Discovery/test/Eureka.Test/EurekaInstanceOptionsTest.cs index 180fe7fbf9..445262fbb4 100644 --- a/src/Discovery/test/Eureka.Test/EurekaInstanceOptionsTest.cs +++ b/src/Discovery/test/Eureka.Test/EurekaInstanceOptionsTest.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Configuration; using Steeltoe.Common.TestResources; -using Steeltoe.Common.TestResources.IO; using Steeltoe.Discovery.Eureka.AppInfo; using Steeltoe.Discovery.Eureka.Configuration; @@ -104,14 +103,11 @@ public void Constructor_ConfiguresEurekaDiscovery_Correctly() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); - var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(directory); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); - configurationBuilder.AddJsonFile(fileName); + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); IConfiguration configuration = configurationBuilder.Build(); IConfigurationSection instanceSection = configuration.GetSection(EurekaInstanceOptions.ConfigurationPrefix); diff --git a/src/Discovery/test/HttpClients.Test/RegisterMultipleDiscoveryClientsTest.cs b/src/Discovery/test/HttpClients.Test/RegisterMultipleDiscoveryClientsTest.cs index 07ef73750c..a4a3a846f1 100644 --- a/src/Discovery/test/HttpClients.Test/RegisterMultipleDiscoveryClientsTest.cs +++ b/src/Discovery/test/HttpClients.Test/RegisterMultipleDiscoveryClientsTest.cs @@ -15,7 +15,6 @@ using Steeltoe.Common.HealthChecks; using Steeltoe.Common.Http.HttpClientPooling; using Steeltoe.Common.TestResources; -using Steeltoe.Common.TestResources.IO; using Steeltoe.Configuration.CloudFoundry; using Steeltoe.Configuration.CloudFoundry.ServiceBindings; using Steeltoe.Configuration.CloudFoundry.ServiceBindings.PostProcessors; @@ -57,14 +56,11 @@ public async Task WithEurekaConfiguration_AddsDiscoveryClient() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(directory); - configurationBuilder.AddJsonFile(fileName); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); IConfiguration configuration = configurationBuilder.Build(); IServiceCollection services = new ServiceCollection(); @@ -656,14 +652,11 @@ public async Task WithConsulConfiguration_AddsDiscoveryClient() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); - string directory = Path.GetDirectoryName(path)!; - string fileName = Path.GetFileName(path); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.SetBasePath(directory); - configurationBuilder.AddJsonFile(fileName); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); IConfiguration configuration = configurationBuilder.Build(); var services = new ServiceCollection(); @@ -829,10 +822,12 @@ public async Task WithAppConfiguration_AddsAndWorks() } """; - using var sandbox = new Sandbox(); - string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings); + var fileProvider = new MemoryFileProvider(); + fileProvider.IncludeAppSettingsJsonFile(appSettings); - IConfiguration configuration = new ConfigurationBuilder().SetBasePath(Path.GetDirectoryName(path)!).AddJsonFile(Path.GetFileName(path)).Build(); + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + IConfiguration configuration = configurationBuilder.Build(); IServiceCollection services = new ServiceCollection(); services.AddOptions(); diff --git a/src/Logging/test/DynamicConsole.Test/DynamicConsoleLoggerProviderTest.cs b/src/Logging/test/DynamicConsole.Test/DynamicConsoleLoggerProviderTest.cs index 3e85796499..dce86c3f4e 100644 --- a/src/Logging/test/DynamicConsole.Test/DynamicConsoleLoggerProviderTest.cs +++ b/src/Logging/test/DynamicConsole.Test/DynamicConsoleLoggerProviderTest.cs @@ -400,19 +400,17 @@ public async Task AppliesChangedConfiguration() { MemoryFileProvider fileProvider = new(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Logging": { - "LogLevel": { - "A": "Warning" + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Logging": { + "LogLevel": { + "A": "Warning" + } + } } - } - } - """); - - using IDynamicLoggerProvider provider = CreateLoggerProvider(configurationBuilder => - configurationBuilder.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true)); + """); + using IDynamicLoggerProvider provider = CreateLoggerProvider(configurationBuilder => configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider)); DynamicLoggingTestContext testContext = new(provider, _consoleOutput); await testContext.Parent.AssertMinLevelAsync(LogLevel.Warning); @@ -426,16 +424,16 @@ public async Task AppliesChangedConfiguration() await testContext.Self.AssertMinLevelAsync(LogLevel.Error, LogLevel.Warning); await testContext.Child.AssertMinLevelAsync(LogLevel.Error); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Logging": { - "LogLevel": { - "A": "Trace", - "A.B.C": "Debug" + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Logging": { + "LogLevel": { + "A": "Trace", + "A.B.C": "Debug" + } + } } - } - } - """); + """); fileProvider.NotifyChanged(); testContext.Refresh(); diff --git a/src/Logging/test/DynamicSerilog.Test/DynamicSerilogLoggerProviderTest.cs b/src/Logging/test/DynamicSerilog.Test/DynamicSerilogLoggerProviderTest.cs index 682a90fdc4..41545a488f 100644 --- a/src/Logging/test/DynamicSerilog.Test/DynamicSerilogLoggerProviderTest.cs +++ b/src/Logging/test/DynamicSerilog.Test/DynamicSerilogLoggerProviderTest.cs @@ -198,24 +198,22 @@ public void AppliesChangedConfiguration() { MemoryFileProvider fileProvider = new(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Serilog": { - "MinimumLevel": { - "Override": { - "A": "Warning" + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Serilog": { + "MinimumLevel": { + "Override": { + "A": "Warning" + } + }, + "WriteTo": { + "Name": "Console" + } } - }, - "WriteTo": { - "Name": "Console" } - } - } - """); - - using IDynamicLoggerProvider provider = CreateLoggerProvider(configurationBuilder => - configurationBuilder.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true)); + """); + using IDynamicLoggerProvider provider = CreateLoggerProvider(configurationBuilder => configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider)); DynamicLoggingTestContext testContext = new(provider, _consoleOutput); testContext.Parent.AssertMinLevel(LogLevel.Warning); @@ -229,19 +227,19 @@ public void AppliesChangedConfiguration() testContext.Self.AssertMinLevel(LogLevel.Error, LogLevel.Warning); testContext.Child.AssertMinLevel(LogLevel.Error); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Serilog": { - "MinimumLevel": { - "Override": { - "A": "Verbose", - "A.B.C": "Debug" + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Serilog": { + "MinimumLevel": { + "Override": { + "A": "Verbose", + "A.B.C": "Debug" + } + }, + "WriteTo": "Console" } - }, - "WriteTo": "Console" - } - } - """); + } + """); fileProvider.NotifyChanged(); testContext.Refresh(); diff --git a/src/Management/src/Endpoint/Configuration/ManagementOptions.cs b/src/Management/src/Endpoint/Configuration/ManagementOptions.cs index cb9bcc5f86..0314b64669 100644 --- a/src/Management/src/Endpoint/Configuration/ManagementOptions.cs +++ b/src/Management/src/Endpoint/Configuration/ManagementOptions.cs @@ -55,7 +55,7 @@ public sealed class ManagementOptions public bool SslEnabled { get; set; } /// - /// Gets or sets a value indicating whether the HTTP response status code is based on the health status. This setting can be overruled by sending an + /// Gets or sets a value indicating whether the HTTP response status code is based on the health status. This setting can be overridden by sending an /// X-Use-Status-Code-From-Response HTTP header. Default value: true. /// public bool UseStatusCodeFromResponse { get; set; } = true; diff --git a/src/Management/src/Endpoint/ConfigurationSchema.json b/src/Management/src/Endpoint/ConfigurationSchema.json index 883f3049cd..6d5e8eacf8 100644 --- a/src/Management/src/Endpoint/ConfigurationSchema.json +++ b/src/Management/src/Endpoint/ConfigurationSchema.json @@ -810,7 +810,7 @@ }, "UseStatusCodeFromResponse": { "type": "boolean", - "description": "Gets or sets a value indicating whether the HTTP response status code is based on the health status. This setting can be overruled by sending an X-Use-Status-Code-From-Response HTTP header. Default value: true." + "description": "Gets or sets a value indicating whether the HTTP response status code is based on the health status. This setting can be overridden by sending an X-Use-Status-Code-From-Response HTTP header. Default value: true." }, "Web": { "type": "object", diff --git a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs index 14f42128c6..6a4e565aea 100644 --- a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs @@ -522,21 +522,21 @@ public async Task Can_change_configuration_at_runtime() var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Info": { - "Enabled": false + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Info": { + "Enabled": false + } + } } } - } - } - """); + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddCloudFoundry(); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddCloudFoundryActuator(); builder.Services.AddInfoActuator(); builder.Services.AddHealthActuator(); @@ -568,17 +568,17 @@ public async Task Can_change_configuration_at_runtime() } """); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Health": { - "Enabled": false + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Health": { + "Enabled": false + } + } } } - } - } - """); + """); fileProvider.NotifyChanged(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Environment/EnvironmentActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Environment/EnvironmentActuatorTest.cs index 13fe635f8b..897202ec36 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Environment/EnvironmentActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Environment/EnvironmentActuatorTest.cs @@ -323,34 +323,34 @@ public async Task Can_change_configuration_at_runtime() { var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Actuator": { - "Exposure": { - "Include": [ - "env" - ] + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Actuator": { + "Exposure": { + "Include": [ + "env" + ] + } + }, + "Env": { + "KeysToSanitize": [ + "Password" + ] + } } }, - "Env": { - "KeysToSanitize": [ - "Password" - ] + "TestSettings": { + "Password": "secret-password", + "AccessToken": "secret-token" } } - }, - "TestSettings": { - "Password": "secret-password", - "AccessToken": "secret-token" - } - } - """); + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.Sources.Clear(); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddEnvironmentActuator(); await using WebApplication host = builder.Build(); @@ -390,30 +390,30 @@ public async Task Can_change_configuration_at_runtime() } """); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Actuator": { - "Exposure": { - "Include": [ - "env" - ] + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Actuator": { + "Exposure": { + "Include": [ + "env" + ] + } + }, + "Env": { + "KeysToSanitize": [ + "AccessToken" + ] + } } }, - "Env": { - "KeysToSanitize": [ - "AccessToken" - ] + "TestSettings": { + "Password": "secret-password", + "AccessToken": "secret-token" } } - }, - "TestSettings": { - "Password": "secret-password", - "AccessToken": "secret-token" - } - } - """); + """); fileProvider.NotifyChanged(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs index 2310840bea..4a5e408466 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs @@ -578,26 +578,26 @@ public async Task Can_change_configuration_at_runtime() { var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Health": { - "Groups": { - "ping-group": { - "include": "ping", - "ShowComponents": "Always" + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Health": { + "Groups": { + "ping-group": { + "include": "ping", + "ShowComponents": "Always" + } + } } } } } - } - } - """); + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(AppSettings); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddHealthActuator(); WebApplication host = builder.Build(); @@ -622,21 +622,21 @@ public async Task Can_change_configuration_at_runtime() } """); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Health": { - "Groups": { - "ping-group": { - "include": "ping" + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Health": { + "Groups": { + "ping-group": { + "include": "ping" + } + } } } } } - } - } - """); + """); fileProvider.NotifyChanged(); diff --git a/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/HttpExchangesActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/HttpExchangesActuatorTest.cs index 25f49eb277..476a670815 100644 --- a/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/HttpExchangesActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/HttpExchangesActuatorTest.cs @@ -422,17 +422,17 @@ public async Task Can_change_configuration_at_runtime() { var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "HttpExchanges": { - "IncludeQueryString": false + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "HttpExchanges": { + "IncludeQueryString": false + } + } } } - } - } - """); + """); DateTime currentTime = 19.September(2024); @@ -446,7 +446,7 @@ public async Task Can_change_configuration_at_runtime() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(AppSettings); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddSingleton(new FakeHttpExchangeRecorder(httpExchanges)); builder.Services.AddHttpExchangesActuator(); await using WebApplication host = builder.Build(); @@ -487,17 +487,17 @@ public async Task Can_change_configuration_at_runtime() } """); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "HttpExchanges": { - "Reverse": false + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "HttpExchanges": { + "Reverse": false + } + } } } - } - } - """); + """); fileProvider.NotifyChanged(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs index abba10aa7b..7325d9b4b1 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs @@ -452,20 +452,20 @@ public async Task Can_change_configuration_at_runtime() { var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Info": { - "Enabled": false + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Info": { + "Enabled": false + } + } } } - } - } - """); + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddHypermediaActuator(); builder.Services.AddInfoActuator(); builder.Services.AddHealthActuator(); @@ -496,17 +496,17 @@ public async Task Can_change_configuration_at_runtime() } """); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Health": { - "Enabled": false + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Health": { + "Enabled": false + } + } } } - } - } - """); + """); fileProvider.NotifyChanged(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorSerilogTest.cs b/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorSerilogTest.cs index 70e96e837a..d7d800c214 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorSerilogTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorSerilogTest.cs @@ -272,23 +272,23 @@ public async Task Can_change_serilog_configuration_at_runtime() { var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Serilog": { - "MinimumLevel": { - "Default": "Error", - "Override": { - "Fake": "Warning", - "Fake.Category.AtDebugLevel": "Debug" + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Serilog": { + "MinimumLevel": { + "Default": "Error", + "Override": { + "Fake": "Warning", + "Fake.Category.AtDebugLevel": "Debug" + } + } } } - } - } - """); + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(AppSettings); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddSingleton(); builder.Logging.AddDynamicSerilog(); builder.Services.AddLoggersActuator(); @@ -342,19 +342,19 @@ public async Task Can_change_serilog_configuration_at_runtime() } """); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Serilog": { - "MinimumLevel": { - "Default": "Information", - "Override": { - "Fake.Some": "Error", - "Fake.Category": "Warning" + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Fake.Some": "Error", + "Fake.Category": "Warning" + } + } } } - } - } - """); + """); fileProvider.NotifyChanged(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs index bde4c60857..0e28b20870 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs @@ -588,21 +588,21 @@ public async Task Can_change_configuration_at_runtime() { var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Logging": { - "LogLevel": { - "Default": "Error", - "Fake": "Warning", - "Fake.Category.AtDebugLevel": "Debug" + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Logging": { + "LogLevel": { + "Default": "Error", + "Fake": "Warning", + "Fake.Category.AtDebugLevel": "Debug" + } + } } - } - } - """); + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(AppSettings); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); EnsureLoggingConfigurationIsBound(builder.Logging, builder.Configuration); builder.Services.AddSingleton(); builder.Services.AddLoggersActuator(); @@ -656,17 +656,17 @@ public async Task Can_change_configuration_at_runtime() } """); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Logging": { - "LogLevel": { - "Default": "Information", - "Fake.Some": "Error", - "Fake.Category": "Warning" + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Logging": { + "LogLevel": { + "Default": "Information", + "Fake.Some": "Error", + "Fake.Category": "Warning" + } + } } - } - } - """); + """); fileProvider.NotifyChanged(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs b/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs index 902db72709..bb571357e7 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs @@ -186,22 +186,22 @@ public async Task Can_change_allowed_verbs_at_runtime() { MemoryFileProvider fileProvider = new(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Actuator": { - "Exposure": { - "Include": ["refresh"] + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Actuator": { + "Exposure": { + "Include": ["refresh"] + } + } } } } - } - } - """); + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddControllersWithViews(options => options.EnableEndpointRouting = false); builder.Services.AddRefreshActuator(); @@ -218,22 +218,22 @@ public async Task Can_change_allowed_verbs_at_runtime() HttpResponseMessage postResponse = await httpClient.PostAsync(requestUri, null, TestContext.Current.CancellationToken); postResponse.StatusCode.Should().Be(HttpStatusCode.OK); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Actuator": { - "Exposure": { - "Include": ["refresh"] + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Actuator": { + "Exposure": { + "Include": ["refresh"] + } + }, + "Refresh": { + "AllowedVerbs": ["GET"] + } } - }, - "Refresh": { - "AllowedVerbs": ["GET"] } } - } - } - """); + """); fileProvider.NotifyChanged(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInEndpointRoutingTest.cs b/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInEndpointRoutingTest.cs index 3687723972..80610b9771 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInEndpointRoutingTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInEndpointRoutingTest.cs @@ -186,22 +186,22 @@ public async Task Can_change_allowed_verbs_at_runtime() { MemoryFileProvider fileProvider = new(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Actuator": { - "Exposure": { - "Include": ["refresh"] + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Actuator": { + "Exposure": { + "Include": ["refresh"] + } + } } } } - } - } - """); + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddControllersWithViews(); builder.Services.AddRefreshActuator(); @@ -218,22 +218,22 @@ public async Task Can_change_allowed_verbs_at_runtime() HttpResponseMessage postResponse = await httpClient.PostAsync(requestUri, null, TestContext.Current.CancellationToken); postResponse.StatusCode.Should().Be(HttpStatusCode.OK); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Actuator": { - "Exposure": { - "Include": ["refresh"] + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Actuator": { + "Exposure": { + "Include": ["refresh"] + } + }, + "Refresh": { + "AllowedVerbs": ["GET"] + } } - }, - "Refresh": { - "AllowedVerbs": ["GET"] } } - } - } - """); + """); fileProvider.NotifyChanged(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs index 0faf8a75af..7ede0f15bd 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs @@ -200,21 +200,21 @@ public async Task Can_change_configuration_at_runtime() { var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Refresh": { - "ReturnConfiguration": false + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Refresh": { + "ReturnConfiguration": false + } + } } } - } - } - """); + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(AppSettings); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddRefreshActuator(); await using WebApplication host = builder.Build(); @@ -229,10 +229,10 @@ public async Task Can_change_configuration_at_runtime() responseBody1.Should().BeJson("[]"); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - } - """); + fileProvider.ReplaceAppSettingsJsonFile(""" + { + } + """); fileProvider.NotifyChanged(); diff --git a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RouteMappingsActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RouteMappingsActuatorTest.cs index 3262df4951..6c63c31926 100644 --- a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RouteMappingsActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RouteMappingsActuatorTest.cs @@ -373,21 +373,21 @@ public async Task Can_change_configuration_at_runtime() { var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management": { - "Endpoints": { - "Mappings": { - "IncludeActuators": false + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Management": { + "Endpoints": { + "Mappings": { + "IncludeActuators": false + } + } } } - } - } - """); + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(AppSettings); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddAllActuators(); await using WebApplication host = builder.Build(); @@ -400,10 +400,10 @@ public async Task Can_change_configuration_at_runtime() responseNode1["contexts"]!["application"]!["mappings"]!["dispatcherServlets"]!["dispatcherServlet"].Should().BeOfType().Subject.Should() .BeEmpty(); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - } - """); + fileProvider.ReplaceAppSettingsJsonFile(""" + { + } + """); fileProvider.NotifyChanged(); diff --git a/src/Management/test/Endpoint.Test/EndpointOptionsTest.cs b/src/Management/test/Endpoint.Test/EndpointOptionsTest.cs index 5610cf05e4..f5d9858575 100644 --- a/src/Management/test/Endpoint.Test/EndpointOptionsTest.cs +++ b/src/Management/test/Endpoint.Test/EndpointOptionsTest.cs @@ -86,14 +86,14 @@ public async Task CanTurnOffEndpointAtRuntimeFromExposureConfiguration() { MemoryFileProvider fileProvider = new(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management:Endpoints:Actuator:Exposure:Include:0": "env" - } - """); + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Management:Endpoints:Actuator:Exposure:Include:0": "env" + } + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddAllActuators(); await using WebApplication app = builder.Build(); @@ -103,12 +103,12 @@ public async Task CanTurnOffEndpointAtRuntimeFromExposureConfiguration() HttpResponseMessage response1 = await httpClient.GetAsync(new Uri("/actuator/env", UriKind.Relative), TestContext.Current.CancellationToken); response1.StatusCode.Should().Be(HttpStatusCode.OK); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management:Endpoints:Actuator:Exposure:Include:0": "env", - "Management:Endpoints:Actuator:Exposure:Exclude:0": "*" - } - """); + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Management:Endpoints:Actuator:Exposure:Include:0": "env", + "Management:Endpoints:Actuator:Exposure:Exclude:0": "*" + } + """); fileProvider.NotifyChanged(); @@ -121,15 +121,15 @@ public async Task CanTurnOnEndpointAtRuntimeFromExposureConfiguration() { MemoryFileProvider fileProvider = new(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management:Endpoints:Actuator:Exposure:Include:0": "env", - "Management:Endpoints:Actuator:Exposure:Exclude:0": "*" - } - """); + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Management:Endpoints:Actuator:Exposure:Include:0": "env", + "Management:Endpoints:Actuator:Exposure:Exclude:0": "*" + } + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddAllActuators(); await using WebApplication app = builder.Build(); @@ -139,11 +139,11 @@ public async Task CanTurnOnEndpointAtRuntimeFromExposureConfiguration() HttpResponseMessage response1 = await httpClient.GetAsync(new Uri("/actuator/env", UriKind.Relative), TestContext.Current.CancellationToken); response1.StatusCode.Should().Be(HttpStatusCode.NotFound); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management:Endpoints:Actuator:Exposure:Include:0": "env" - } - """); + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Management:Endpoints:Actuator:Exposure:Include:0": "env" + } + """); fileProvider.NotifyChanged(); @@ -156,15 +156,15 @@ public async Task CanTurnOffEndpointAtRuntimeFromEndpointConfiguration() { MemoryFileProvider fileProvider = new(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management:Endpoints:Actuator:Exposure:Include:0": "env", - "Management:Endpoints:Env:Enabled": "true" - } - """); + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Management:Endpoints:Actuator:Exposure:Include:0": "env", + "Management:Endpoints:Env:Enabled": "true" + } + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddAllActuators(); await using WebApplication app = builder.Build(); @@ -174,12 +174,12 @@ public async Task CanTurnOffEndpointAtRuntimeFromEndpointConfiguration() HttpResponseMessage response1 = await httpClient.GetAsync(new Uri("/actuator/env", UriKind.Relative), TestContext.Current.CancellationToken); response1.StatusCode.Should().Be(HttpStatusCode.OK); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management:Endpoints:Actuator:Exposure:Include:0": "env", - "Management:Endpoints:Env:Enabled": "false" - } - """); + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Management:Endpoints:Actuator:Exposure:Include:0": "env", + "Management:Endpoints:Env:Enabled": "false" + } + """); fileProvider.NotifyChanged(); @@ -192,15 +192,15 @@ public async Task CanTurnOnEndpointAtRuntimeFromEndpointConfiguration() { MemoryFileProvider fileProvider = new(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management:Endpoints:Actuator:Exposure:Include:0": "env", - "Management:Endpoints:Env:Enabled": "false" - } - """); + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Management:Endpoints:Actuator:Exposure:Include:0": "env", + "Management:Endpoints:Env:Enabled": "false" + } + """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddAllActuators(); await using WebApplication app = builder.Build(); @@ -210,12 +210,12 @@ public async Task CanTurnOnEndpointAtRuntimeFromEndpointConfiguration() HttpResponseMessage response1 = await httpClient.GetAsync(new Uri("/actuator/env", UriKind.Relative), TestContext.Current.CancellationToken); response1.StatusCode.Should().Be(HttpStatusCode.NotFound); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, """ - { - "Management:Endpoints:Actuator:Exposure:Include:0": "env", - "Management:Endpoints:Env:Enabled": "true" - } - """); + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Management:Endpoints:Actuator:Exposure:Include:0": "env", + "Management:Endpoints:Env:Enabled": "true" + } + """); fileProvider.NotifyChanged(); diff --git a/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs b/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs index 7687a578c4..81dfc18dee 100644 --- a/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs +++ b/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs @@ -154,7 +154,7 @@ public async Task PeriodicRefreshCanBeTurnedOnAfterStart() { var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, $$""" + fileProvider.IncludeAppSettingsJsonFile($$""" { "Spring": { "Boot": { @@ -171,7 +171,7 @@ public async Task PeriodicRefreshCanBeTurnedOnAfterStart() """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.CreateDefault(false); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddSingleton(); builder.Services.AddSpringBootAdminClient(); @@ -186,7 +186,7 @@ public async Task PeriodicRefreshCanBeTurnedOnAfterStart() handler.Mock.GetMatchCount(registerMock).Should().Be(1); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, $$""" + fileProvider.ReplaceAppSettingsJsonFile($$""" { "Spring": { "Boot": { @@ -213,7 +213,7 @@ public async Task PeriodicRefreshCanBeTurnedOffAfterStart() { var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, $$""" + fileProvider.IncludeAppSettingsJsonFile($$""" { "Spring": { "Boot": { @@ -230,7 +230,7 @@ public async Task PeriodicRefreshCanBeTurnedOffAfterStart() """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.CreateDefault(false); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddSingleton(); builder.Services.AddSpringBootAdminClient(); @@ -246,7 +246,7 @@ public async Task PeriodicRefreshCanBeTurnedOffAfterStart() handler.Mock.GetMatchCount(registerMock).Should().BeGreaterThan(1); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, $$""" + fileProvider.ReplaceAppSettingsJsonFile($$""" { "Spring": { "Boot": { @@ -370,7 +370,7 @@ public async Task UnregistersFromPreviousServerOnConfigurationChange() { var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, $$""" + fileProvider.IncludeAppSettingsJsonFile($$""" { "Spring": { "Boot": { @@ -387,7 +387,7 @@ public async Task UnregistersFromPreviousServerOnConfigurationChange() """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.CreateDefault(false); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddSingleton(); builder.Services.AddSpringBootAdminClient(); @@ -405,7 +405,7 @@ public async Task UnregistersFromPreviousServerOnConfigurationChange() handler.Mock.GetMatchCount(registerMock1).Should().BeGreaterThan(1); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, $$""" + fileProvider.ReplaceAppSettingsJsonFile($$""" { "Spring": { "Boot": { @@ -434,7 +434,7 @@ public async Task UnregistersFromPreviousServerOnShutdownAfterConfigurationBecam { var fileProvider = new MemoryFileProvider(); - fileProvider.IncludeFile(MemoryFileProvider.DefaultAppSettingsFileName, $$""" + fileProvider.IncludeAppSettingsJsonFile($$""" { "Spring": { "Boot": { @@ -451,7 +451,7 @@ public async Task UnregistersFromPreviousServerOnShutdownAfterConfigurationBecam """); WebApplicationBuilder builder = TestWebApplicationBuilderFactory.CreateDefault(false); - builder.Configuration.AddJsonFile(fileProvider, MemoryFileProvider.DefaultAppSettingsFileName, false, true); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); builder.Services.AddSingleton(); builder.Services.AddSpringBootAdminClient(); @@ -470,7 +470,7 @@ public async Task UnregistersFromPreviousServerOnShutdownAfterConfigurationBecam handler.Mock.GetMatchCount(registerMock1).Should().BeGreaterThan(1); - fileProvider.ReplaceFile(MemoryFileProvider.DefaultAppSettingsFileName, $$""" + fileProvider.ReplaceAppSettingsJsonFile($$""" { "Spring": { "Boot": { diff --git a/src/Management/test/RazorPagesTestWebApp/Program.cs b/src/Management/test/RazorPagesTestWebApp/Program.cs index 508e5edcfa..b4295ca480 100644 --- a/src/Management/test/RazorPagesTestWebApp/Program.cs +++ b/src/Management/test/RazorPagesTestWebApp/Program.cs @@ -12,7 +12,7 @@ { // This project intentionally does NOT include appsettings*.json files, because they get copied to test projects // that reference this project, and that affects test outcomes. For example, setting the minimum log level - // to Trace on WebApplicationBuilder wouldn't work, because these files overrule log levels. + // to Trace on WebApplicationBuilder wouldn't work, because these files override log levels. ["DetailedErrors"] = builder.Environment.IsDevelopment() ? "true" : "false", ["Logging:LogLevel:Default"] = "Information", From 465924520b4296a677be68da7b53b9d494d0daee Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:15:34 +0200 Subject: [PATCH 43/81] Update versions of GitHub Actions (#1668) * Update versions of GitHub Actions * Test: artifact download and package signing * Revert "Test: artifact download and package signing" This reverts commit 459cb3181378a4625096f149d9333f66450f9d98. --- .github/workflows/Steeltoe.All.yml | 4 ++-- .github/workflows/component-shared-workflow.yml | 4 ++-- .github/workflows/package.yml | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/Steeltoe.All.yml b/.github/workflows/Steeltoe.All.yml index 2b399806f2..32b0eb6c15 100644 --- a/.github/workflows/Steeltoe.All.yml +++ b/.github/workflows/Steeltoe.All.yml @@ -99,7 +99,7 @@ jobs: - name: Upload crash/hang dumps (on failure) if: ${{ !cancelled() && (steps.test.outcome == 'failure' || steps.test-memory-dumps.outcome == 'failure') }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: FailedTestOutput-${{ matrix.os }} path: | @@ -109,7 +109,7 @@ jobs: - name: Report test results if: ${{ !cancelled() && (steps.test.outcome != 'skipped' || steps.test-memory-dumps.outcome != 'skipped') }} - uses: dorny/test-reporter@v2 + uses: dorny/test-reporter@v3 with: name: ${{ matrix.os }} test results reporter: dotnet-trx diff --git a/.github/workflows/component-shared-workflow.yml b/.github/workflows/component-shared-workflow.yml index f22f44241e..f7c144a622 100644 --- a/.github/workflows/component-shared-workflow.yml +++ b/.github/workflows/component-shared-workflow.yml @@ -91,7 +91,7 @@ jobs: - name: Upload crash/hang dumps (on failure) if: ${{ !cancelled() && (steps.test.outcome == 'failure' || steps.test-memory-dumps.outcome == 'failure') }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: FailedTestOutput-${{ inputs.OS }}-latest path: | @@ -101,7 +101,7 @@ jobs: - name: Report test results if: ${{ !cancelled() && (steps.test.outcome != 'skipped' || steps.test-memory-dumps.outcome != 'skipped') }} - uses: dorny/test-reporter@v2 + uses: dorny/test-reporter@v3 with: name: ${{ inputs.OS }}-latest test results reporter: dotnet-trx diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 46b1dbab50..4134cf0610 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -123,7 +123,7 @@ jobs: run: dotnet pack ${{ env.SOLUTION_FILE }} --no-build --configuration Release --output ${{ github.workspace }}/packages /p:VersionSuffix=${{ env.PACKAGE_VERSION_SUFFIX }} - name: Upload unsigned packages - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: if-no-files-found: error name: unsigned-packages @@ -141,7 +141,7 @@ jobs: steps: - name: Download unsigned packages - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: unsigned-packages path: packages @@ -155,7 +155,7 @@ jobs: run: dotnet tool install --global sign --prerelease - name: Azure login - uses: azure/login@v2 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} @@ -174,7 +174,7 @@ jobs: --description-url 'https://steeltoe.io/' - name: Upload signed packages - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: if-no-files-found: error name: signed-packages @@ -194,14 +194,14 @@ jobs: steps: - name: Azure login - uses: azure/login@v2 + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: Download signed packages - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: signed-packages path: packages @@ -243,7 +243,7 @@ jobs: dotnet-version: 8.0.* - name: Download signed packages - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: signed-packages path: packages From 176f7ae4ff3a9b16d4d547f5ba95fe8ed75ef327 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:56:23 +0200 Subject: [PATCH 44/81] Update AssemblyInfo.cs files to include logging categories for all Steeltoe assemblies (#1669) --- .../ConfigurationSchema.json | 17 ++++++++++++++++ .../Properties/AssemblyInfo.cs | 3 +++ .../src/Common/ConfigurationSchema.json | 14 +++++++++++++ .../src/Common/Properties/AssemblyInfo.cs | 11 ++++++++++ .../src/Hosting/ConfigurationSchema.json | 17 ++++++++++++++++ .../src/Hosting/Properties/AssemblyInfo.cs | 3 +++ src/Common/src/Http/ConfigurationSchema.json | 17 ++++++++++++++++ .../src/Http/Properties/AssemblyInfo.cs | 3 +++ .../src/Logging/ConfigurationSchema.json | 17 ++++++++++++++++ .../src/Logging/Properties/AssemblyInfo.cs | 3 +++ src/Common/src/Net/ConfigurationSchema.json | 17 ++++++++++++++++ src/Common/src/Net/Properties/AssemblyInfo.cs | 3 +++ .../src/Abstractions/ConfigurationSchema.json | 17 ++++++++++++++++ .../Abstractions/Properties/AssemblyInfo.cs | 3 +++ .../src/CloudFoundry/ConfigurationSchema.json | 17 ++++++++++++++++ .../CloudFoundry/Properties/AssemblyInfo.cs | 3 +++ .../ConfigurationSchema.json | 20 +++++++++++++++++++ .../Properties/AssemblyInfo.cs | 3 +++ .../src/Placeholder/ConfigurationSchema.json | 17 ++++++++++++++++ .../Placeholder/Properties/AssemblyInfo.cs | 3 +++ .../src/RandomValue/ConfigurationSchema.json | 17 ++++++++++++++++ .../RandomValue/Properties/AssemblyInfo.cs | 3 +++ .../src/SpringBoot/ConfigurationSchema.json | 17 ++++++++++++++++ .../src/SpringBoot/Properties/AssemblyInfo.cs | 3 +++ .../ConfigurationSchema.json | 17 ++++++++++++++++ .../Properties/AssemblyInfo.cs | 3 +++ .../src/HttpClients/ConfigurationSchema.json | 17 ++++++++++++++++ .../HttpClients/Properties/AssemblyInfo.cs | 3 +++ .../src/Abstractions/ConfigurationSchema.json | 17 ++++++++++++++++ .../Abstractions/Properties/AssemblyInfo.cs | 3 +++ .../src/Abstractions/ConfigurationSchema.json | 17 ++++++++++++++++ .../Abstractions/Properties/AssemblyInfo.cs | 3 +++ .../src/Tracing/ConfigurationSchema.json | 15 ++++++++++++++ .../src/Tracing/Properties/AssemblyInfo.cs | 1 + .../ConfigurationSchema.json | 20 +++++++++++++++++++ .../Properties/AssemblyInfo.cs | 3 +++ .../ConfigurationSchema.json | 20 +++++++++++++++++++ .../Properties/AssemblyInfo.cs | 3 +++ .../ConfigurationSchema.json | 20 +++++++++++++++++++ .../Properties/AssemblyInfo.cs | 3 +++ 40 files changed, 413 insertions(+) create mode 100644 src/Bootstrap/src/AutoConfiguration/ConfigurationSchema.json create mode 100644 src/Common/src/Common/ConfigurationSchema.json create mode 100644 src/Common/src/Hosting/ConfigurationSchema.json create mode 100644 src/Common/src/Http/ConfigurationSchema.json create mode 100644 src/Common/src/Logging/ConfigurationSchema.json create mode 100644 src/Common/src/Net/ConfigurationSchema.json create mode 100644 src/Configuration/src/Abstractions/ConfigurationSchema.json create mode 100644 src/Configuration/src/CloudFoundry/ConfigurationSchema.json create mode 100644 src/Configuration/src/Kubernetes.ServiceBindings/ConfigurationSchema.json create mode 100644 src/Configuration/src/Placeholder/ConfigurationSchema.json create mode 100644 src/Configuration/src/RandomValue/ConfigurationSchema.json create mode 100644 src/Configuration/src/SpringBoot/ConfigurationSchema.json create mode 100644 src/Connectors/src/EntityFrameworkCore/ConfigurationSchema.json create mode 100644 src/Discovery/src/HttpClients/ConfigurationSchema.json create mode 100644 src/Logging/src/Abstractions/ConfigurationSchema.json create mode 100644 src/Management/src/Abstractions/ConfigurationSchema.json create mode 100644 src/Security/src/Authentication.JwtBearer/ConfigurationSchema.json create mode 100644 src/Security/src/Authentication.OpenIdConnect/ConfigurationSchema.json create mode 100644 src/Security/src/DataProtection.Redis/ConfigurationSchema.json diff --git a/src/Bootstrap/src/AutoConfiguration/ConfigurationSchema.json b/src/Bootstrap/src/AutoConfiguration/ConfigurationSchema.json new file mode 100644 index 0000000000..d3dd0a402f --- /dev/null +++ b/src/Bootstrap/src/AutoConfiguration/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Bootstrap": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Bootstrap.AutoConfiguration": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Bootstrap/src/AutoConfiguration/Properties/AssemblyInfo.cs b/src/Bootstrap/src/AutoConfiguration/Properties/AssemblyInfo.cs index 84aa78e3ca..584c9ab7d9 100644 --- a/src/Bootstrap/src/AutoConfiguration/Properties/AssemblyInfo.cs +++ b/src/Bootstrap/src/AutoConfiguration/Properties/AssemblyInfo.cs @@ -3,5 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Bootstrap", "Steeltoe.Bootstrap.AutoConfiguration")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] diff --git a/src/Common/src/Common/ConfigurationSchema.json b/src/Common/src/Common/ConfigurationSchema.json new file mode 100644 index 0000000000..2f5c0236d4 --- /dev/null +++ b/src/Common/src/Common/ConfigurationSchema.json @@ -0,0 +1,14 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Common": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Common/src/Common/Properties/AssemblyInfo.cs b/src/Common/src/Common/Properties/AssemblyInfo.cs index 61e3dec787..cb106ca1a6 100644 --- a/src/Common/src/Common/Properties/AssemblyInfo.cs +++ b/src/Common/src/Common/Properties/AssemblyInfo.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] @@ -11,13 +12,18 @@ [assembly: InternalsVisibleTo("Steeltoe.Common.Hosting")] [assembly: InternalsVisibleTo("Steeltoe.Common.Hosting.Test")] [assembly: InternalsVisibleTo("Steeltoe.Common.Http")] +[assembly: InternalsVisibleTo("Steeltoe.Common.Logging")] +[assembly: InternalsVisibleTo("Steeltoe.Common.Net")] [assembly: InternalsVisibleTo("Steeltoe.Common.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Configuration.Abstractions")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.CloudFoundry")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.CloudFoundry.Test")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.ConfigServer")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.Encryption")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.Kubernetes.ServiceBindings")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.Placeholder")] +[assembly: InternalsVisibleTo("Steeltoe.Configuration.RandomValue")] +[assembly: InternalsVisibleTo("Steeltoe.Configuration.SpringBoot")] [assembly: InternalsVisibleTo("Steeltoe.Connectors")] [assembly: InternalsVisibleTo("Steeltoe.Connectors.EntityFrameworkCore")] [assembly: InternalsVisibleTo("Steeltoe.Discovery.Configuration")] @@ -38,6 +44,11 @@ [assembly: InternalsVisibleTo("Steeltoe.Management.Tasks")] [assembly: InternalsVisibleTo("Steeltoe.Management.Tracing")] [assembly: InternalsVisibleTo("Steeltoe.Management.Tracing.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.JwtBearer")] [assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.CloudFoundry.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.OpenIdConnect")] [assembly: InternalsVisibleTo("Steeltoe.Security.Authorization.Certificate")] +[assembly: InternalsVisibleTo("Steeltoe.Security.DataProtection.Redis")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Common")] diff --git a/src/Common/src/Hosting/ConfigurationSchema.json b/src/Common/src/Hosting/ConfigurationSchema.json new file mode 100644 index 0000000000..3ca1828da6 --- /dev/null +++ b/src/Common/src/Hosting/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Common": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Common.Hosting": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Common/src/Hosting/Properties/AssemblyInfo.cs b/src/Common/src/Hosting/Properties/AssemblyInfo.cs index c073f1765b..cdcc9e9e37 100644 --- a/src/Common/src/Hosting/Properties/AssemblyInfo.cs +++ b/src/Common/src/Hosting/Properties/AssemblyInfo.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Common", "Steeltoe.Common.Hosting")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration")] [assembly: InternalsVisibleTo("Steeltoe.Common.Hosting.Test")] diff --git a/src/Common/src/Http/ConfigurationSchema.json b/src/Common/src/Http/ConfigurationSchema.json new file mode 100644 index 0000000000..be28e95f0f --- /dev/null +++ b/src/Common/src/Http/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Common": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Common.Http": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Common/src/Http/Properties/AssemblyInfo.cs b/src/Common/src/Http/Properties/AssemblyInfo.cs index 313de07940..0978e3a5c1 100644 --- a/src/Common/src/Http/Properties/AssemblyInfo.cs +++ b/src/Common/src/Http/Properties/AssemblyInfo.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Common", "Steeltoe.Common.Http")] [assembly: InternalsVisibleTo("Steeltoe.Common.Http.Test")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.ConfigServer")] diff --git a/src/Common/src/Logging/ConfigurationSchema.json b/src/Common/src/Logging/ConfigurationSchema.json new file mode 100644 index 0000000000..4dc17d7a1f --- /dev/null +++ b/src/Common/src/Logging/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Common": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Common.Logging": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Common/src/Logging/Properties/AssemblyInfo.cs b/src/Common/src/Logging/Properties/AssemblyInfo.cs index d6487d6f38..156a9a3d45 100644 --- a/src/Common/src/Logging/Properties/AssemblyInfo.cs +++ b/src/Common/src/Logging/Properties/AssemblyInfo.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Common", "Steeltoe.Common.Logging")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration")] [assembly: InternalsVisibleTo("Steeltoe.Common.Logging.Test")] diff --git a/src/Common/src/Net/ConfigurationSchema.json b/src/Common/src/Net/ConfigurationSchema.json new file mode 100644 index 0000000000..d1b98cae74 --- /dev/null +++ b/src/Common/src/Net/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Common": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Common.Net": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Common/src/Net/Properties/AssemblyInfo.cs b/src/Common/src/Net/Properties/AssemblyInfo.cs index c2d054e7ae..ac25e802b1 100644 --- a/src/Common/src/Net/Properties/AssemblyInfo.cs +++ b/src/Common/src/Net/Properties/AssemblyInfo.cs @@ -3,5 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Common", "Steeltoe.Common.Net")] [assembly: InternalsVisibleTo("Steeltoe.Common.Net.Test")] diff --git a/src/Configuration/src/Abstractions/ConfigurationSchema.json b/src/Configuration/src/Abstractions/ConfigurationSchema.json new file mode 100644 index 0000000000..4e42c281ea --- /dev/null +++ b/src/Configuration/src/Abstractions/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Configuration": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Configuration.Abstractions": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Configuration/src/Abstractions/Properties/AssemblyInfo.cs b/src/Configuration/src/Abstractions/Properties/AssemblyInfo.cs index 0140d8c883..296b2a6cce 100644 --- a/src/Configuration/src/Abstractions/Properties/AssemblyInfo.cs +++ b/src/Configuration/src/Abstractions/Properties/AssemblyInfo.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Configuration", "Steeltoe.Configuration.Abstractions")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.CloudFoundry")] diff --git a/src/Configuration/src/CloudFoundry/ConfigurationSchema.json b/src/Configuration/src/CloudFoundry/ConfigurationSchema.json new file mode 100644 index 0000000000..c44c85ae27 --- /dev/null +++ b/src/Configuration/src/CloudFoundry/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Configuration": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Configuration.CloudFoundry": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Configuration/src/CloudFoundry/Properties/AssemblyInfo.cs b/src/Configuration/src/CloudFoundry/Properties/AssemblyInfo.cs index e15ccb54fe..a9b0b36089 100644 --- a/src/Configuration/src/CloudFoundry/Properties/AssemblyInfo.cs +++ b/src/Configuration/src/CloudFoundry/Properties/AssemblyInfo.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Configuration", "Steeltoe.Configuration.CloudFoundry")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] diff --git a/src/Configuration/src/Kubernetes.ServiceBindings/ConfigurationSchema.json b/src/Configuration/src/Kubernetes.ServiceBindings/ConfigurationSchema.json new file mode 100644 index 0000000000..df9796cf2b --- /dev/null +++ b/src/Configuration/src/Kubernetes.ServiceBindings/ConfigurationSchema.json @@ -0,0 +1,20 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Configuration": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Configuration.Kubernetes": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Configuration.Kubernetes.ServiceBindings": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Configuration/src/Kubernetes.ServiceBindings/Properties/AssemblyInfo.cs b/src/Configuration/src/Kubernetes.ServiceBindings/Properties/AssemblyInfo.cs index 81a5cd3ad3..2f1ffe7ab8 100644 --- a/src/Configuration/src/Kubernetes.ServiceBindings/Properties/AssemblyInfo.cs +++ b/src/Configuration/src/Kubernetes.ServiceBindings/Properties/AssemblyInfo.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Configuration", "Steeltoe.Configuration.Kubernetes", "Steeltoe.Configuration.Kubernetes.ServiceBindings")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.Kubernetes.ServiceBindings.Test")] diff --git a/src/Configuration/src/Placeholder/ConfigurationSchema.json b/src/Configuration/src/Placeholder/ConfigurationSchema.json new file mode 100644 index 0000000000..fd9d115255 --- /dev/null +++ b/src/Configuration/src/Placeholder/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Configuration": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Configuration.Placeholder": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Configuration/src/Placeholder/Properties/AssemblyInfo.cs b/src/Configuration/src/Placeholder/Properties/AssemblyInfo.cs index 1a575f9ac5..db6fab0b9f 100644 --- a/src/Configuration/src/Placeholder/Properties/AssemblyInfo.cs +++ b/src/Configuration/src/Placeholder/Properties/AssemblyInfo.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Configuration", "Steeltoe.Configuration.Placeholder")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.ConfigServer")] diff --git a/src/Configuration/src/RandomValue/ConfigurationSchema.json b/src/Configuration/src/RandomValue/ConfigurationSchema.json new file mode 100644 index 0000000000..049c278508 --- /dev/null +++ b/src/Configuration/src/RandomValue/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Configuration": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Configuration.RandomValue": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Configuration/src/RandomValue/Properties/AssemblyInfo.cs b/src/Configuration/src/RandomValue/Properties/AssemblyInfo.cs index 11ef1de91a..e2e0837a76 100644 --- a/src/Configuration/src/RandomValue/Properties/AssemblyInfo.cs +++ b/src/Configuration/src/RandomValue/Properties/AssemblyInfo.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Configuration", "Steeltoe.Configuration.RandomValue")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.RandomValue.Test")] diff --git a/src/Configuration/src/SpringBoot/ConfigurationSchema.json b/src/Configuration/src/SpringBoot/ConfigurationSchema.json new file mode 100644 index 0000000000..5082a91d02 --- /dev/null +++ b/src/Configuration/src/SpringBoot/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Configuration": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Configuration.SpringBoot": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Configuration/src/SpringBoot/Properties/AssemblyInfo.cs b/src/Configuration/src/SpringBoot/Properties/AssemblyInfo.cs index 6f92dcdbb9..ac320c12db 100644 --- a/src/Configuration/src/SpringBoot/Properties/AssemblyInfo.cs +++ b/src/Configuration/src/SpringBoot/Properties/AssemblyInfo.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Configuration", "Steeltoe.Configuration.SpringBoot")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.SpringBoot.Test")] diff --git a/src/Connectors/src/EntityFrameworkCore/ConfigurationSchema.json b/src/Connectors/src/EntityFrameworkCore/ConfigurationSchema.json new file mode 100644 index 0000000000..d07e30ca58 --- /dev/null +++ b/src/Connectors/src/EntityFrameworkCore/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Connectors": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Connectors.EntityFrameworkCore": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Connectors/src/EntityFrameworkCore/Properties/AssemblyInfo.cs b/src/Connectors/src/EntityFrameworkCore/Properties/AssemblyInfo.cs index df6dc2105e..02b620e310 100644 --- a/src/Connectors/src/EntityFrameworkCore/Properties/AssemblyInfo.cs +++ b/src/Connectors/src/EntityFrameworkCore/Properties/AssemblyInfo.cs @@ -3,5 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Connectors", "Steeltoe.Connectors.EntityFrameworkCore")] [assembly: InternalsVisibleTo("Steeltoe.Connectors.EntityFrameworkCore.Test")] diff --git a/src/Discovery/src/HttpClients/ConfigurationSchema.json b/src/Discovery/src/HttpClients/ConfigurationSchema.json new file mode 100644 index 0000000000..7543664787 --- /dev/null +++ b/src/Discovery/src/HttpClients/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Discovery": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Discovery.HttpClients": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Discovery/src/HttpClients/Properties/AssemblyInfo.cs b/src/Discovery/src/HttpClients/Properties/AssemblyInfo.cs index 5bed50d08c..61c147559d 100644 --- a/src/Discovery/src/HttpClients/Properties/AssemblyInfo.cs +++ b/src/Discovery/src/HttpClients/Properties/AssemblyInfo.cs @@ -3,5 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Discovery", "Steeltoe.Discovery.HttpClients")] [assembly: InternalsVisibleTo("Steeltoe.Discovery.HttpClients.Test")] diff --git a/src/Logging/src/Abstractions/ConfigurationSchema.json b/src/Logging/src/Abstractions/ConfigurationSchema.json new file mode 100644 index 0000000000..4a14939b1c --- /dev/null +++ b/src/Logging/src/Abstractions/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Logging": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Logging.Abstractions": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Logging/src/Abstractions/Properties/AssemblyInfo.cs b/src/Logging/src/Abstractions/Properties/AssemblyInfo.cs index e74dba04fc..39195e6162 100644 --- a/src/Logging/src/Abstractions/Properties/AssemblyInfo.cs +++ b/src/Logging/src/Abstractions/Properties/AssemblyInfo.cs @@ -3,5 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Logging", "Steeltoe.Logging.Abstractions")] [assembly: InternalsVisibleTo("Steeltoe.Logging.DynamicSerilog.Test")] diff --git a/src/Management/src/Abstractions/ConfigurationSchema.json b/src/Management/src/Abstractions/ConfigurationSchema.json new file mode 100644 index 0000000000..20ca549eec --- /dev/null +++ b/src/Management/src/Abstractions/ConfigurationSchema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Management": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Management.Abstractions": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Management/src/Abstractions/Properties/AssemblyInfo.cs b/src/Management/src/Abstractions/Properties/AssemblyInfo.cs index e92dcc05ab..2aa6de9a8f 100644 --- a/src/Management/src/Abstractions/Properties/AssemblyInfo.cs +++ b/src/Management/src/Abstractions/Properties/AssemblyInfo.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Management", "Steeltoe.Management.Abstractions")] [assembly: InternalsVisibleTo("Steeltoe.Management.Endpoint")] [assembly: InternalsVisibleTo("Steeltoe.Management.Endpoint.Test")] diff --git a/src/Management/src/Tracing/ConfigurationSchema.json b/src/Management/src/Tracing/ConfigurationSchema.json index a36a6c531c..bcccfa21ed 100644 --- a/src/Management/src/Tracing/ConfigurationSchema.json +++ b/src/Management/src/Tracing/ConfigurationSchema.json @@ -1,4 +1,19 @@ { + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Management": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Management.Tracing": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + }, "type": "object", "properties": { "Spring": { diff --git a/src/Management/src/Tracing/Properties/AssemblyInfo.cs b/src/Management/src/Tracing/Properties/AssemblyInfo.cs index 882eca9492..44d789a80e 100644 --- a/src/Management/src/Tracing/Properties/AssemblyInfo.cs +++ b/src/Management/src/Tracing/Properties/AssemblyInfo.cs @@ -7,5 +7,6 @@ using Steeltoe.Common.Configuration; [assembly: ConfigurationSchema("Spring:Application", typeof(SpringApplicationSettings))] +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Management", "Steeltoe.Management.Tracing")] [assembly: InternalsVisibleTo("Steeltoe.Management.Tracing.Test")] diff --git a/src/Security/src/Authentication.JwtBearer/ConfigurationSchema.json b/src/Security/src/Authentication.JwtBearer/ConfigurationSchema.json new file mode 100644 index 0000000000..215b1d2242 --- /dev/null +++ b/src/Security/src/Authentication.JwtBearer/ConfigurationSchema.json @@ -0,0 +1,20 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Security": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Security.Authentication": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Security.Authentication.JwtBearer": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Security/src/Authentication.JwtBearer/Properties/AssemblyInfo.cs b/src/Security/src/Authentication.JwtBearer/Properties/AssemblyInfo.cs index 5dbdb9307b..0b844a8323 100644 --- a/src/Security/src/Authentication.JwtBearer/Properties/AssemblyInfo.cs +++ b/src/Security/src/Authentication.JwtBearer/Properties/AssemblyInfo.cs @@ -3,5 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Security", "Steeltoe.Security.Authentication", "Steeltoe.Security.Authentication.JwtBearer")] [assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.JwtBearer.Test")] diff --git a/src/Security/src/Authentication.OpenIdConnect/ConfigurationSchema.json b/src/Security/src/Authentication.OpenIdConnect/ConfigurationSchema.json new file mode 100644 index 0000000000..59d837acda --- /dev/null +++ b/src/Security/src/Authentication.OpenIdConnect/ConfigurationSchema.json @@ -0,0 +1,20 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Security": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Security.Authentication": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Security.Authentication.OpenIdConnect": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Security/src/Authentication.OpenIdConnect/Properties/AssemblyInfo.cs b/src/Security/src/Authentication.OpenIdConnect/Properties/AssemblyInfo.cs index c087fbbab4..cb4419c6c0 100644 --- a/src/Security/src/Authentication.OpenIdConnect/Properties/AssemblyInfo.cs +++ b/src/Security/src/Authentication.OpenIdConnect/Properties/AssemblyInfo.cs @@ -3,5 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Security", "Steeltoe.Security.Authentication", "Steeltoe.Security.Authentication.OpenIdConnect")] [assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.OpenIdConnect.Test")] diff --git a/src/Security/src/DataProtection.Redis/ConfigurationSchema.json b/src/Security/src/DataProtection.Redis/ConfigurationSchema.json new file mode 100644 index 0000000000..1ccb69c5e4 --- /dev/null +++ b/src/Security/src/DataProtection.Redis/ConfigurationSchema.json @@ -0,0 +1,20 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Steeltoe": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Security": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Security.DataProtection": { + "$ref": "#/definitions/logLevelThreshold" + }, + "Steeltoe.Security.DataProtection.Redis": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + } +} diff --git a/src/Security/src/DataProtection.Redis/Properties/AssemblyInfo.cs b/src/Security/src/DataProtection.Redis/Properties/AssemblyInfo.cs index 022305bba9..ce180d3c47 100644 --- a/src/Security/src/DataProtection.Redis/Properties/AssemblyInfo.cs +++ b/src/Security/src/DataProtection.Redis/Properties/AssemblyInfo.cs @@ -3,5 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using Aspire; + +[assembly: LoggingCategories("Steeltoe", "Steeltoe.Security", "Steeltoe.Security.DataProtection", "Steeltoe.Security.DataProtection.Redis")] [assembly: InternalsVisibleTo("Steeltoe.Security.DataProtection.Redis.Test")] From d95d9bfeed00d270ccd9f217b9cb8a184fe847d4 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:33:08 +0200 Subject: [PATCH 45/81] Bump non-exposed NuGet dependencies (#1670) --- versions.props | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/versions.props b/versions.props index 123a774861..bd806b1215 100644 --- a/versions.props +++ b/versions.props @@ -6,13 +6,13 @@ --> 9.0.* - 6.0.* + 8.0.* 7.2.* - 3.57.* + 3.58.* 5.0.* - 6.1.* + 7.0.* 7.0.* - 3.6.* + 3.7.* 4.20.69 2.5.* 9.6.* @@ -26,7 +26,7 @@ 2.0.* 8.15.* 4.9.* - 18.0.* + 18.4.* 3.2.* 3.1.* From 5d88bb2e7214b38a6e9bd98905ec9f43c25e94fd Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:59:49 +0200 Subject: [PATCH 46/81] Add IDiscoveryClient.InstancesFetched event (#1672) * Add IDiscoveryClient.InstancesFetched event * Consul: don't use UseNetworkInterfaces to determine ports (reverts change from #1666) * Cleanup queries, fix case insensivity --- .../DiscoveryInstancesFetchedEventArgs.cs | 23 +++++ .../src/Common/Discovery/IDiscoveryClient.cs | 5 ++ src/Common/src/Common/PublicAPI.Unshipped.txt | 4 + .../ConfigServerDiscoveryServiceTest.cs | 4 + .../ConfigurationDiscoveryClient.cs | 61 ++++++++++++- .../src/Configuration/PublicAPI.Unshipped.txt | 2 + .../Configuration/ConsulDiscoveryOptions.cs | 3 +- .../src/Consul/ConfigurationSchema.json | 2 +- .../src/Consul/ConsulDiscoveryClient.cs | 7 ++ .../PostConfigureConsulDiscoveryOptions.cs | 2 +- .../src/Consul/PublicAPI.Unshipped.txt | 1 + .../src/Eureka/EurekaDiscoveryClient.cs | 73 +++++++++++++--- .../src/Eureka/PublicAPI.Unshipped.txt | 1 + .../ConfigurationDiscoveryClientTest.cs | 86 +++++++++++++++++++ .../Eureka.Test/EurekaDiscoveryClientTest.cs | 33 +++++-- .../RoundRobinLoadBalancerTest.cs | 8 ++ 16 files changed, 291 insertions(+), 24 deletions(-) create mode 100644 src/Common/src/Common/Discovery/DiscoveryInstancesFetchedEventArgs.cs diff --git a/src/Common/src/Common/Discovery/DiscoveryInstancesFetchedEventArgs.cs b/src/Common/src/Common/Discovery/DiscoveryInstancesFetchedEventArgs.cs new file mode 100644 index 0000000000..2f51827683 --- /dev/null +++ b/src/Common/src/Common/Discovery/DiscoveryInstancesFetchedEventArgs.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +namespace Steeltoe.Common.Discovery; + +/// +/// Provides data for the event. +/// +public sealed class DiscoveryInstancesFetchedEventArgs : EventArgs +{ + /// + /// Gets the updated list of service instances, grouped by service ID. + /// + public IReadOnlyDictionary> InstancesByServiceId { get; } + + public DiscoveryInstancesFetchedEventArgs(IReadOnlyDictionary> instancesByServiceId) + { + ArgumentNullException.ThrowIfNull(instancesByServiceId); + + InstancesByServiceId = instancesByServiceId; + } +} diff --git a/src/Common/src/Common/Discovery/IDiscoveryClient.cs b/src/Common/src/Common/Discovery/IDiscoveryClient.cs index 598095b2a9..bd2b8c3cb8 100644 --- a/src/Common/src/Common/Discovery/IDiscoveryClient.cs +++ b/src/Common/src/Common/Discovery/IDiscoveryClient.cs @@ -14,6 +14,11 @@ public interface IDiscoveryClient /// string Description { get; } + /// + /// Occurs when service instances have been fetched from the discovery server. + /// + public event EventHandler InstancesFetched; + /// /// Gets information used to register the local service instance (this app) to the discovery server. /// diff --git a/src/Common/src/Common/PublicAPI.Unshipped.txt b/src/Common/src/Common/PublicAPI.Unshipped.txt index 7dc5c58110..ddcc76f1b3 100644 --- a/src/Common/src/Common/PublicAPI.Unshipped.txt +++ b/src/Common/src/Common/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ #nullable enable +Steeltoe.Common.Discovery.DiscoveryInstancesFetchedEventArgs +Steeltoe.Common.Discovery.DiscoveryInstancesFetchedEventArgs.DiscoveryInstancesFetchedEventArgs(System.Collections.Generic.IReadOnlyDictionary!>! instancesByServiceId) -> void +Steeltoe.Common.Discovery.DiscoveryInstancesFetchedEventArgs.InstancesByServiceId.get -> System.Collections.Generic.IReadOnlyDictionary!>! +Steeltoe.Common.Discovery.IDiscoveryClient.InstancesFetched -> System.EventHandler! diff --git a/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerDiscoveryServiceTest.cs b/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerDiscoveryServiceTest.cs index 2b193cad38..556dfc475e 100644 --- a/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerDiscoveryServiceTest.cs +++ b/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerDiscoveryServiceTest.cs @@ -114,6 +114,10 @@ private sealed class TestDiscoveryClient : IDiscoveryClient { public string Description => throw new NotImplementedException(); +#pragma warning disable CS0067 // The event is never used + public event EventHandler? InstancesFetched; +#pragma warning restore CS0067 // The event is never used + public Task> GetServiceIdsAsync(CancellationToken cancellationToken) { throw new NotImplementedException(); diff --git a/src/Discovery/src/Configuration/ConfigurationDiscoveryClient.cs b/src/Discovery/src/Configuration/ConfigurationDiscoveryClient.cs index 86aaad4529..37a787c777 100644 --- a/src/Discovery/src/Configuration/ConfigurationDiscoveryClient.cs +++ b/src/Discovery/src/Configuration/ConfigurationDiscoveryClient.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Collections.ObjectModel; using Microsoft.Extensions.Options; using Steeltoe.Common.Discovery; @@ -10,17 +11,69 @@ namespace Steeltoe.Discovery.Configuration; /// /// A discovery client that reads service instances from app configuration. /// -public sealed class ConfigurationDiscoveryClient : IDiscoveryClient +public sealed class ConfigurationDiscoveryClient : IDiscoveryClient, IDisposable { private readonly IOptionsMonitor _optionsMonitor; + private readonly IDisposable? _changeTokenRegistration; public string Description => "A discovery client that returns service instances from app configuration."; + /// + /// Occurs when the configuration of service instances has been reloaded. + /// + public event EventHandler? InstancesFetched; + public ConfigurationDiscoveryClient(IOptionsMonitor optionsMonitor) { ArgumentNullException.ThrowIfNull(optionsMonitor); _optionsMonitor = optionsMonitor; + _changeTokenRegistration = optionsMonitor.OnChange(OnOptionsChanged); + } + + private void OnOptionsChanged(ConfigurationDiscoveryOptions options) + { + if (InstancesFetched != null) + { + ReadOnlyDictionary> instancesByServiceId = ToServiceInstanceMap(options.Services); + var eventArgs = new DiscoveryInstancesFetchedEventArgs(instancesByServiceId); + RaiseFetchEvent(eventArgs); + } + } + + private static ReadOnlyDictionary> ToServiceInstanceMap(IList services) + { + // @formatter:wrap_chained_method_calls chop_always + // @formatter:wrap_before_first_method_call true + + return services + .Where(service => service.ServiceId != null) + .GroupBy(service => service.ServiceId!, StringComparer.OrdinalIgnoreCase) + .ToDictionary(grouping => grouping.Key, grouping => (IReadOnlyList)grouping + .Cast() + .ToList() + .AsReadOnly(), StringComparer.OrdinalIgnoreCase) + .AsReadOnly(); + + // @formatter:wrap_before_first_method_call restore + // @formatter:wrap_chained_method_calls restore + } + + private void RaiseFetchEvent(DiscoveryInstancesFetchedEventArgs eventArgs) + { + // Execute on separate thread, so we won't block the configuration system in case the handler logic is expensive. + ThreadPool.QueueUserWorkItem(_ => + { + try + { + InstancesFetched?.Invoke(this, eventArgs); + } + catch (Exception) + { + // Intentionally left empty. Adding a logger to the constructor is a breaking change. + // Adding an extra constructor confuses the service container. + } + }); } /// @@ -54,4 +107,10 @@ public Task ShutdownAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } + + /// + public void Dispose() + { + _changeTokenRegistration?.Dispose(); + } } diff --git a/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt b/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt index 7dc5c58110..02bc4bc7e9 100644 --- a/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt +++ b/src/Discovery/src/Configuration/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Steeltoe.Discovery.Configuration.ConfigurationDiscoveryClient.Dispose() -> void +Steeltoe.Discovery.Configuration.ConfigurationDiscoveryClient.InstancesFetched -> System.EventHandler? diff --git a/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs b/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs index fd9b8b77d9..93e3b565f5 100644 --- a/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs +++ b/src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs @@ -176,8 +176,7 @@ public sealed class ConsulDiscoveryOptions /// /// Gets or sets a value indicating whether to register with the port number ASP.NET Core is listening on. Default value: true. /// - /// This property is ignored when or is explicitly configured, or when is - /// true. + /// This property is ignored when or is explicitly configured. /// public bool UseAspNetCoreUrls { get; set; } = true; } diff --git a/src/Discovery/src/Consul/ConfigurationSchema.json b/src/Discovery/src/Consul/ConfigurationSchema.json index a00e7f69bf..58acfc8ab2 100644 --- a/src/Discovery/src/Consul/ConfigurationSchema.json +++ b/src/Discovery/src/Consul/ConfigurationSchema.json @@ -188,7 +188,7 @@ }, "UseAspNetCoreUrls": { "type": "boolean", - "description": "Gets or sets a value indicating whether to register with the port number ASP.NET Core is listening on. Default value: true.\n\nThis property is ignored when 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Port' or 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Scheme' is explicitly configured, or when 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.UseNetworkInterfaces' is true." + "description": "Gets or sets a value indicating whether to register with the port number ASP.NET Core is listening on. Default value: true.\n\nThis property is ignored when 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Port' or 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Scheme' is explicitly configured." }, "UseNetworkInterfaces": { "type": "boolean", diff --git a/src/Discovery/src/Consul/ConsulDiscoveryClient.cs b/src/Discovery/src/Consul/ConsulDiscoveryClient.cs index 22d3df0ef3..2521aba036 100644 --- a/src/Discovery/src/Consul/ConsulDiscoveryClient.cs +++ b/src/Discovery/src/Consul/ConsulDiscoveryClient.cs @@ -30,6 +30,13 @@ public sealed class ConsulDiscoveryClient : IDiscoveryClient /// public string Description => "A discovery client for HashiCorp Consul."; + /// + /// This event is never raised. The Consul client doesn't implement caching. + /// +#pragma warning disable CS0067 // The event is never used + public event EventHandler? InstancesFetched; +#pragma warning restore CS0067 // The event is never used + /// /// Initializes a new instance of the class. /// diff --git a/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs b/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs index 91dcd274cc..424cec5f9f 100644 --- a/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs +++ b/src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs @@ -62,7 +62,7 @@ public void PostConfigure(string? name, ConsulDiscoveryOptions options) options.HostName = options.IPAddress; } - if (options is { UseAspNetCoreUrls: true, Port: 0, Scheme: null, UseNetworkInterfaces: false }) + if (options is { UseAspNetCoreUrls: true, Port: 0, Scheme: null }) { ICollection addresses = _configuration.GetListenAddresses(); SetSchemeWithPortFromListenAddresses(options, addresses); diff --git a/src/Discovery/src/Consul/PublicAPI.Unshipped.txt b/src/Discovery/src/Consul/PublicAPI.Unshipped.txt index 7dc5c58110..e1d5475e90 100644 --- a/src/Discovery/src/Consul/PublicAPI.Unshipped.txt +++ b/src/Discovery/src/Consul/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +Steeltoe.Discovery.Consul.ConsulDiscoveryClient.InstancesFetched -> System.EventHandler? diff --git a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs index ac6852233d..e955b3c454 100644 --- a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs +++ b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Collections.ObjectModel; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -69,6 +70,9 @@ internal ApplicationInfoCollection Applications /// public event EventHandler? ApplicationsFetched; + /// + public event EventHandler? InstancesFetched; + public EurekaDiscoveryClient(EurekaApplicationInfoManager appInfoManager, EurekaClient eurekaClient, IOptionsMonitor clientOptionsMonitor, HealthCheckHandlerProvider healthCheckHandlerProvider, TimeProvider timeProvider, ILogger logger) @@ -326,7 +330,8 @@ internal async Task FetchRegistryAsync(bool doFullUpdate, CancellationToken canc await _registryFetchAsyncLock.WaitAsync(cancellationToken); - ApplicationsFetchedEventArgs eventArgs; + ApplicationsFetchedEventArgs? applicationsEventArgs = null; + DiscoveryInstancesFetchedEventArgs? instancesEventArgs = null; try { @@ -344,33 +349,75 @@ internal async Task FetchRegistryAsync(bool doFullUpdate, CancellationToken canc UpdateLastRemoteInstanceStatusFromCache(); - eventArgs = new ApplicationsFetchedEventArgs(_remoteApps); + if (ApplicationsFetched != null) + { + applicationsEventArgs = new ApplicationsFetchedEventArgs(_remoteApps); + } + + if (InstancesFetched != null) + { + ReadOnlyDictionary> instancesByServiceId = ToServiceInstanceMap(_remoteApps); + instancesEventArgs = new DiscoveryInstancesFetchedEventArgs(instancesByServiceId); + } } finally { _registryFetchAsyncLock.Release(); } - OnApplicationsFetched(eventArgs); + if (applicationsEventArgs != null || instancesEventArgs != null) + { + RaiseFetchEvents(applicationsEventArgs, instancesEventArgs); + } } - private void OnApplicationsFetched(ApplicationsFetchedEventArgs? args) + private static ReadOnlyDictionary> ToServiceInstanceMap(ApplicationInfoCollection apps) { - if (args != null) + // @formatter:wrap_chained_method_calls chop_always + // @formatter:wrap_before_first_method_call true + + return apps + .SelectMany(app => app.Instances) + .GroupBy(instance => instance.AppName, StringComparer.OrdinalIgnoreCase) + .ToDictionary(grouping => grouping.Key, grouping => (IReadOnlyList)grouping + .Select(instance => instance.ToServiceInstance()) + .ToList() + .AsReadOnly(), StringComparer.OrdinalIgnoreCase) + .AsReadOnly(); + + // @formatter:wrap_before_first_method_call restore + // @formatter:wrap_chained_method_calls restore + } + + private void RaiseFetchEvents(ApplicationsFetchedEventArgs? applicationsEventArgs, DiscoveryInstancesFetchedEventArgs? instancesEventArgs) + { + // Execute on separate thread, so we won't block the periodic refresh in case the handler logic is expensive. + ThreadPool.QueueUserWorkItem(_ => { - // Execute on separate thread, so we won't block the periodic refresh in case the handler logic is expensive. - ThreadPool.QueueUserWorkItem(_ => + try { - try + if (applicationsEventArgs != null) { - ApplicationsFetched?.Invoke(this, args); + ApplicationsFetched?.Invoke(this, applicationsEventArgs); } - catch (Exception exception) + } + catch (Exception exception) + { + LogFailedToHandleEvent(exception, nameof(ApplicationsFetched)); + } + + try + { + if (instancesEventArgs != null) { - LogFailedToHandleEvent(exception, nameof(ApplicationsFetched)); + InstancesFetched?.Invoke(this, instancesEventArgs); } - }); - } + } + catch (Exception exception) + { + LogFailedToHandleEvent(exception, nameof(InstancesFetched)); + } + }); } internal async Task FetchFullRegistryAsync(CancellationToken cancellationToken) diff --git a/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt b/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt index e09ccfe57f..1379bc4083 100644 --- a/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt +++ b/src/Discovery/src/Eureka/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ #nullable enable Steeltoe.Discovery.Eureka.Configuration.EurekaInstanceOptions.UseAspNetCoreUrls.get -> bool Steeltoe.Discovery.Eureka.Configuration.EurekaInstanceOptions.UseAspNetCoreUrls.set -> void +Steeltoe.Discovery.Eureka.EurekaDiscoveryClient.InstancesFetched -> System.EventHandler? diff --git a/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs b/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs index dcbb000c47..7fdf7aa3df 100644 --- a/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs +++ b/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using FluentAssertions.Extensions; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -188,4 +190,88 @@ public void Does_not_register_multiple_times() services.Count.Should().Be(beforeServiceCount); } + + [Fact] + public async Task InstancesFetched_event_is_raised_after_configuration_change() + { + var fileProvider = new MemoryFileProvider(); + + fileProvider.IncludeAppSettingsJsonFile(""" + { + "Discovery": { + "Services": [ + { + "ServiceId": "serviceA", + "host": "instanceA1", + "port": 443, + "isSecure": true + }, + { + "ServiceId": "serviceA", + "host": "instanceA2", + "port": 443, + "isSecure": true + }, + { + "ServiceId": "serviceB", + "host": "instanceB1", + "port": 443, + "isSecure": true + } + ] + } + } + """); + + WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); + builder.Configuration.AddInMemoryAppSettingsJsonFile(fileProvider); + builder.Services.AddConfigurationDiscoveryClient(); + await using WebApplication webApplication = builder.Build(); + + ConfigurationDiscoveryClient discoveryClient = webApplication.Services.GetServices().OfType().Single(); + int eventCount = 0; + DiscoveryInstancesFetchedEventArgs? eventArgs = null; + + discoveryClient.InstancesFetched += (_, args) => + { + eventCount++; + eventArgs = args; + }; + + fileProvider.ReplaceAppSettingsJsonFile(""" + { + "Discovery": { + "Services": [ + { + "ServiceId": "serviceA", + "host": "instanceA1", + "port": 443, + "isSecure": true + }, + { + "ServiceId": "serviceB", + "host": "instanceB1", + "port": 443, + "isSecure": true + }, + { + "ServiceId": "serviceB", + "host": "instanceB2", + "port": 443, + "isSecure": true + } + ] + } + } + """); + + fileProvider.NotifyChanged(); + + SpinWait.SpinUntil(() => eventCount == 1, 5.Seconds()).Should().BeTrue(); + + eventArgs.Should().NotBeNull(); + eventArgs.InstancesByServiceId.Should().HaveCount(2); + eventArgs.InstancesByServiceId.Should().ContainKey("ServiceA").WhoseValue.Should().HaveCount(1); + eventArgs.InstancesByServiceId.Should().ContainKey("ServiceB").WhoseValue.Should().HaveCount(2); + } } diff --git a/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs b/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs index 84f39e1df6..1891be394b 100644 --- a/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs +++ b/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs @@ -88,7 +88,7 @@ public sealed class EurekaDiscoveryClientTest "instance": [ { "instanceId": "localhost:foo", - "hostName": "localhost", + "hostName": "modified-host", "app": "FOO", "ipAddr": "192.168.56.1", "status": "UP", @@ -635,7 +635,7 @@ public async Task Can_manipulate_request_headers() } [Fact] - public async Task ApplicationEventsFireOnChangeDuringFetch() + public async Task ApplicationEventsFireAfterFetch() { var appSettings = new Dictionary { @@ -655,17 +655,38 @@ public async Task ApplicationEventsFireOnChangeDuringFetch() webApplication.Services.GetRequiredService().Using(handler); var discoveryClient = webApplication.Services.GetRequiredService(); - int eventCount = 0; + int applicationsEventCount = 0; + ApplicationsFetchedEventArgs? applicationsEventArgs = null; + int instancesEventCount = 0; + DiscoveryInstancesFetchedEventArgs? instancesEventArgs = null; + + discoveryClient.ApplicationsFetched += (_, args) => + { + applicationsEventCount++; + applicationsEventArgs = args; + }; - discoveryClient.ApplicationsFetched += (_, _) => eventCount++; + discoveryClient.InstancesFetched += (_, args) => + { + instancesEventCount++; + instancesEventArgs = args; + }; await discoveryClient.FetchRegistryAsync(true, TestContext.Current.CancellationToken); - SpinWait.SpinUntil(() => eventCount == 1, 5.Seconds()).Should().BeTrue(); + SpinWait.SpinUntil(() => applicationsEventCount == 1 && instancesEventCount == 1, 5.Seconds()).Should().BeTrue(); await discoveryClient.FetchRegistryAsync(false, TestContext.Current.CancellationToken); - SpinWait.SpinUntil(() => eventCount == 2, 5.Seconds()).Should().BeTrue(); + SpinWait.SpinUntil(() => applicationsEventCount == 2 && instancesEventCount == 2, 5.Seconds()).Should().BeTrue(); handler.Mock.VerifyNoOutstandingExpectation(); + + applicationsEventArgs.Should().NotBeNull(); + InstanceInfo newInstanceInfo = applicationsEventArgs.Applications.Should().ContainSingle().Which.Instances.Should().ContainSingle().Which; + newInstanceInfo.ActionType.Should().Be(ActionType.Modified); + + instancesEventArgs.Should().NotBeNull(); + IServiceInstance newServiceInstance = instancesEventArgs.InstancesByServiceId.Should().ContainKey("foo").WhoseValue.Should().ContainSingle().Which; + newServiceInstance.Uri.ToString().Should().Be("http://modified-host:8080/"); } private sealed class ExtraRequestHeadersDelegatingHandler : DelegatingHandler diff --git a/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs b/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs index 0f41aab316..afb96b2624 100644 --- a/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs +++ b/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs @@ -257,6 +257,10 @@ private sealed class TestDiscoveryClient(IServiceInstance? instance = null) : ID public string Description => throw new NotImplementedException(); +#pragma warning disable CS0067 // The event is never used + public event EventHandler? InstancesFetched; +#pragma warning restore CS0067 // The event is never used + public Task> GetServiceIdsAsync(CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -289,6 +293,10 @@ private sealed class ThrowingDiscoveryClient : IDiscoveryClient { public string Description => throw new NotImplementedException(); +#pragma warning disable CS0067 // The event is never used + public event EventHandler? InstancesFetched; +#pragma warning restore CS0067 // The event is never used + public IServiceInstance GetLocalServiceInstance() { throw new InvalidOperationException(); From b75cce021816c9ab3c785c6b5fcecd683193d114 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:06:16 +0200 Subject: [PATCH 47/81] Follow-up changes for new `IDiscoveryClient.InstancesFetched` event (#1677) * Fixed: Adapt EurekaDiscoveryClient.InstancesFetched event args match what's returned by GetInstancesAsync: - Keyed by VIP addresses instead of app names - Take ReturnUpInstancesOnly into account - Don't return an empty collection when no instances are found * Adapt load balancers to ignore duplicate URIs --- .../src/Eureka/EurekaDiscoveryClient.cs | 27 ++-- .../LoadBalancers/ServiceInstancesResolver.cs | 19 +++ .../ConfigurationDiscoveryClientTest.cs | 4 +- .../Eureka.Test/EurekaDiscoveryClientTest.cs | 110 ++++++++++++++-- .../RoundRobinLoadBalancerTest.cs | 118 +++++++++++++++--- 5 files changed, 232 insertions(+), 46 deletions(-) diff --git a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs index e955b3c454..8076affe7a 100644 --- a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs +++ b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs @@ -373,20 +373,19 @@ internal async Task FetchRegistryAsync(bool doFullUpdate, CancellationToken canc private static ReadOnlyDictionary> ToServiceInstanceMap(ApplicationInfoCollection apps) { - // @formatter:wrap_chained_method_calls chop_always - // @formatter:wrap_before_first_method_call true - - return apps - .SelectMany(app => app.Instances) - .GroupBy(instance => instance.AppName, StringComparer.OrdinalIgnoreCase) - .ToDictionary(grouping => grouping.Key, grouping => (IReadOnlyList)grouping - .Select(instance => instance.ToServiceInstance()) - .ToList() - .AsReadOnly(), StringComparer.OrdinalIgnoreCase) - .AsReadOnly(); - - // @formatter:wrap_before_first_method_call restore - // @formatter:wrap_chained_method_calls restore + var dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + foreach (string vipAddress in apps.VipInstanceMap.Keys.ToArray()) + { + ReadOnlyCollection instancesByVipAddress = apps.GetInstancesByVipAddress(vipAddress); + + if (instancesByVipAddress.Count > 0) + { + dictionary[vipAddress] = instancesByVipAddress.Select(instance => instance.ToServiceInstance()).ToList().AsReadOnly(); + } + } + + return new ReadOnlyDictionary>(dictionary); } private void RaiseFetchEvents(ApplicationsFetchedEventArgs? applicationsEventArgs, DiscoveryInstancesFetchedEventArgs? instancesEventArgs) diff --git a/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs b/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs index e1231d171d..79a97ddc46 100644 --- a/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs +++ b/src/Discovery/src/HttpClients/LoadBalancers/ServiceInstancesResolver.cs @@ -83,6 +83,7 @@ public async Task> ResolveInstancesAsync(string serviceI if (instancesFromCache != null) { + instancesFromCache = RemoveDuplicatesByUri(instancesFromCache); LogReturningInstancesFromCache(instancesFromCache.Count); return instancesFromCache; } @@ -103,6 +104,8 @@ public async Task> ResolveInstancesAsync(string serviceI } } + instances = RemoveDuplicatesByUri(instances); + if (_distributedCache != null) { byte[] cacheValue = ToCacheValue(instances); @@ -112,6 +115,22 @@ public async Task> ResolveInstancesAsync(string serviceI return instances; } + private static List RemoveDuplicatesByUri(List instances) + { + var seenUris = new HashSet(StringComparer.OrdinalIgnoreCase); + var result = new List(); + + foreach (IServiceInstance instance in instances) + { + if (seenUris.Add(instance.Uri.AbsoluteUri)) + { + result.Add(instance); + } + } + + return result; + } + private static List? FromCacheValue(byte[]? cacheValue) { if (cacheValue is { Length: > 0 }) diff --git a/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs b/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs index 7fdf7aa3df..f83e5b555c 100644 --- a/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs +++ b/src/Discovery/test/Configuration.Test/ConfigurationDiscoveryClientTest.cs @@ -229,13 +229,13 @@ public async Task InstancesFetched_event_is_raised_after_configuration_change() await using WebApplication webApplication = builder.Build(); ConfigurationDiscoveryClient discoveryClient = webApplication.Services.GetServices().OfType().Single(); - int eventCount = 0; DiscoveryInstancesFetchedEventArgs? eventArgs = null; + int eventCount = 0; discoveryClient.InstancesFetched += (_, args) => { - eventCount++; eventArgs = args; + Interlocked.Increment(ref eventCount); }; fileProvider.ReplaceAppSettingsJsonFile(""" diff --git a/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs b/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs index 1891be394b..f98e97c2e0 100644 --- a/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs +++ b/src/Discovery/test/Eureka.Test/EurekaDiscoveryClientTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Globalization; using System.Net; using FluentAssertions.Extensions; using Microsoft.AspNetCore.Builder; @@ -655,38 +656,127 @@ public async Task ApplicationEventsFireAfterFetch() webApplication.Services.GetRequiredService().Using(handler); var discoveryClient = webApplication.Services.GetRequiredService(); - int applicationsEventCount = 0; ApplicationsFetchedEventArgs? applicationsEventArgs = null; - int instancesEventCount = 0; + int applicationsEventCount = 0; DiscoveryInstancesFetchedEventArgs? instancesEventArgs = null; + int instancesEventCount = 0; discoveryClient.ApplicationsFetched += (_, args) => { - applicationsEventCount++; applicationsEventArgs = args; + Interlocked.Increment(ref applicationsEventCount); }; discoveryClient.InstancesFetched += (_, args) => { - instancesEventCount++; instancesEventArgs = args; + Interlocked.Increment(ref instancesEventCount); }; await discoveryClient.FetchRegistryAsync(true, TestContext.Current.CancellationToken); SpinWait.SpinUntil(() => applicationsEventCount == 1 && instancesEventCount == 1, 5.Seconds()).Should().BeTrue(); + applicationsEventArgs.Should().NotBeNull(); + InstanceInfo oldInstanceFromAppEvent = applicationsEventArgs.Applications.Should().ContainSingle().Which.Instances.Should().ContainSingle().Which; + oldInstanceFromAppEvent.ActionType.Should().Be(ActionType.Added); + + instancesEventArgs.Should().NotBeNull(); + IServiceInstance oldInstanceFromEvent = instancesEventArgs.InstancesByServiceId.Should().ContainKey("foo").WhoseValue.Should().ContainSingle().Which; + oldInstanceFromEvent.Uri.ToString().Should().Be("http://localhost:8080/"); + + IList oldInstancesFromGet = await discoveryClient.GetInstancesAsync("foo", TestContext.Current.CancellationToken); + oldInstancesFromGet.Should().ContainSingle().Which.Uri.Should().Be(oldInstanceFromEvent.Uri); + await discoveryClient.FetchRegistryAsync(false, TestContext.Current.CancellationToken); SpinWait.SpinUntil(() => applicationsEventCount == 2 && instancesEventCount == 2, 5.Seconds()).Should().BeTrue(); + InstanceInfo newInstanceFromAppEvent = applicationsEventArgs.Applications.Should().ContainSingle().Which.Instances.Should().ContainSingle().Which; + newInstanceFromAppEvent.ActionType.Should().Be(ActionType.Modified); + + IServiceInstance newInstanceFromEvent = instancesEventArgs.InstancesByServiceId.Should().ContainKey("foo").WhoseValue.Should().ContainSingle().Which; + newInstanceFromEvent.Uri.ToString().Should().Be("http://modified-host:8080/"); + + IList newInstancesFromGet = await discoveryClient.GetInstancesAsync("foo", TestContext.Current.CancellationToken); + newInstancesFromGet.Should().ContainSingle().Which.Uri.Should().Be(newInstanceFromEvent.Uri); + handler.Mock.VerifyNoOutstandingExpectation(); + } - applicationsEventArgs.Should().NotBeNull(); - InstanceInfo newInstanceInfo = applicationsEventArgs.Applications.Should().ContainSingle().Which.Instances.Should().ContainSingle().Which; - newInstanceInfo.ActionType.Should().Be(ActionType.Modified); + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task InstancesFetched_returns_same_data_as_GetInstancesAsync(bool filterOnlyUpInstances) + { + const string registryJson = """ + { + "applications": { + "application": [ + { + "name": "ignored", + "instance": [ + { + "instanceId": "id1", + "hostName": "h1", + "app": "app1", + "ipAddr": "10.0.0.1", + "status": "UP", + "dataCenterInfo": { + "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", + "name": "MyOwn" + }, + "vipAddress": "vapp1" + }, + { + "instanceId": "id2", + "hostName": "h2", + "app": "app1", + "ipAddr": "10.0.0.2", + "status": "DOWN", + "dataCenterInfo": { + "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", + "name": "MyOwn" + }, + "vipAddress": "vapp1" + } + ] + } + ] + } + } + """; - instancesEventArgs.Should().NotBeNull(); - IServiceInstance newServiceInstance = instancesEventArgs.InstancesByServiceId.Should().ContainKey("foo").WhoseValue.Should().ContainSingle().Which; - newServiceInstance.Uri.ToString().Should().Be("http://modified-host:8080/"); + var appSettings = new Dictionary + { + ["Eureka:Client:ShouldFetchRegistry"] = "false", + ["Eureka:Client:ShouldRegisterWithEureka"] = "false", + ["Eureka:Client:ShouldFilterOnlyUpInstances"] = filterOnlyUpInstances.ToString(CultureInfo.InvariantCulture) + }; + + WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); + builder.Configuration.AddInMemoryCollection(appSettings); + builder.Services.AddEurekaDiscoveryClient(); + + var handler = new DelegateToMockHttpClientHandler(); + handler.Mock.Expect(HttpMethod.Get, "http://localhost:8761/eureka/apps").Respond("application/json", registryJson); + + await using WebApplication webApplication = builder.Build(); + webApplication.Services.GetRequiredService().Using(handler); + + var discoveryClient = webApplication.Services.GetRequiredService(); + DiscoveryInstancesFetchedEventArgs? eventArgs = null; + discoveryClient.InstancesFetched += (_, args) => eventArgs = args; + + await discoveryClient.FetchRegistryAsync(true, TestContext.Current.CancellationToken); + SpinWait.SpinUntil(() => eventArgs != null, 5.Seconds()).Should().BeTrue(); + + eventArgs.Should().NotBeNull(); + + IList instancesFromGet = await discoveryClient.GetInstancesAsync("vapp1", TestContext.Current.CancellationToken); + IReadOnlyList instancesFromEvent = eventArgs.InstancesByServiceId.Should().ContainKey("vapp1").WhoseValue; + + instancesFromEvent.Should().BeEquivalentTo(instancesFromGet); + + handler.Mock.VerifyNoOutstandingExpectation(); } private sealed class ExtraRequestHeadersDelegatingHandler : DelegatingHandler diff --git a/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs b/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs index afb96b2624..c84ead2d37 100644 --- a/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs +++ b/src/Discovery/test/HttpClients.Test/LoadBalancers/RoundRobinLoadBalancerTest.cs @@ -112,6 +112,67 @@ public async Task ResolveServiceInstanceAsync_FindsService_ReturnsURI() result.Should().Be(new Uri("https://foundit:5555/test/bar/foo?test=1&test2=2")); } + [Fact] + public async Task ResolveServiceInstanceAsync_RemovesDuplicates_CaseInsensitive() + { + var client = new TestDiscoveryClient([ + new TestServiceInstance(new Uri("HTTPS://CASE-HOST:1234/")), + new TestServiceInstance(new Uri("https://case-host:1234/")) + ], "svc"); + + var resolver = new ServiceInstancesResolver([client], NullLogger.Instance); + var loadBalancer = new RoundRobinLoadBalancer(resolver, null, null, NullLogger.Instance); + + Uri first = await loadBalancer.ResolveServiceInstanceAsync(new Uri("https://svc/api"), TestContext.Current.CancellationToken); + Uri second = await loadBalancer.ResolveServiceInstanceAsync(new Uri("https://svc/api"), TestContext.Current.CancellationToken); + + first.Should().Be(second); + } + + [Fact] + public async Task ResolveServiceInstanceAsync_RemovesDuplicates_CaseInsensitive_MultipleDiscoveryClients() + { + var targetUri = new Uri("https://merged:1/"); + var clientA = new TestDiscoveryClient([new TestServiceInstance(targetUri)], "svc"); + var clientB = new TestDiscoveryClient([new TestServiceInstance(targetUri)], "svc"); + + var resolver = new ServiceInstancesResolver([ + clientA, + clientB + ], NullLogger.Instance); + + var loadBalancer = new RoundRobinLoadBalancer(resolver, null, null, NullLogger.Instance); + + Uri first = await loadBalancer.ResolveServiceInstanceAsync(new Uri("https://svc/api"), TestContext.Current.CancellationToken); + Uri second = await loadBalancer.ResolveServiceInstanceAsync(new Uri("https://svc/api"), TestContext.Current.CancellationToken); + + first.Should().Be(second); + } + + [Fact] + public async Task ResolveServiceInstanceAsync_RemovesDuplicates_Rotates() + { + var shared = new Uri("https://shared:100/"); + var other = new Uri("https://other:100/"); + + var client = new TestDiscoveryClient([ + new TestServiceInstance(shared), + new TestServiceInstance(shared), + new TestServiceInstance(other) + ], "svc"); + + var resolver = new ServiceInstancesResolver([client], NullLogger.Instance); + var loadBalancer = new RoundRobinLoadBalancer(resolver, null, null, NullLogger.Instance); + + Uri first = await loadBalancer.ResolveServiceInstanceAsync(new Uri("https://svc/api"), TestContext.Current.CancellationToken); + Uri second = await loadBalancer.ResolveServiceInstanceAsync(new Uri("https://svc/api"), TestContext.Current.CancellationToken); + Uri third = await loadBalancer.ResolveServiceInstanceAsync(new Uri("https://svc/api"), TestContext.Current.CancellationToken); + + first.Should().Be(new Uri(shared, "api")); + second.Should().Be(new Uri(other, "api")); + third.Should().Be(first); + } + [Fact] public async Task ResolveServiceInstanceAsync_SkipsOverThrowingDiscoveryClients() { @@ -134,15 +195,28 @@ public async Task ResolveServiceInstanceAsync_SkipsOverThrowingDiscoveryClients( [Fact] public async Task ResolveServiceInstanceAsync_CachesInstances() { - ConfigurationDiscoveryOptions options = CreateTestServiceInstances(); + var options = new ConfigurationDiscoveryOptions + { + Services = + { + new ConfigurationServiceInstance + { + ServiceId = "fruit-service", + Host = "before-reload", + Port = 8000, + IsSecure = true + } + } + }; + TestOptionsMonitor optionsMonitor = TestOptionsMonitor.Create(options); var client = new ConfigurationDiscoveryClient(optionsMonitor); IDistributedCache distributedCache = GetCache(); var resolver = new ServiceInstancesResolver([client], distributedCache, null, NullLogger.Instance); var loadBalancer = new RoundRobinLoadBalancer(resolver, null, null, NullLogger.Instance); - Uri fruitUri = await loadBalancer.ResolveServiceInstanceAsync(new Uri("https://fruit-service/api"), TestContext.Current.CancellationToken); - fruitUri.Should().Be("https://fruit-ball:8000/api"); + Uri first = await loadBalancer.ResolveServiceInstanceAsync(new Uri("https://fruit-service/api"), TestContext.Current.CancellationToken); + first.Should().Be("https://before-reload:8000/api"); optionsMonitor.Change(new ConfigurationDiscoveryOptions { @@ -151,15 +225,15 @@ public async Task ResolveServiceInstanceAsync_CachesInstances() new ConfigurationServiceInstance { ServiceId = "fruit-service", - Host = "CHANGED", + Host = "after-reload", Port = 8000, IsSecure = true } } }); - fruitUri = await loadBalancer.ResolveServiceInstanceAsync(new Uri("https://fruit-service/api"), TestContext.Current.CancellationToken); - fruitUri.Should().Be("https://fruit-ball:8000/api"); + Uri second = await loadBalancer.ResolveServiceInstanceAsync(new Uri("https://fruit-service/api"), TestContext.Current.CancellationToken); + second.Should().Be(first); } private static ConfigurationDiscoveryOptions CreateTestServiceInstances() @@ -251,9 +325,10 @@ private sealed class TestServiceInstance(Uri uri) : IServiceInstance public IReadOnlyDictionary Metadata => throw new NotImplementedException(); } - private sealed class TestDiscoveryClient(IServiceInstance? instance = null) : IDiscoveryClient + private sealed class TestDiscoveryClient(IList instances, string? serviceId = null) : IDiscoveryClient { - private readonly IServiceInstance? _instance = instance; + private readonly IList _instances = instances; + private readonly string? _serviceId = serviceId; public string Description => throw new NotImplementedException(); @@ -261,31 +336,34 @@ private sealed class TestDiscoveryClient(IServiceInstance? instance = null) : ID public event EventHandler? InstancesFetched; #pragma warning restore CS0067 // The event is never used - public Task> GetServiceIdsAsync(CancellationToken cancellationToken) + public TestDiscoveryClient() + : this([]) { - throw new NotImplementedException(); } - public Task> GetInstancesAsync(string serviceId, CancellationToken cancellationToken) + public TestDiscoveryClient(IServiceInstance instance, string? serviceId = null) + : this([instance], serviceId) { - IList instances = []; + } - if (_instance != null) - { - instances.Add(_instance); - } + public Task> GetServiceIdsAsync(CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } - return Task.FromResult(instances); + public Task> GetInstancesAsync(string serviceId, CancellationToken cancellationToken) + { + return Task.FromResult(_serviceId == null || serviceId == _serviceId ? _instances : []); } - public IServiceInstance GetLocalServiceInstance() + public IServiceInstance? GetLocalServiceInstance() { - throw new NotImplementedException(); + return null; } public Task ShutdownAsync(CancellationToken cancellationToken) { - throw new NotImplementedException(); + return Task.CompletedTask; } } From e46e6a5d258e0d1dbc310c8d9237a19edebfa280 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:05:04 +0200 Subject: [PATCH 48/81] Gracefully handle when unable to fetch access token in Eureka and Config Server (#1679) --- .../ConfigServerConfigurationProvider.cs | 43 +++++++++++++------ ...ServerConfigurationProviderTest.Loading.cs | 33 ++++++++++++++ .../ConfigServerConfigurationProviderTest.cs | 20 ++++++--- src/Discovery/src/Eureka/EurekaClient.cs | 35 +++++++++++---- .../Eureka.Test/Transport/EurekaClientTest.cs | 36 ++++++++++++++++ 5 files changed, 142 insertions(+), 25 deletions(-) diff --git a/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs b/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs index 2246ff3e45..9a24046abe 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs @@ -593,7 +593,7 @@ internal async Task ShutdownAsync(CancellationToken cancellationToken) /// /// The HttpRequestMessage built from the path. /// - internal async Task GetRequestMessageAsync(ConfigServerClientOptions optionsSnapshot, Uri requestUri, + internal async Task GetConfigServerRequestMessageAsync(ConfigServerClientOptions optionsSnapshot, Uri requestUri, CancellationToken cancellationToken) { var uriWithoutUserInfo = new Uri(requestUri.GetComponents(UriComponents.HttpRequestUrl, UriFormat.UriEscaped)); @@ -641,18 +641,34 @@ internal async Task GetRequestMessageAsync(ConfigServerClien foreach (Uri requestUri in requestUris) { - // Make Config Server URI from settings - Uri uri = BuildConfigServerUri(optionsSnapshot, requestUri, label); + try + { + // Make Config Server URI from settings + Uri uri = BuildConfigServerUri(optionsSnapshot, requestUri, label); + + LogTryingToConnect(uri.ToMaskedString()); + HttpRequestMessage request; + + try + { + // Get the request message (potentially fetches access token) + LogBuildingHttpRequest(); + request = await GetConfigServerRequestMessageAsync(optionsSnapshot, uri, cancellationToken); + } + catch (Exception exception) when (!exception.IsCancellation()) + { + if (!string.IsNullOrEmpty(optionsSnapshot.AccessTokenUri)) + { + var accessTokenUri = new Uri(optionsSnapshot.AccessTokenUri); + LogFailedToFetchAccessToken(exception, accessTokenUri.ToMaskedString()); - LogTryingToConnect(uri.ToMaskedString()); + continue; + } - // Get the request message - LogBuildingHttpRequest(); - HttpRequestMessage request = await GetRequestMessageAsync(optionsSnapshot, uri, cancellationToken); + throw; + } - // Invoke Config Server - try - { + // Invoke Config Server LogSendingHttpRequest(); using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken); @@ -808,7 +824,7 @@ internal async Task RefreshVaultTokenAsync(ConfigServerClientOptions optionsSnap { using HttpClient httpClient = CreateHttpClient(optionsSnapshot); - Uri uri = GetVaultRenewUri(optionsSnapshot); + Uri uri = BuildVaultRenewUri(optionsSnapshot); HttpRequestMessage message = await GetVaultRenewRequestMessageAsync(optionsSnapshot, uri, cancellationToken); LogRenewingVaultToken(obscuredToken, optionsSnapshot.TokenTtl, uri.ToMaskedString()); @@ -825,7 +841,7 @@ internal async Task RefreshVaultTokenAsync(ConfigServerClientOptions optionsSnap } } - private static Uri GetVaultRenewUri(ConfigServerClientOptions optionsSnapshot) + private static Uri BuildVaultRenewUri(ConfigServerClientOptions optionsSnapshot) { string baseUri = optionsSnapshot.Uri!.Split(',')[0].Trim(); @@ -1022,6 +1038,9 @@ private void ShutdownTimers() [LoggerMessage(Level = LogLevel.Debug, Message = "Fetched access token from {AccessTokenUri}.")] private partial void LogAccessTokenFetched(string accessTokenUri); + [LoggerMessage(Level = LogLevel.Warning, Message = "Failed to fetch access token from '{AccessTokenUri}'.")] + private partial void LogFailedToFetchAccessToken(Exception exception, string accessTokenUri); + [LoggerMessage(Level = LogLevel.Trace, Message = "Entered {Method}.")] private partial void LogRemoteLoadEntered(string method); diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Loading.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Loading.cs index 257fdb3821..806e869a61 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Loading.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Loading.cs @@ -8,6 +8,7 @@ using System.Text; using FluentAssertions.Extensions; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using RichardSzalay.MockHttp; using Steeltoe.Common.TestResources; @@ -448,6 +449,38 @@ public void Load_MultipleConfigServers_SocketError_FallsBackToNextServer() value.Should().Be("value1"); } + [Fact] + public void Load_MultipleConfigServers_SocketErrorFromAccessTokenUri_LogsWarnings() + { + using var loggerProvider = new CapturingLoggerProvider((_, level) => level == LogLevel.Warning); + using var loggerFactory = new LoggerFactory([loggerProvider]); + + using var handler = new DelegateToMockHttpClientHandler(); + + handler.Mock.When(HttpMethod.Get, "http://auth-server.com") + .Throw(new HttpRequestException("Connection refused", new SocketException((int)SocketError.ConnectionRefused))); + + var options = new ConfigServerClientOptions + { + Name = "myName", + AccessTokenUri = "http://auth-server.com", + Uri = "http://config-server1:8888,http://config-server2:8888" + }; + + using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, loggerFactory); + provider.Load(); + + IList logMessages = loggerProvider.GetAll(); + + logMessages.Should().BeEquivalentTo([ + $"WARN {typeof(ConfigServerConfigurationProvider)}: Failed to fetch access token from 'http://auth-server.com/'.", + $"WARN {typeof(ConfigServerConfigurationProvider)}: Failed to fetch access token from 'http://auth-server.com/'.", + $"WARN {typeof(ConfigServerConfigurationProvider)}: Failed fetching remote configuration from server(s)." + ], assertionOptions => assertionOptions.WithStrictOrdering()); + + provider.InnerData.Should().BeEmpty(); + } + [Fact] public void Load_IdenticalData_DoesNotTriggerReload() { diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs index 5cccbdb791..7b8c950a39 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs @@ -152,7 +152,9 @@ public async Task GetRequestMessage_AddsBasicAuthIfUserNameAndPasswordInURL() provider.Load(); Uri requestUri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null); - HttpRequestMessage request = await provider.GetRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); + + HttpRequestMessage request = + await provider.GetConfigServerRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); request.Method.Should().Be(HttpMethod.Get); request.RequestUri.Should().Be(requestUri); @@ -177,7 +179,9 @@ public async Task GetRequestMessage_AddsBasicAuthIfUserNameAndPasswordInSettings provider.Load(); Uri requestUri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null); - HttpRequestMessage request = await provider.GetRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); + + HttpRequestMessage request = + await provider.GetConfigServerRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); request.Method.Should().Be(HttpMethod.Get); request.RequestUri.Should().Be(requestUri); @@ -202,7 +206,9 @@ public async Task GetRequestMessage_BasicAuthInSettingsOverridesUserNameAndPassw provider.Load(); Uri requestUri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null); - HttpRequestMessage request = await provider.GetRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); + + HttpRequestMessage request = + await provider.GetConfigServerRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); request.Method.Should().Be(HttpMethod.Get); request.RequestUri.Should().Be(requestUri); @@ -225,7 +231,9 @@ public async Task GetRequestMessage_AddsVaultToken_IfNeeded() provider.Load(); Uri requestUri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri!), null); - HttpRequestMessage request = await provider.GetRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); + + HttpRequestMessage request = + await provider.GetConfigServerRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); request.Method.Should().Be(HttpMethod.Get); request.RequestUri.Should().Be(requestUri); @@ -260,7 +268,9 @@ public async Task GetRequestMessage_AddsBearerToken_WhenAccessTokenUriIsSet() using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); Uri requestUri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null); - HttpRequestMessage request = await provider.GetRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); + + HttpRequestMessage request = + await provider.GetConfigServerRequestMessageAsync(provider.ClientOptions, requestUri, TestContext.Current.CancellationToken); handler.Mock.VerifyNoOutstandingExpectation(); diff --git a/src/Discovery/src/Eureka/EurekaClient.cs b/src/Discovery/src/Eureka/EurekaClient.cs index ad4b97e420..59bc33dc9d 100644 --- a/src/Discovery/src/Eureka/EurekaClient.cs +++ b/src/Discovery/src/Eureka/EurekaClient.cs @@ -236,7 +236,24 @@ private async Task ExecuteRequestAsync(HttpMethod method, stri Uri requestUri = GetRequestUri(serviceUri, path, queryString); HttpContent? requestContent = requestBody != null ? new StringContent(requestBody, Encoding.UTF8, MediaType) : null; - HttpRequestMessage request = await GetRequestMessageAsync(method, requestUri, requestContent, cancellationToken); + HttpRequestMessage request; + + try + { + request = await GetRequestMessageAsync(clientOptions, method, requestUri, requestContent, cancellationToken); + } + catch (Exception exception) when (!exception.IsCancellation()) + { + if (!string.IsNullOrEmpty(clientOptions.AccessTokenUri)) + { + var accessTokenUri = new Uri(clientOptions.AccessTokenUri); + LogFailedToFetchAccessToken(exception, accessTokenUri.ToMaskedString(), attempt); + + continue; + } + + throw; + } if (!string.IsNullOrEmpty(requestBody)) { @@ -306,7 +323,8 @@ private static Uri GetRequestUri(Uri baseUri, string path, IDictionary GetRequestMessageAsync(HttpMethod method, Uri requestUri, HttpContent? content, CancellationToken cancellationToken) + private async Task GetRequestMessageAsync(EurekaClientOptions optionsSnapshot, HttpMethod method, Uri requestUri, HttpContent? content, + CancellationToken cancellationToken) { var uriWithoutUserInfo = new Uri(requestUri.GetComponents(UriComponents.HttpRequestUrl, UriFormat.UriEscaped)); var requestMessage = new HttpRequestMessage(method, uriWithoutUserInfo); @@ -320,15 +338,13 @@ private async Task GetRequestMessageAsync(HttpMethod method, } else { - EurekaClientOptions clientOptions = _optionsMonitor.CurrentValue; - - if (!string.IsNullOrEmpty(clientOptions.AccessTokenUri)) + if (!string.IsNullOrEmpty(optionsSnapshot.AccessTokenUri)) { using HttpClient httpClient = CreateHttpClient("AccessTokenForEureka", GetAccessTokenTimeout); - var accessTokenUri = new Uri(clientOptions.AccessTokenUri); + var accessTokenUri = new Uri(optionsSnapshot.AccessTokenUri); - string accessToken = await httpClient.GetAccessTokenAsync(accessTokenUri, clientOptions.ClientId, - clientOptions.ClientSecret, cancellationToken); + string accessToken = + await httpClient.GetAccessTokenAsync(accessTokenUri, optionsSnapshot.ClientId, optionsSnapshot.ClientSecret, cancellationToken); LogAccessTokenFetched(accessTokenUri.ToMaskedString()); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); @@ -371,4 +387,7 @@ private async Task GetRequestMessageAsync(HttpMethod method, [LoggerMessage(Level = LogLevel.Debug, Message = "Fetched access token from '{AccessTokenUri}'.")] private partial void LogAccessTokenFetched(string accessTokenUri); + + [LoggerMessage(Level = LogLevel.Warning, Message = "Failed to fetch access token from '{AccessTokenUri}' in attempt {Attempt}.")] + private partial void LogFailedToFetchAccessToken(Exception exception, string accessTokenUri, int attempt); } diff --git a/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs b/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs index b69254588b..af880a4275 100644 --- a/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs +++ b/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs @@ -194,6 +194,42 @@ public async Task RegisterAsync_ThrowsOnUnreachableServer() $"WARN {typeof(EurekaClient)}: Failed to execute HTTP POST request to 'http://host-that-does-not-exist.net:9999/apps/FOOBAR' in attempt 1."); } + [Fact] + public async Task RegisterAsync_ThrowsOnUnreachableAccessTokenServer() + { + using var capturingLoggerProvider = new CapturingLoggerProvider(category => category.StartsWith("Steeltoe.", StringComparison.Ordinal)); + + var services = new ServiceCollection(); + services.AddLogging(options => options.SetMinimumLevel(LogLevel.Trace).AddProvider(capturingLoggerProvider)); + services.AddOptions().Configure(options => options.AccessTokenUri = "http://host-that-does-not-exist.net:9999/"); + services.AddSingleton(new TestHttpClientFactory()); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(TimeProvider.System); + + await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + var client = serviceProvider.GetRequiredService(); + + var instance = new InstanceInfo("some", "FOOBAR", "localhost", "127.0.0.1", new DataCenterInfo(), TimeProvider.System) + { + NonSecurePort = 8080, + IsNonSecurePortEnabled = true, + SecurePort = 9090, + IsSecurePortEnabled = false, + LastUpdatedTimeUtc = new DateTime(638_440_245_328_236_418, DateTimeKind.Utc), + LastDirtyTimeUtc = new DateTime(638_440_245_328_236_418, DateTimeKind.Utc) + }; + + Func asyncAction = async () => await client.RegisterAsync(instance, TestContext.Current.CancellationToken); + + await asyncAction.Should().ThrowExactlyAsync().WithMessage("Failed to execute request on all known Eureka servers."); + + IList logMessages = capturingLoggerProvider.GetAll(); + + logMessages.Should().BeEquivalentTo( + $"WARN {typeof(EurekaClient)}: Failed to fetch access token from 'http://host-that-does-not-exist.net:9999/' in attempt 1."); + } + [Fact] public async Task RegisterAsync_ThrowsOnErrorResponse() { From 4594c30a66fb3867ea681b3432c3f2793d91e620 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:34:56 +0200 Subject: [PATCH 49/81] Update to ReSharper v2026.1 (#1681) * Update to R# v2026.1.0.1 * Fix R# warning: Member can be private * Fix R# warning: Type can be internal * Fix R# warning: Type is never used * Fix R# warning: Method return value is never used * Fix R# warning: Captured variable is disposed in the outer scope * Fix R# warning: Convert into method group * Fix R# warning: Use nameof * Fix R# warning: Type is never instantiated * Fix R# warning: Short-lived HttpClient * Fix R# warning: Missing doc-comment on public type * Fix R# warning: auto-property is never used * Fix CA1859: Use concrete types when possible for improved performance * Cleanup sandbox types --- .config/dotnet-tools.json | 2 +- src/Common/src/Hosting/HostBuilderWrapper.cs | 28 ++++++--------- .../Hosting.Test/HostBuilderWrapperTest.cs | 4 +++ .../BootstrapperLoggerFactoryTest.cs | 1 + .../test/TestResources/EmptyDisposable.cs | 2 +- src/Common/test/TestResources/IO/Sandbox.cs | 19 +---------- .../test/TestResources/IO/TempDirectory.cs | 10 +----- src/Common/test/TestResources/IO/TempFile.cs | 11 ------ .../ConfigServerConfigurationProvider.cs | 2 +- ...ServerConfigurationProviderTest.Loading.cs | 2 +- .../test/ConfigServer.Test/HttpRequestInfo.cs | 34 ------------------- ...ServiceBindingConfigurationProviderTest.cs | 2 +- .../src/Eureka/EurekaDiscoveryClient.cs | 4 +-- .../Eureka.Test/Transport/EurekaClientTest.cs | 2 ++ .../CloudFoundrySecurityMiddlewareTest.cs | 3 ++ .../ApplicationAvailabilityTest.cs | 6 ++-- .../ComplexDetailsContributor.cs | 2 ++ ...gnosticObserverHttpExchangeRecorderTest.cs | 1 + .../test/Tasks.Test/TaskHostExtensionsTest.cs | 2 ++ src/Steeltoe.All.sln.DotSettings | 17 +++++----- .../GeneratorTests.cs | 1 + 21 files changed, 47 insertions(+), 108 deletions(-) delete mode 100644 src/Configuration/test/ConfigServer.Test/HttpRequestInfo.cs diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 826a95c4eb..4c979f3d12 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2025.3.3", + "version": "2026.1.0.1", "commands": [ "jb" ], diff --git a/src/Common/src/Hosting/HostBuilderWrapper.cs b/src/Common/src/Hosting/HostBuilderWrapper.cs index 0766d64d03..07ee6af1c8 100644 --- a/src/Common/src/Hosting/HostBuilderWrapper.cs +++ b/src/Common/src/Hosting/HostBuilderWrapper.cs @@ -86,14 +86,14 @@ private static void InvokeDeferredActions(IEnumerable configureAction) + public void ConfigureServices(Action configureAction) { ArgumentNullException.ThrowIfNull(configureAction); - return ConfigureServices((_, services) => configureAction(services)); + ConfigureServices((_, services) => configureAction(services)); } - public HostBuilderWrapper ConfigureServices(Action configureAction) + public void ConfigureServices(Action configureAction) { ArgumentNullException.ThrowIfNull(configureAction); @@ -106,18 +106,16 @@ public HostBuilderWrapper ConfigureServices(Action configureAction) + public void ConfigureAppConfiguration(Action configureAction) { ArgumentNullException.ThrowIfNull(configureAction); - return ConfigureAppConfiguration((_, configurationBuilder) => configureAction(configurationBuilder)); + ConfigureAppConfiguration((_, configurationBuilder) => configureAction(configurationBuilder)); } - public HostBuilderWrapper ConfigureAppConfiguration(Action configureAction) + public void ConfigureAppConfiguration(Action configureAction) { ArgumentNullException.ThrowIfNull(configureAction); @@ -130,18 +128,16 @@ public HostBuilderWrapper ConfigureAppConfiguration(Action configureAction) + public void ConfigureLogging(Action configureAction) { ArgumentNullException.ThrowIfNull(configureAction); - return ConfigureLogging((_, configurationBuilder) => configureAction(configurationBuilder)); + ConfigureLogging((_, configurationBuilder) => configureAction(configurationBuilder)); } - public HostBuilderWrapper ConfigureLogging(Action configureAction) + public void ConfigureLogging(Action configureAction) { ArgumentNullException.ThrowIfNull(configureAction); @@ -156,11 +152,9 @@ public HostBuilderWrapper ConfigureLogging(Action collection.AddLogging(builder => configureAction(contextWrapper, builder))); #pragma warning restore S4792 // Configuring loggers is security-sensitive } - - return this; } - public HostBuilderWrapper ConfigureWebHost(Action configureAction) + public void ConfigureWebHost(Action configureAction) { ArgumentNullException.ThrowIfNull(configureAction); @@ -184,7 +178,5 @@ public HostBuilderWrapper ConfigureWebHost(Action configureActi { throw new NotSupportedException($"Unknown host builder type '{_innerBuilder.GetType()}'."); } - - return this; } } diff --git a/src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs b/src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs index 1e5b7ea4d1..65e138ca66 100644 --- a/src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs +++ b/src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs @@ -29,6 +29,7 @@ public async Task WebApplicationBuilder_Wraps() HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); wrapper.ConfigureServices(services => services.AddSingleton()); wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); + // ReSharper disable once AccessToDisposedClosure wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddProvider(capturingLoggerProvider)); wrapper.ConfigureWebHost(hostBuilder => hostBuilder.UseUrls("http://*:8888")); wrapper.ConfigureServices((contextWrapper, _) => contextWrapper.HostEnvironment.ApplicationName = "TestApp"); @@ -64,6 +65,7 @@ public void HostApplicationBuilder_Wraps() HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); wrapper.ConfigureServices(services => services.AddSingleton()); wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); + // ReSharper disable once AccessToDisposedClosure wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddProvider(capturingLoggerProvider)); wrapper.ConfigureServices((contextWrapper, _) => contextWrapper.HostEnvironment.ApplicationName = "TestApp"); @@ -98,6 +100,7 @@ public void WebHostBuilder_Wraps() HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); wrapper.ConfigureServices(services => services.AddSingleton()); wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); + // ReSharper disable once AccessToDisposedClosure wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddProvider(capturingLoggerProvider)); wrapper.ConfigureWebHost(hostBuilder => hostBuilder.UseUrls("http://*:8888")); wrapper.ConfigureServices((contextWrapper, _) => contextWrapper.HostEnvironment.ApplicationName = "TestApp"); @@ -135,6 +138,7 @@ public void GenericHostBuilder_Wraps() HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); wrapper.ConfigureServices(services => services.AddSingleton()); wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); + // ReSharper disable once AccessToDisposedClosure wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddProvider(capturingLoggerProvider)); wrapper.ConfigureWebHost(hostBuilder => hostBuilder.UseUrls("http://*:8888")); wrapper.ConfigureServices((contextWrapper, _) => contextWrapper.HostEnvironment.ApplicationName = "TestApp"); diff --git a/src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs b/src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs index 434e7feaeb..79ea85acc6 100644 --- a/src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs +++ b/src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs @@ -20,6 +20,7 @@ public async Task Upgrades_existing_loggers() var bootstrapLoggerFactory = BootstrapLoggerFactory.CreateEmpty(loggingBuilder => { loggingBuilder.SetMinimumLevel(LogLevel.Trace); + // ReSharper disable once AccessToDisposedClosure loggingBuilder.AddProvider(capturingLoggerProvider); }); diff --git a/src/Common/test/TestResources/EmptyDisposable.cs b/src/Common/test/TestResources/EmptyDisposable.cs index af2bdd15cd..20f90e22e0 100644 --- a/src/Common/test/TestResources/EmptyDisposable.cs +++ b/src/Common/test/TestResources/EmptyDisposable.cs @@ -7,7 +7,7 @@ namespace Steeltoe.Common.TestResources; /// /// Implements without doing anything. /// -public sealed class EmptyDisposable : IDisposable +internal sealed class EmptyDisposable : IDisposable { public static IDisposable Instance { get; } = new EmptyDisposable(); diff --git a/src/Common/test/TestResources/IO/Sandbox.cs b/src/Common/test/TestResources/IO/Sandbox.cs index bdc91ba063..3faa1578f6 100644 --- a/src/Common/test/TestResources/IO/Sandbox.cs +++ b/src/Common/test/TestResources/IO/Sandbox.cs @@ -29,30 +29,13 @@ public Sandbox() /// /// The physical path. /// - public string ResolvePath(string path) + private string ResolvePath(string path) { ArgumentNullException.ThrowIfNull(path); return Path.Combine(FullPath, path); } - /// - /// Creates a directory at the specified path within the sandbox. - /// - /// - /// The directory path. - /// - /// - /// The physical path of the created directory. - /// - public string CreateDirectory(string path) - { - ArgumentNullException.ThrowIfNull(path); - - DirectoryInfo directoryInfo = Directory.CreateDirectory(ResolvePath(path)); - return directoryInfo.FullName; - } - /// /// Creates a file at the specified path within the sandbox. /// diff --git a/src/Common/test/TestResources/IO/TempDirectory.cs b/src/Common/test/TestResources/IO/TempDirectory.cs index 3d3238a412..0d353f0cdb 100644 --- a/src/Common/test/TestResources/IO/TempDirectory.cs +++ b/src/Common/test/TestResources/IO/TempDirectory.cs @@ -9,21 +9,13 @@ namespace Steeltoe.Common.TestResources.IO; /// public class TempDirectory : TempPath { - /// - /// Initializes a new instance of the class. - /// - public TempDirectory() - : base(string.Empty) - { - } - /// /// Initializes a new instance of the class. /// /// /// Directory name prefix. /// - public TempDirectory(string prefix) + protected TempDirectory(string prefix) : base(prefix) { } diff --git a/src/Common/test/TestResources/IO/TempFile.cs b/src/Common/test/TestResources/IO/TempFile.cs index 56e6b90da4..0b9a352a28 100644 --- a/src/Common/test/TestResources/IO/TempFile.cs +++ b/src/Common/test/TestResources/IO/TempFile.cs @@ -17,17 +17,6 @@ public TempFile() { } - /// - /// Initializes a new instance of the class. - /// - /// - /// File name prefix. - /// - public TempFile(string prefix) - : base(prefix) - { - } - /// /// Creates the temporary file. /// diff --git a/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs b/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs index 9a24046abe..71dbe8f5e0 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs @@ -375,7 +375,7 @@ public override void Load() return await DoLoadAsync(optionsSnapshot, updateDictionary, cancellationToken); } - internal async Task DoLoadAsync(ConfigServerClientOptions optionsSnapshot, bool updateDictionary, CancellationToken cancellationToken) + private async Task DoLoadAsync(ConfigServerClientOptions optionsSnapshot, bool updateDictionary, CancellationToken cancellationToken) { LogFetchingRemoteConfiguration(); diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Loading.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Loading.cs index 806e869a61..9fe210d9f4 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Loading.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Loading.cs @@ -787,7 +787,7 @@ await TestFailureTracer.CaptureAsync(async tracer => using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, tracer.LoggerFactory); - Action action = () => provider.Load(); + Action action = provider.Load; action.Should().ThrowExactly(); diff --git a/src/Configuration/test/ConfigServer.Test/HttpRequestInfo.cs b/src/Configuration/test/ConfigServer.Test/HttpRequestInfo.cs deleted file mode 100644 index 7137ab3bdf..0000000000 --- a/src/Configuration/test/ConfigServer.Test/HttpRequestInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Http; - -namespace Steeltoe.Configuration.ConfigServer.Test; - -public sealed class HttpRequestInfo -{ - public string Method { get; } - public HostString Host { get; } - public PathString Path { get; } - public QueryString QueryString { get; } - public Stream Body { get; } = new MemoryStream(); - - private HttpRequestInfo(HttpRequest request) - { - Method = request.Method; - Host = request.Host; - Path = request.Path; - QueryString = request.QueryString; - } - - public static async Task CopyFromAsync(HttpContext httpContext) - { - var info = new HttpRequestInfo(httpContext.Request); - - await httpContext.Request.Body.CopyToAsync(info.Body, httpContext.RequestAborted); - info.Body.Seek(0, SeekOrigin.Begin); - - return info; - } -} diff --git a/src/Configuration/test/Kubernetes.ServiceBindings.Test/KubernetesServiceBindingConfigurationProviderTest.cs b/src/Configuration/test/Kubernetes.ServiceBindings.Test/KubernetesServiceBindingConfigurationProviderTest.cs index 5fef604467..7b92c1f5ca 100644 --- a/src/Configuration/test/Kubernetes.ServiceBindings.Test/KubernetesServiceBindingConfigurationProviderTest.cs +++ b/src/Configuration/test/Kubernetes.ServiceBindings.Test/KubernetesServiceBindingConfigurationProviderTest.cs @@ -123,7 +123,7 @@ private static string GetEmptyK8SResourcesDirectory() private sealed class TestPostProcessor : IConfigurationPostProcessor { - public bool PostProcessorCalled { get; set; } + public bool PostProcessorCalled { get; private set; } public void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData) { diff --git a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs index 8076affe7a..5e286261f2 100644 --- a/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs +++ b/src/Discovery/src/Eureka/EurekaDiscoveryClient.cs @@ -161,7 +161,7 @@ public EurekaDiscoveryClient(EurekaApplicationInfoManager appInfoManager, Eureka return Applications.GetRegisteredApplication(appName); } - internal IReadOnlyList GetInstancesByVipAddress(string vipAddress) + private ReadOnlyCollection GetInstancesByVipAddress(string vipAddress) { ArgumentException.ThrowIfNullOrWhiteSpace(vipAddress); @@ -576,7 +576,7 @@ public Task> GetInstancesAsync(string serviceId, Cancell { ArgumentException.ThrowIfNullOrWhiteSpace(serviceId); - IReadOnlyList instances = GetInstancesByVipAddress(serviceId); + ReadOnlyCollection instances = GetInstancesByVipAddress(serviceId); IServiceInstance[] serviceInstances = instances.Select(instance => new EurekaServiceInstance(instance)).Cast().ToArray(); if (_logger.IsEnabled(LogLevel.Debug)) diff --git a/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs b/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs index af880a4275..0b6ece9106 100644 --- a/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs +++ b/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs @@ -13,6 +13,8 @@ using Steeltoe.Discovery.Eureka.Configuration; using Steeltoe.Discovery.Eureka.Transport; +// ReSharper disable AccessToDisposedClosure + namespace Steeltoe.Discovery.Eureka.Test.Transport; public sealed class EurekaClientTest diff --git a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs index 18e350eb83..1197f57d97 100644 --- a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs @@ -337,6 +337,7 @@ public async Task RedactsHttpHeaders() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); builder.Configuration.AddCloudFoundry(); + // ReSharper disable once AccessToDisposedClosure builder.Services.AddLogging(options => options.SetMinimumLevel(LogLevel.Trace).AddProvider(capturingLoggerProvider)); builder.Services.AddCloudFoundryActuator(); await using WebApplication app = builder.Build(); @@ -378,7 +379,9 @@ public async Task Returns_expected_response_on_permission_check(string scenario, host.Services.GetRequiredService().Using(CloudControllerPermissionsMock.GetHttpMessageHandler()); await host.StartAsync(TestContext.Current.CancellationToken); + // ReSharper disable once ShortLivedHttpClient using var client = new HttpClient(); + var authenticationRequest = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost:5000/cloudfoundryapplication")); authenticationRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", MockAccessToken); var authorizationRequest = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost:5000/cloudfoundryapplication/info")); diff --git a/src/Management/test/Endpoint.Test/Actuators/Health/Availability/ApplicationAvailabilityTest.cs b/src/Management/test/Endpoint.Test/Actuators/Health/Availability/ApplicationAvailabilityTest.cs index ad6a8e5872..4a01115076 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Health/Availability/ApplicationAvailabilityTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Health/Availability/ApplicationAvailabilityTest.cs @@ -20,9 +20,9 @@ public void TracksAndReturnsState() { var availability = new ApplicationAvailability(NullLogger.Instance); - availability.SetAvailabilityState("Test", LivenessState.Broken, GetType().Name); - availability.SetAvailabilityState(ApplicationAvailability.LivenessKey, LivenessState.Correct, GetType().Name); - availability.SetAvailabilityState(ApplicationAvailability.ReadinessKey, ReadinessState.AcceptingTraffic, GetType().Name); + availability.SetAvailabilityState("Test", LivenessState.Broken, nameof(ApplicationAvailabilityTest)); + availability.SetAvailabilityState(ApplicationAvailability.LivenessKey, LivenessState.Correct, nameof(ApplicationAvailabilityTest)); + availability.SetAvailabilityState(ApplicationAvailability.ReadinessKey, ReadinessState.AcceptingTraffic, nameof(ApplicationAvailabilityTest)); availability.GetAvailabilityState("Test").Should().Be(LivenessState.Broken); availability.GetLivenessState().Should().Be(LivenessState.Correct); diff --git a/src/Management/test/Endpoint.Test/Actuators/Health/TestContributors/ComplexDetailsContributor.cs b/src/Management/test/Endpoint.Test/Actuators/Health/TestContributors/ComplexDetailsContributor.cs index b3bffabe72..6017635ba5 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Health/TestContributors/ComplexDetailsContributor.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Health/TestContributors/ComplexDetailsContributor.cs @@ -39,6 +39,7 @@ internal sealed class ComplexDetailsContributor : IHealthContributor private sealed class TestHealthDetails { + // ReSharper disable UnusedAutoPropertyAccessor.Local [JsonPropertyName("testString")] public string TestString { get; set; } = "test-string"; @@ -69,5 +70,6 @@ private sealed class TestHealthDetails ["Two"] = 2, ["Three"] = 3 }; + // ReSharper restore UnusedAutoPropertyAccessor.Local } } diff --git a/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/DiagnosticObserverHttpExchangeRecorderTest.cs b/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/DiagnosticObserverHttpExchangeRecorderTest.cs index 54e765795f..47b82fefff 100644 --- a/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/DiagnosticObserverHttpExchangeRecorderTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/DiagnosticObserverHttpExchangeRecorderTest.cs @@ -52,6 +52,7 @@ public async Task Records_http_requests() host.MapGet("/hello", () => "Hello World!"); await host.StartAsync(TestContext.Current.CancellationToken); + // ReSharper disable once ShortLivedHttpClient using var httpClient = new HttpClient(); HttpResponseMessage helloResponse = diff --git a/src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs b/src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs index 10e6f5db60..82bb71d688 100644 --- a/src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs +++ b/src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs @@ -199,6 +199,7 @@ public async Task WebApplication_LogsErrorOnUnknownTask() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(args); using var capturingLoggerProvider = new CapturingLoggerProvider(category => category.StartsWith("Steeltoe.", StringComparison.Ordinal)); + // ReSharper disable once AccessToDisposedClosure builder.Services.AddLogging(options => options.AddProvider(capturingLoggerProvider)); WebApplication app = builder.Build(); @@ -338,6 +339,7 @@ public async Task RunAsync(CancellationToken cancellationToken) } } + // ReSharper disable once ClassNeverInstantiated.Local private sealed class ThrowingApplicationTask : IApplicationTask { public Task RunAsync(CancellationToken cancellationToken) diff --git a/src/Steeltoe.All.sln.DotSettings b/src/Steeltoe.All.sln.DotSettings index 767688ffbd..7e0a2d1fc4 100644 --- a/src/Steeltoe.All.sln.DotSettings +++ b/src/Steeltoe.All.sln.DotSettings @@ -16,7 +16,6 @@ DC1BC61A-E0FA-4CF9-9F24-D4C564A07836/f:Directory.Build.targets/l:..?Directory.Build.targets SOLUTION True - True SUGGESTION SUGGESTION SUGGESTION @@ -24,6 +23,7 @@ SUGGESTION SUGGESTION SUGGESTION + WARNING SUGGESTION SUGGESTION SUGGESTION @@ -60,6 +60,7 @@ DO_NOT_SHOW DO_NOT_SHOW WARNING + SUGGESTION DO_NOT_SHOW HINT SUGGESTION @@ -79,10 +80,12 @@ WARNING SUGGESTION SUGGESTION + WARNING SUGGESTION WARNING WARNING WARNING + WARNING DO_NOT_SHOW DO_NOT_SHOW WARNING @@ -92,6 +95,7 @@ WARNING SUGGESTION WARNING + SUGGESTION HINT WARNING WARNING @@ -102,14 +106,16 @@ SUGGESTION WARNING True + ShowAndRun SUGGESTION False - <?xml version="1.0" encoding="utf-16"?><Profile name="Steeltoe Full Cleanup"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" ArrangeNullCheckingPattern="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSReformatInactiveBranches>True</CSReformatInactiveBranches><CSUpdateFileHeader>True</CSUpdateFileHeader></Profile> + <?xml version="1.0" encoding="utf-16"?><Profile name="Steeltoe Full Cleanup"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" ArrangeNullCheckingPattern="True" ArrangeAccessors="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSReformatInactiveBranches>True</CSReformatInactiveBranches><CSharpReformatComments>True</CSharpReformatComments><CSUpdateFileHeader>True</CSUpdateFileHeader></Profile> Steeltoe Full Cleanup Required Required Required Required + StringEmpty Conditional False False @@ -611,15 +617,12 @@ See the LICENSE file in the project root for more information. False <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> - <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> - <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, PrivateProtected" Description="Protected fields"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> - <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Protected, PrivateProtected" Description="Protected fields"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="True" Prefix="" Suffix="" Style="aaBb" /></Policy> True True True True True - True True True True @@ -642,7 +645,6 @@ See the LICENSE file in the project root for more information. True CSHARP False - Replace argument null check with Guard clause System.ArgumentNullException.ThrowIfNull($argument$); $left$ = $right$; $left$ = $right$ ?? throw new ArgumentNullException(nameof($argument$)); @@ -657,7 +659,6 @@ $left$ = $right$; True CSHARP False - Replace argument null check with Guard clause System.ArgumentNullException.ThrowIfNull($argument$); if ($argument$ == null) throw new ArgumentNullException(nameof($argument$)); WARNING diff --git a/src/Tools/test/ConfigurationSchemaGenerator.Tests/GeneratorTests.cs b/src/Tools/test/ConfigurationSchemaGenerator.Tests/GeneratorTests.cs index 480464db8a..73ce367b66 100644 --- a/src/Tools/test/ConfigurationSchemaGenerator.Tests/GeneratorTests.cs +++ b/src/Tools/test/ConfigurationSchemaGenerator.Tests/GeneratorTests.cs @@ -29,6 +29,7 @@ namespace ConfigurationSchemaGenerator.Tests; +/// public partial class GeneratorTests { private static readonly SyntaxTree s_implicitUsingsSyntaxTree = SyntaxFactory.ParseSyntaxTree(SourceText.From( From bbf3ee94da97d756bd3ecb3a2a89a3778f774529 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:41:52 +0200 Subject: [PATCH 50/81] R#: Move inline suppressions for AccessToDisposedClosure in tests to .editorconfig (#1682) * Move inline suppressions for AccessToDisposedClosure in tests to .editorconfig * Run code cleanup --- .editorconfig | 4 ++++ src/Common/src/Common/Discovery/IDiscoveryClient.cs | 2 +- src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs | 4 ---- src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs | 1 - .../CloudfoundryConfigurationProviderTest.cs | 1 - .../ConfigServerClientOptionsTest.cs | 2 -- .../test/ConfigServer.Test/ConfigServerClientOptionsTest.cs | 2 -- .../ConfigServerConfigurationProviderTest.Settings.cs | 4 ++-- .../ConfigServerConfigurationProviderTest.cs | 3 --- .../Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs | 2 -- .../Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs | 1 - .../Connectors.Test/RabbitMQ/RabbitMQHealthContributorTest.cs | 2 -- .../test/Connectors.Test/Redis/RedisHealthContributorTest.cs | 2 -- .../RelationalDatabaseHealthContributorTest.cs | 2 -- .../Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs | 1 - src/Discovery/test/Consul.Test/Discovery/TtlSchedulerTest.cs | 2 -- .../test/Consul.Test/Registry/ConsulServiceRegistryTest.cs | 1 - src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs | 2 -- .../DiscoveryHostApplicationBuilderExtensionsTest.cs | 1 - .../DiscoveryWebApplicationBuilderExtensionsTest.cs | 1 - .../LoadBalancers/DiscoveryHttpClientHandlerTest.cs | 2 -- .../LoadBalancers/DiscoveryHttpDelegatingHandlerTest.cs | 2 -- .../test/Endpoint.Test/Actuators/All/AllActuatorsTest.cs | 3 --- .../Actuators/CloudFoundry/CloudFoundryActuatorTest.cs | 2 -- .../CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs | 2 -- .../Actuators/DbMigrations/DbMigrationsActuatorTest.cs | 1 - .../Actuators/Environment/EnvironmentActuatorTest.cs | 1 - .../test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs | 1 - .../Endpoint.Test/Actuators/Health/HealthAggregationTest.cs | 3 --- .../Endpoint.Test/Actuators/HeapDump/HeapDumpActuatorTest.cs | 1 - .../Actuators/HttpExchanges/HttpExchangesActuatorTest.cs | 1 - .../Actuators/Hypermedia/HypermediaActuatorTest.cs | 1 - .../test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs | 1 - .../Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs | 1 - .../Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs | 1 - .../Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs | 1 - .../Actuators/RouteMappings/RouteMappingsActuatorTest.cs | 1 - .../Endpoint.Test/Actuators/Services/ServicesActuatorTest.cs | 1 - .../Actuators/ThreadDump/ThreadDumpActuatorTest.cs | 1 - .../Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs | 1 - src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs | 1 - src/Steeltoe.All.sln.DotSettings | 1 - 42 files changed, 7 insertions(+), 63 deletions(-) diff --git a/.editorconfig b/.editorconfig index adf8ab64ba..ae71398246 100644 --- a/.editorconfig +++ b/.editorconfig @@ -162,3 +162,7 @@ dotnet_diagnostic.JSON002.severity = silent # CA1848: Use the LoggerMessage delegates (depends on https://github.com/SteeltoeOSS/Steeltoe/issues/969) dotnet_diagnostic.CA1848.severity = silent + +[*Test.cs] +resharper_access_to_disposed_closure_highlighting = none +resharper_access_to_modified_closure_highlighting = none diff --git a/src/Common/src/Common/Discovery/IDiscoveryClient.cs b/src/Common/src/Common/Discovery/IDiscoveryClient.cs index bd2b8c3cb8..07c9c21b0c 100644 --- a/src/Common/src/Common/Discovery/IDiscoveryClient.cs +++ b/src/Common/src/Common/Discovery/IDiscoveryClient.cs @@ -17,7 +17,7 @@ public interface IDiscoveryClient /// /// Occurs when service instances have been fetched from the discovery server. /// - public event EventHandler InstancesFetched; + event EventHandler InstancesFetched; /// /// Gets information used to register the local service instance (this app) to the discovery server. diff --git a/src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs b/src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs index 65e138ca66..1e5b7ea4d1 100644 --- a/src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs +++ b/src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs @@ -29,7 +29,6 @@ public async Task WebApplicationBuilder_Wraps() HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); wrapper.ConfigureServices(services => services.AddSingleton()); wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); - // ReSharper disable once AccessToDisposedClosure wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddProvider(capturingLoggerProvider)); wrapper.ConfigureWebHost(hostBuilder => hostBuilder.UseUrls("http://*:8888")); wrapper.ConfigureServices((contextWrapper, _) => contextWrapper.HostEnvironment.ApplicationName = "TestApp"); @@ -65,7 +64,6 @@ public void HostApplicationBuilder_Wraps() HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); wrapper.ConfigureServices(services => services.AddSingleton()); wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); - // ReSharper disable once AccessToDisposedClosure wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddProvider(capturingLoggerProvider)); wrapper.ConfigureServices((contextWrapper, _) => contextWrapper.HostEnvironment.ApplicationName = "TestApp"); @@ -100,7 +98,6 @@ public void WebHostBuilder_Wraps() HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); wrapper.ConfigureServices(services => services.AddSingleton()); wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); - // ReSharper disable once AccessToDisposedClosure wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddProvider(capturingLoggerProvider)); wrapper.ConfigureWebHost(hostBuilder => hostBuilder.UseUrls("http://*:8888")); wrapper.ConfigureServices((contextWrapper, _) => contextWrapper.HostEnvironment.ApplicationName = "TestApp"); @@ -138,7 +135,6 @@ public void GenericHostBuilder_Wraps() HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); wrapper.ConfigureServices(services => services.AddSingleton()); wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); - // ReSharper disable once AccessToDisposedClosure wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddProvider(capturingLoggerProvider)); wrapper.ConfigureWebHost(hostBuilder => hostBuilder.UseUrls("http://*:8888")); wrapper.ConfigureServices((contextWrapper, _) => contextWrapper.HostEnvironment.ApplicationName = "TestApp"); diff --git a/src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs b/src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs index 79ea85acc6..434e7feaeb 100644 --- a/src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs +++ b/src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs @@ -20,7 +20,6 @@ public async Task Upgrades_existing_loggers() var bootstrapLoggerFactory = BootstrapLoggerFactory.CreateEmpty(loggingBuilder => { loggingBuilder.SetMinimumLevel(LogLevel.Trace); - // ReSharper disable once AccessToDisposedClosure loggingBuilder.AddProvider(capturingLoggerProvider); }); diff --git a/src/Configuration/test/CloudFoundry.Test/CloudfoundryConfigurationProviderTest.cs b/src/Configuration/test/CloudFoundry.Test/CloudfoundryConfigurationProviderTest.cs index d461d659a7..5566efc479 100644 --- a/src/Configuration/test/CloudFoundry.Test/CloudfoundryConfigurationProviderTest.cs +++ b/src/Configuration/test/CloudFoundry.Test/CloudfoundryConfigurationProviderTest.cs @@ -216,7 +216,6 @@ public void Load_VCAP_APPLICATION_Allows_Reload_Without_Throwing_Exception() _ = Task.Run(() => { - // ReSharper disable once AccessToDisposedClosure while (!tokenSource.IsCancellationRequested) { configurationRoot.Reload(); diff --git a/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerClientOptionsTest.cs b/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerClientOptionsTest.cs index 5ca2eab3c0..61c57e5a36 100644 --- a/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerClientOptionsTest.cs +++ b/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerClientOptionsTest.cs @@ -90,7 +90,6 @@ public void Config_Server_URI_is_resolved_from_discovery_and_survives_changes_in var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); - // ReSharper disable once AccessToDisposedClosure configurationBuilder.AddConfigServer(new ConfigServerClientOptions(), configureOptions, () => handler, NullLoggerFactory.Instance); IConfigurationRoot configuration = configurationBuilder.Build(); @@ -306,7 +305,6 @@ public void Updates_discovered_Config_Server_URI_on_provider_reload() var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); - // ReSharper disable once AccessToDisposedClosure configurationBuilder.AddConfigServer(new ConfigServerClientOptions(), configureOptions, () => handler, NullLoggerFactory.Instance); IConfigurationRoot configuration = configurationBuilder.Build(); diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerClientOptionsTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerClientOptionsTest.cs index 2c648dc0b3..27d0f98929 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerClientOptionsTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerClientOptionsTest.cs @@ -265,7 +265,6 @@ public void Certificate_configuration_survives_options_reload() var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); - // ReSharper disable once AccessToDisposedClosure configurationBuilder.AddConfigServer(new ConfigServerClientOptions(), null, () => handler, NullLoggerFactory.Instance); IConfigurationRoot configuration = configurationBuilder.Build(); @@ -368,7 +367,6 @@ public void Changes_in_IConfiguration_update_provider_options_and_injected_optio var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); configurationBuilder.AddPlaceholderResolver(); - // ReSharper disable once AccessToDisposedClosure configurationBuilder.AddConfigServer(defaultOptions, configureOptions, () => handler, NullLoggerFactory.Instance); IConfigurationRoot configuration = configurationBuilder.Build(); diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Settings.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Settings.cs index 1cd6f664f6..d043acfc9c 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Settings.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Settings.cs @@ -7,6 +7,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; +// ReSharper disable AccessToDisposedClosure + namespace Steeltoe.Configuration.ConfigServer.Test; public sealed partial class ConfigServerConfigurationProviderTest @@ -65,9 +67,7 @@ public void GetConfigServerUri_NoBaseUri_Throws() using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); - // ReSharper disable AccessToDisposedClosure Action action = () => provider.BuildConfigServerUri(provider.ClientOptions, null!, null); - // ReSharper restore AccessToDisposedClosure action.Should().ThrowExactly(); } diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs index 7b8c950a39..7b588ee87a 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs @@ -264,7 +264,6 @@ public async Task GetRequestMessage_AddsBearerToken_WhenAccessTokenUriIsSet() } """); - // ReSharper disable once AccessToDisposedClosure using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); Uri requestUri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null); @@ -294,7 +293,6 @@ public async Task RefreshVaultToken_Succeeds() handler.Mock.Expect(HttpMethod.Post, "http://localhost:8888/vault/v1/auth/token/renew-self").WithHeaders("X-Vault-Token", "MyVaultToken") .WithContent("{\"increment\":300}").Respond(HttpStatusCode.NoContent); - // ReSharper disable once AccessToDisposedClosure using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); provider.Load(); @@ -324,7 +322,6 @@ public async Task RefreshVaultToken_With_AccessTokenUri_Succeeds() handler.Mock.Expect(HttpMethod.Post, "http://localhost:8888/vault/v1/auth/token/renew-self").WithHeaders("X-Vault-Token", "MyVaultToken") .WithHeaders("Authorization", "Bearer secret").WithContent("{\"increment\":300}").Respond(HttpStatusCode.NoContent); - // ReSharper disable once AccessToDisposedClosure using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); await provider.RefreshVaultTokenAsync(provider.ClientOptions, TestContext.Current.CancellationToken); diff --git a/src/Connectors/test/Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs index 303f5c0c21..990db81109 100644 --- a/src/Connectors/test/Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs @@ -116,9 +116,7 @@ public async Task Canceled_Throws() using var source = new CancellationTokenSource(); await source.CancelAsync(); - // ReSharper disable AccessToDisposedClosure Func action = async () => await healthContributor.CheckHealthAsync(source.Token); - // ReSharper restore AccessToDisposedClosure await action.Should().ThrowExactlyAsync(); } diff --git a/src/Connectors/test/Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs index abf1a00a0a..d57155154b 100644 --- a/src/Connectors/test/Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs @@ -86,7 +86,6 @@ public async Task Canceled_Throws() using var source = new CancellationTokenSource(); await source.CancelAsync(); - // ReSharper disable once AccessToDisposedClosure Func action = async () => await healthContributor.CheckHealthAsync(source.Token); await action.Should().ThrowExactlyAsync(); diff --git a/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQHealthContributorTest.cs index 7d8ee14216..7f3290395e 100644 --- a/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQHealthContributorTest.cs @@ -109,9 +109,7 @@ public async Task Canceled_Throws() using var source = new CancellationTokenSource(); await source.CancelAsync(); - // ReSharper disable AccessToDisposedClosure Func action = async () => await healthContributor.CheckHealthAsync(source.Token); - // ReSharper restore AccessToDisposedClosure await action.Should().ThrowExactlyAsync(); } diff --git a/src/Connectors/test/Connectors.Test/Redis/RedisHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/Redis/RedisHealthContributorTest.cs index f2dc8540d4..aa0bcf3988 100644 --- a/src/Connectors/test/Connectors.Test/Redis/RedisHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/Redis/RedisHealthContributorTest.cs @@ -74,9 +74,7 @@ public async Task Canceled_Throws() using var source = new CancellationTokenSource(); await source.CancelAsync(); - // ReSharper disable AccessToDisposedClosure Func action = async () => await healthContributor.CheckHealthAsync(source.Token); - // ReSharper restore AccessToDisposedClosure await action.Should().ThrowExactlyAsync(); } diff --git a/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs index 267d293dac..de229c4d91 100644 --- a/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs @@ -175,9 +175,7 @@ public async Task Canceled_Throws() using var source = new CancellationTokenSource(); await source.CancelAsync(); - // ReSharper disable AccessToDisposedClosure Func action = async () => await healthContributor.CheckHealthAsync(source.Token); - // ReSharper restore AccessToDisposedClosure await action.Should().ThrowExactlyAsync(); } diff --git a/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs b/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs index 17a40ea71e..320feb7679 100644 --- a/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs +++ b/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs @@ -88,7 +88,6 @@ public async Task Throws_for_missing_connection_string_with_version_detection() await using WebApplication app = builder.Build(); await using AsyncServiceScope scope = app.Services.CreateAsyncScope(); - // ReSharper disable once AccessToDisposedClosure Action action = () => _ = scope.ServiceProvider.GetRequiredService(); action.Should().ThrowExactly().WithMessage("Server version must be specified when no connection string is provided."); diff --git a/src/Discovery/test/Consul.Test/Discovery/TtlSchedulerTest.cs b/src/Discovery/test/Consul.Test/Discovery/TtlSchedulerTest.cs index 4799f45024..7736b6bb7d 100644 --- a/src/Discovery/test/Consul.Test/Discovery/TtlSchedulerTest.cs +++ b/src/Discovery/test/Consul.Test/Discovery/TtlSchedulerTest.cs @@ -20,7 +20,6 @@ public async Task Add_Throws_Invalid_InstanceId() var optionsMonitor = new TestOptionsMonitor(); await using var scheduler = new TtlScheduler(optionsMonitor, clientMoq.Object, NullLoggerFactory.Instance); - // ReSharper disable once AccessToDisposedClosure Action action = () => scheduler.Add(string.Empty); action.Should().ThrowExactly(); @@ -97,7 +96,6 @@ public async Task Remove_Throws_Invalid_InstanceId() var optionsMonitor = new TestOptionsMonitor(); await using var scheduler = new TtlScheduler(optionsMonitor, clientMoq.Object, NullLoggerFactory.Instance); - // ReSharper disable once AccessToDisposedClosure Func action = async () => await scheduler.RemoveAsync(string.Empty); await action.Should().ThrowExactlyAsync(); diff --git a/src/Discovery/test/Consul.Test/Registry/ConsulServiceRegistryTest.cs b/src/Discovery/test/Consul.Test/Registry/ConsulServiceRegistryTest.cs index a654543c51..0a9c15d66f 100644 --- a/src/Discovery/test/Consul.Test/Registry/ConsulServiceRegistryTest.cs +++ b/src/Discovery/test/Consul.Test/Registry/ConsulServiceRegistryTest.cs @@ -87,7 +87,6 @@ public async Task SetStatusAsync_ThrowsInvalidStatus() await using var registry = new ConsulServiceRegistry(clientMoq.Object, optionsMonitor, scheduler, NullLogger.Instance); - // ReSharper disable once AccessToDisposedClosure Func action = async () => await registry.SetStatusAsync(registration, string.Empty, TestContext.Current.CancellationToken); await action.Should().ThrowExactlyAsync(); diff --git a/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs b/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs index 0b6ece9106..af880a4275 100644 --- a/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs +++ b/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs @@ -13,8 +13,6 @@ using Steeltoe.Discovery.Eureka.Configuration; using Steeltoe.Discovery.Eureka.Transport; -// ReSharper disable AccessToDisposedClosure - namespace Steeltoe.Discovery.Eureka.Test.Transport; public sealed class EurekaClientTest diff --git a/src/Discovery/test/HttpClients.Test/DiscoveryHostApplicationBuilderExtensionsTest.cs b/src/Discovery/test/HttpClients.Test/DiscoveryHostApplicationBuilderExtensionsTest.cs index 48435d1d15..f16be05f32 100644 --- a/src/Discovery/test/HttpClients.Test/DiscoveryHostApplicationBuilderExtensionsTest.cs +++ b/src/Discovery/test/HttpClients.Test/DiscoveryHostApplicationBuilderExtensionsTest.cs @@ -51,7 +51,6 @@ public async Task AddEurekaDiscoveryClient_HostApplicationBuilder_StartsUp() using IHost host = hostBuilder.Build(); - // ReSharper disable once AccessToDisposedClosure Func action = async () => await host.StartAsync(TestContext.Current.CancellationToken); await action.Should().NotThrowAsync(); diff --git a/src/Discovery/test/HttpClients.Test/DiscoveryWebApplicationBuilderExtensionsTest.cs b/src/Discovery/test/HttpClients.Test/DiscoveryWebApplicationBuilderExtensionsTest.cs index 358958b738..1d347f30bc 100644 --- a/src/Discovery/test/HttpClients.Test/DiscoveryWebApplicationBuilderExtensionsTest.cs +++ b/src/Discovery/test/HttpClients.Test/DiscoveryWebApplicationBuilderExtensionsTest.cs @@ -73,7 +73,6 @@ public async Task AddEurekaDiscoveryClient_WorksWithGlobalServiceDiscovery() await using WebApplication host = builder.Build(); - // ReSharper disable once AccessToDisposedClosure Task resolveTask = Task.Run(() => _ = host.Services.GetServices().OfType().Single()); Func action = async () => await resolveTask.WaitAsync(5.Seconds(), TestContext.Current.CancellationToken); diff --git a/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpClientHandlerTest.cs b/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpClientHandlerTest.cs index 37589f843d..00343415b7 100644 --- a/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpClientHandlerTest.cs +++ b/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpClientHandlerTest.cs @@ -17,7 +17,6 @@ public async Task DoesNotTrackStatistics_WhenResolutionFails_WithProvidedLoadBal var handler = new DiscoveryHttpClientHandler(loadBalancer, TimeProvider.System); using var invoker = new HttpMessageInvoker(handler); - // ReSharper disable once AccessToDisposedClosure Func action = async () => _ = await invoker.SendAsync(httpRequestMessage, TestContext.Current.CancellationToken); await action.Should().ThrowExactlyAsync(); @@ -33,7 +32,6 @@ public async Task TracksStatistics_WhenRequestsGoWrong_WithProvidedLoadBalancer( var handler = new DiscoveryHttpClientHandler(loadBalancer, TimeProvider.System); using var invoker = new HttpMessageInvoker(handler); - // ReSharper disable once AccessToDisposedClosure Func action = async () => _ = await invoker.SendAsync(httpRequestMessage, TestContext.Current.CancellationToken); await action.Should().ThrowExactlyAsync(); diff --git a/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpDelegatingHandlerTest.cs b/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpDelegatingHandlerTest.cs index 19d55c7db3..a7cd3eb814 100644 --- a/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpDelegatingHandlerTest.cs +++ b/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpDelegatingHandlerTest.cs @@ -55,7 +55,6 @@ public async Task DoesNotTrackStatistics_WhenRequestIsCanceled() using var invoker = new HttpMessageInvoker(handler); - // ReSharper disable once AccessToDisposedClosure Func action = async () => await invoker.SendAsync(httpRequestMessage, TestContext.Current.CancellationToken); await action.Should().ThrowExactlyAsync(); @@ -80,7 +79,6 @@ public async Task DoesNotTrackStatistics_WhenResolutionFails_WithProvidedLoadBal using var invoker = new HttpMessageInvoker(handler); - // ReSharper disable once AccessToDisposedClosure Func action = async () => await invoker.SendAsync(httpRequestMessage, TestContext.Current.CancellationToken); await action.Should().ThrowExactlyAsync(); diff --git a/src/Management/test/Endpoint.Test/Actuators/All/AllActuatorsTest.cs b/src/Management/test/Endpoint.Test/Actuators/All/AllActuatorsTest.cs index 1efdbebb63..c28a325a4d 100644 --- a/src/Management/test/Endpoint.Test/Actuators/All/AllActuatorsTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/All/AllActuatorsTest.cs @@ -62,7 +62,6 @@ public async Task Registers_dependent_services(bool platformIsCloudFoundry) foreach (Type middlewareType in middlewareTypes) { - // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(middlewareType); action.Should().NotThrow(); @@ -72,10 +71,8 @@ public async Task Registers_dependent_services(bool platformIsCloudFoundry) { Action action = () => { - // ReSharper disable AccessToDisposedClosure _ = serviceProvider.GetRequiredService(); _ = serviceProvider.GetRequiredService(); - // ReSharper restore AccessToDisposedClosure }; action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs index 6a4e565aea..4e0b82d74d 100644 --- a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs @@ -43,11 +43,9 @@ public async Task Registers_dependent_services() services.AddCloudFoundryActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - // ReSharper disable once AccessToDisposedClosure Action middlewareAction = () => serviceProvider.GetRequiredService(); middlewareAction.Should().NotThrow(); - // ReSharper disable once AccessToDisposedClosure Action providerAction = () => serviceProvider.GetRequiredService(); providerAction.Should().NotThrow(); } diff --git a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs index 1197f57d97..44bf05c385 100644 --- a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs @@ -316,7 +316,6 @@ public async Task ThrowsWhenAddMethodNotCalled() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); await using WebApplication app = builder.Build(); - // ReSharper disable once AccessToDisposedClosure Action action = () => app.UseCloudFoundrySecurity(); action.Should().ThrowExactly().WithMessage("Please call IServiceCollection.AddCloudFoundryActuator first."); @@ -337,7 +336,6 @@ public async Task RedactsHttpHeaders() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); builder.Configuration.AddCloudFoundry(); - // ReSharper disable once AccessToDisposedClosure builder.Services.AddLogging(options => options.SetMinimumLevel(LogLevel.Trace).AddProvider(capturingLoggerProvider)); builder.Services.AddCloudFoundryActuator(); await using WebApplication app = builder.Build(); diff --git a/src/Management/test/Endpoint.Test/Actuators/DbMigrations/DbMigrationsActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/DbMigrations/DbMigrationsActuatorTest.cs index 491af47e67..4413007092 100644 --- a/src/Management/test/Endpoint.Test/Actuators/DbMigrations/DbMigrationsActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/DbMigrations/DbMigrationsActuatorTest.cs @@ -30,7 +30,6 @@ public async Task Registers_dependent_services() services.AddDbMigrationsActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Environment/EnvironmentActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Environment/EnvironmentActuatorTest.cs index 897202ec36..4afda96b82 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Environment/EnvironmentActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Environment/EnvironmentActuatorTest.cs @@ -32,7 +32,6 @@ public async Task Registers_dependent_services() services.AddEnvironmentActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs index 4a5e408466..cbd260f5fc 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs @@ -40,7 +40,6 @@ public async Task Registers_dependent_services() services.AddHealthActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs b/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs index ad76b9eaad..68f77a7bb3 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs @@ -450,9 +450,7 @@ public async Task Propagates_cancellation() using var source = new CancellationTokenSource(); source.CancelAfter(1.Seconds()); - // ReSharper disable AccessToDisposedClosure Func action = async () => await aggregator.AggregateAsync(contributors, [], emptyServiceProvider, source.Token); - // ReSharper restore AccessToDisposedClosure await action.Should().ThrowExactlyAsync(); } @@ -574,7 +572,6 @@ public async Task Can_use_scoped_AspNet_health_check() builder.Services.AddHealthActuator(); await using WebApplication host = builder.Build(); - // ReSharper disable once AccessToDisposedClosure Action action = () => host.Services.GetRequiredService(); action.Should().ThrowExactly(); diff --git a/src/Management/test/Endpoint.Test/Actuators/HeapDump/HeapDumpActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/HeapDump/HeapDumpActuatorTest.cs index da6de1a61d..a8080ab2e1 100644 --- a/src/Management/test/Endpoint.Test/Actuators/HeapDump/HeapDumpActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/HeapDump/HeapDumpActuatorTest.cs @@ -29,7 +29,6 @@ public async Task Registers_dependent_services() services.AddHeapDumpActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/HttpExchangesActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/HttpExchangesActuatorTest.cs index 476a670815..3b2ca41c24 100644 --- a/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/HttpExchangesActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/HttpExchangesActuatorTest.cs @@ -33,7 +33,6 @@ public async Task Registers_dependent_services() services.AddHttpExchangesActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs index 7325d9b4b1..243f494b93 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs @@ -30,7 +30,6 @@ public async Task Registers_dependent_services() services.AddHypermediaActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs index 7d7756a273..5eb8ce37eb 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs @@ -50,7 +50,6 @@ public async Task Registers_dependent_services() services.AddInfoActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs index 0e28b20870..be018bc879 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs @@ -33,7 +33,6 @@ public async Task Registers_dependent_services() services.AddLoggersActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs b/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs index bb571357e7..fc3370d09b 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs @@ -255,7 +255,6 @@ public async Task ThrowsForCallback() builder.Services.AddMvc(options => options.EnableEndpointRouting = false); await using WebApplication host = builder.Build(); - // ReSharper disable once AccessToDisposedClosure Action action = () => host.UseActuatorEndpoints(configureEndpointsCallback); action.Should().ThrowExactly().WithMessage("Customizing endpoints is only supported when using endpoint routing."); diff --git a/src/Management/test/Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs index 7ede0f15bd..377765c18c 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs @@ -31,7 +31,6 @@ public async Task Registers_dependent_services() services.AddRefreshActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RouteMappingsActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RouteMappingsActuatorTest.cs index 6c63c31926..aaa52b889c 100644 --- a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RouteMappingsActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RouteMappingsActuatorTest.cs @@ -39,7 +39,6 @@ public async Task Registers_dependent_services() services.AddRouteMappingsActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Services/ServicesActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Services/ServicesActuatorTest.cs index 5dfae76140..7f0f22dce2 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Services/ServicesActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Services/ServicesActuatorTest.cs @@ -35,7 +35,6 @@ public async Task Registers_dependent_services() services.AddServicesActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/ThreadDump/ThreadDumpActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/ThreadDump/ThreadDumpActuatorTest.cs index a6887e2ad5..0d10ceba71 100644 --- a/src/Management/test/Endpoint.Test/Actuators/ThreadDump/ThreadDumpActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/ThreadDump/ThreadDumpActuatorTest.cs @@ -27,7 +27,6 @@ public async Task Registers_dependent_services() services.AddThreadDumpActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); - // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs b/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs index 81dfc18dee..8d161276df 100644 --- a/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs +++ b/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs @@ -515,7 +515,6 @@ public async Task RecoversFromSpringBootAdminServerRestarts() bool isServerOnline = false; using var handler = new DelegateToMockHttpClientHandler(); - // ReSharper disable once AccessToModifiedClosure MockedRequest registerMock = handler.Mock.When(HttpMethod.Post, "http://sba-server.com/instances").Respond(_ => isServerOnline ? new HttpResponseMessage(HttpStatusCode.OK) { diff --git a/src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs b/src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs index 82bb71d688..278c99f366 100644 --- a/src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs +++ b/src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs @@ -199,7 +199,6 @@ public async Task WebApplication_LogsErrorOnUnknownTask() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(args); using var capturingLoggerProvider = new CapturingLoggerProvider(category => category.StartsWith("Steeltoe.", StringComparison.Ordinal)); - // ReSharper disable once AccessToDisposedClosure builder.Services.AddLogging(options => options.AddProvider(capturingLoggerProvider)); WebApplication app = builder.Build(); diff --git a/src/Steeltoe.All.sln.DotSettings b/src/Steeltoe.All.sln.DotSettings index 7e0a2d1fc4..aceff66495 100644 --- a/src/Steeltoe.All.sln.DotSettings +++ b/src/Steeltoe.All.sln.DotSettings @@ -600,7 +600,6 @@ UseVarWhenEvident False False - False Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the Apache 2.0 License. See the LICENSE file in the project root for more information. From e79fcfa46cf2fa6c31fb570df45cbf37e2d26fc4 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 23 Apr 2026 22:58:43 +0200 Subject: [PATCH 51/81] Convert to .slnx format (#1683) * Convert to .slnx format * Fix R# generated/ignored code --- .github/workflows/Steeltoe.All.yml | 2 +- .github/workflows/package.yml | 2 +- .../scan-vulnerable-dependencies.yml | 2 +- .github/workflows/sonarcube.yml | 2 +- .github/workflows/verify-code-style.yml | 2 +- AGENTS.md | 4 +- cleanupcode.ps1 | 2 +- src/Steeltoe.All.sln | 572 ------------------ src/Steeltoe.All.slnx | 116 ++++ ...Settings => Steeltoe.All.slnx.DotSettings} | 19 +- src/Steeltoe.Common.slnf | 2 +- src/Steeltoe.Configuration.slnf | 2 +- src/Steeltoe.Connectors.slnf | 2 +- src/Steeltoe.Discovery.slnf | 2 +- src/Steeltoe.Logging.slnf | 2 +- src/Steeltoe.Management.slnf | 2 +- src/Steeltoe.Security.slnf | 2 +- 17 files changed, 139 insertions(+), 598 deletions(-) delete mode 100644 src/Steeltoe.All.sln create mode 100644 src/Steeltoe.All.slnx rename src/{Steeltoe.All.sln.DotSettings => Steeltoe.All.slnx.DotSettings} (96%) diff --git a/.github/workflows/Steeltoe.All.yml b/.github/workflows/Steeltoe.All.yml index 32b0eb6c15..0ba18080c8 100644 --- a/.github/workflows/Steeltoe.All.yml +++ b/.github/workflows/Steeltoe.All.yml @@ -20,7 +20,7 @@ env: STEELTOE_MACOS_DIAGNOSE_HOSTNAME_LOOKUP: true DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: true - SOLUTION_FILE: 'src/Steeltoe.All.sln' + SOLUTION_FILE: 'src/Steeltoe.All.slnx' COMMON_TEST_ARGS: >- --no-build --configuration Release --collect "XPlat Code Coverage" --logger trx --results-directory ${{ github.workspace }}/TestOutput --settings coverlet.runsettings --blame-crash --blame-hang-timeout 1m diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 4134cf0610..82af36ca0f 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -21,7 +21,7 @@ permissions: env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: true - SOLUTION_FILE: 'src/Steeltoe.All.sln' + SOLUTION_FILE: 'src/Steeltoe.All.slnx' VERSION_FILE: 'shared-package.props' jobs: diff --git a/.github/workflows/scan-vulnerable-dependencies.yml b/.github/workflows/scan-vulnerable-dependencies.yml index 221643a3ff..5cfe3f066f 100644 --- a/.github/workflows/scan-vulnerable-dependencies.yml +++ b/.github/workflows/scan-vulnerable-dependencies.yml @@ -18,7 +18,7 @@ permissions: env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: true - SOLUTION_FILE: 'src/Steeltoe.All.sln' + SOLUTION_FILE: 'src/Steeltoe.All.slnx' jobs: scan: diff --git a/.github/workflows/sonarcube.yml b/.github/workflows/sonarcube.yml index c336b6e073..4e3222b613 100644 --- a/.github/workflows/sonarcube.yml +++ b/.github/workflows/sonarcube.yml @@ -21,7 +21,7 @@ env: STEELTOE_MACOS_DIAGNOSE_HOSTNAME_LOOKUP: true DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: true - SOLUTION_FILE: 'src/Steeltoe.All.sln' + SOLUTION_FILE: 'src/Steeltoe.All.slnx' SONAR_TEST_ARGS: >- --no-build --configuration Release --collect "XPlat Code Coverage" --logger trx --results-directory ${{ github.workspace }}/TestOutput --settings coverlet.runsettings -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.UseSourceLink=false diff --git a/.github/workflows/verify-code-style.yml b/.github/workflows/verify-code-style.yml index ca649bdfd5..0cab3f9724 100644 --- a/.github/workflows/verify-code-style.yml +++ b/.github/workflows/verify-code-style.yml @@ -18,7 +18,7 @@ permissions: env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: true - SOLUTION_FILE: 'src/Steeltoe.All.sln' + SOLUTION_FILE: 'src/Steeltoe.All.slnx' jobs: verify: diff --git a/AGENTS.md b/AGENTS.md index 51c8d0c29c..003a5b960d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,8 +41,8 @@ dotnet test For final validation before committing changes: ```bash -dotnet build src/Steeltoe.All.sln --configuration Release -dotnet test src/Steeltoe.All.sln --configuration Release +dotnet build src/Steeltoe.All.slnx --configuration Release +dotnet test src/Steeltoe.All.slnx --configuration Release ``` ### Run Tests diff --git a/cleanupcode.ps1 b/cleanupcode.ps1 index 1c5043ff19..f8c18429ab 100644 --- a/cleanupcode.ps1 +++ b/cleanupcode.ps1 @@ -9,7 +9,7 @@ param( $ErrorActionPreference = "Stop" $PSNativeCommandUseErrorActionPreference = $true -$solutionFile = 'src/Steeltoe.All.sln' +$solutionFile = 'src/Steeltoe.All.slnx' dotnet tool restore dotnet restore $solutionFile /p:NuGetAudit=false diff --git a/src/Steeltoe.All.sln b/src/Steeltoe.All.sln deleted file mode 100644 index 43d8724a28..0000000000 --- a/src/Steeltoe.All.sln +++ /dev/null @@ -1,572 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 18 -VisualStudioVersion = 18.0.11222.15 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common", "Common\src\Common\Steeltoe.Common.csproj", "{61812938-5132-4AB6-B48D-2DF4189B3E37}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Test", "Common\test\Common.Test\Steeltoe.Common.Test.csproj", "{12BB796A-63C5-40D0-A2DE-80D9996DE571}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Http", "Common\src\Http\Steeltoe.Common.Http.csproj", "{E58EB8CA-2389-4A28-B0EF-34C2B57BB270}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Net", "Common\src\Net\Steeltoe.Common.Net.csproj", "{8F27A2D4-FEF2-4783-99C4-6B2ABA3D9431}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Certificates", "Common\src\Certificates\Steeltoe.Common.Certificates.csproj", "{DFE642E6-1CD0-4485-AC86-43CEBC451484}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{59874241-E276-4035-B31D-14924889A1C9}" - ProjectSection(SolutionItems) = preProject - Common\README.md = Common\README.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Http.Test", "Common\test\Http.Test\Steeltoe.Common.Http.Test.csproj", "{C67D2028-D637-4D81-84A2-5D64F2E389DA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Net.Test", "Common\test\Net.Test\Steeltoe.Common.Net.Test.csproj", "{1ACC6991-7D4C-48B2-A41C-4B179B19A85C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Certificates.Test", "Common\test\Certificates.Test\Steeltoe.Common.Certificates.Test.csproj", "{880208DF-05C6-4763-A447-744D6C8DBDEA}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_shared", "_shared", "{DC1BC61A-E0FA-4CF9-9F24-D4C564A07836}" - ProjectSection(SolutionItems) = preProject - ..\.editorconfig = ..\.editorconfig - ..\.gitattributes = ..\.gitattributes - ..\.gitignore = ..\.gitignore - ..\cleanupcode.ps1 = ..\cleanupcode.ps1 - ..\coverlet.runsettings = ..\coverlet.runsettings - ..\Directory.Build.targets = ..\Directory.Build.targets - ..\nuget.config = ..\nuget.config - ..\PackageReadme.md = ..\PackageReadme.md - ..\shared-package.props = ..\shared-package.props - ..\shared-project.props = ..\shared-project.props - ..\shared-test.props = ..\shared-test.props - ..\shared.props = ..\shared.props - ..\Steeltoe.Debug.ruleset = ..\Steeltoe.Debug.ruleset - ..\Steeltoe.Release.ruleset = ..\Steeltoe.Release.ruleset - ..\stylecop.json = ..\stylecop.json - ..\versions.props = ..\versions.props - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configuration", "Configuration", "{4AB95F47-0C93-4C88-B87F-231262CD0E89}" - ProjectSection(SolutionItems) = preProject - Configuration\README.md = Configuration\README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Connectors", "Connectors", "{3CBE0336-C3C8-4DC2-AE45-86FC1D1B3C25}" - ProjectSection(SolutionItems) = preProject - Connectors\README.md = Connectors\README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Discovery", "Discovery", "{31BAEBB1-696E-44A1-B1EF-0D150E6D2559}" - ProjectSection(SolutionItems) = preProject - Discovery\README.md = Discovery\README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Logging", "Logging", "{DDB99068-891F-4937-98B0-E72CC3F4964B}" - ProjectSection(SolutionItems) = preProject - Logging\README.md = Logging\README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Management", "Management", "{8BD7D5E5-D887-4BD7-9F42-725A8714F7BC}" - ProjectSection(SolutionItems) = preProject - Management\README.md = Management\README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Security", "Security", "{5128206A-242E-4069-AD30-910EDC40B165}" - ProjectSection(SolutionItems) = preProject - Security\README.md = Security\README.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.CloudFoundry", "Configuration\src\CloudFoundry\Steeltoe.Configuration.CloudFoundry.csproj", "{3084B070-6F72-4673-889F-EF00DADB468B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.ConfigServer", "Configuration\src\ConfigServer\Steeltoe.Configuration.ConfigServer.csproj", "{727CC05F-A99D-474F-9C66-A9E57D6B25CC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.Placeholder", "Configuration\src\Placeholder\Steeltoe.Configuration.Placeholder.csproj", "{EBFDDFDE-BF5D-4607-AADA-7039445E2AB7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.RandomValue", "Configuration\src\RandomValue\Steeltoe.Configuration.RandomValue.csproj", "{C51FBF31-BFEE-460B-B182-49CD65F8F8EA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.CloudFoundry.Test", "Configuration\test\CloudFoundry.Test\Steeltoe.Configuration.CloudFoundry.Test.csproj", "{0E5746A9-C13B-4BB9-BF3B-C524F4E5BC3A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.ConfigServer.Integration.Test", "Configuration\test\ConfigServer.Integration.Test\Steeltoe.Configuration.ConfigServer.Integration.Test.csproj", "{25F1BF2F-1B6A-466A-A3BF-8963B0D040F4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.ConfigServer.Test", "Configuration\test\ConfigServer.Test\Steeltoe.Configuration.ConfigServer.Test.csproj", "{5802F580-3E70-45F4-AF60-4F4E5F8FAADA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.Placeholder.Test", "Configuration\test\Placeholder.Test\Steeltoe.Configuration.Placeholder.Test.csproj", "{1E9A51CC-1E5E-4BB1-9E47-58D6C2DB8450}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.RandomValue.Test", "Configuration\test\RandomValue.Test\Steeltoe.Configuration.RandomValue.Test.csproj", "{98489BC4-B03B-4703-B25D-A89D617E761F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Connectors.EntityFrameworkCore", "Connectors\src\EntityFrameworkCore\Steeltoe.Connectors.EntityFrameworkCore.csproj", "{B1B7522F-6EF2-4935-BE9F-66B6DB974303}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Connectors", "Connectors\src\Connectors\Steeltoe.Connectors.csproj", "{34891C2A-FAF2-466A-90A6-F25DBD8283E8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Connectors.EntityFrameworkCore.Test", "Connectors\test\EntityFrameworkCore.Test\Steeltoe.Connectors.EntityFrameworkCore.Test.csproj", "{72C7AD35-98E4-478E-B594-06A3DB2E61D6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Discovery.Consul", "Discovery\src\Consul\Steeltoe.Discovery.Consul.csproj", "{C3098547-C3B5-4136-9CEB-30466EE1FA53}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Discovery.Eureka", "Discovery\src\Eureka\Steeltoe.Discovery.Eureka.csproj", "{E6DBF77D-04A3-4422-A7D2-D3131DF5B829}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Discovery.Consul.Test", "Discovery\test\Consul.Test\Steeltoe.Discovery.Consul.Test.csproj", "{B5D0C674-1958-43BF-B644-0865E9DDCCBD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Discovery.Eureka.Test", "Discovery\test\Eureka.Test\Steeltoe.Discovery.Eureka.Test.csproj", "{23B95817-4183-4B42-BF27-EA575184DB2D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Logging.DynamicConsole", "Logging\src\DynamicConsole\Steeltoe.Logging.DynamicConsole.csproj", "{43DC1B99-588C-4984-AEAC-43AA5B47D84C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Logging.DynamicConsole.Test", "Logging\test\DynamicConsole.Test\Steeltoe.Logging.DynamicConsole.Test.csproj", "{04BC571A-0055-4B9B-805E-D2B0823C9D8D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Management.Tasks", "Management\src\Tasks\Steeltoe.Management.Tasks.csproj", "{990DCEE9-D88F-4B9F-8298-678B524AC4D6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Management.Endpoint", "Management\src\Endpoint\Steeltoe.Management.Endpoint.csproj", "{557AFE26-B89D-497D-92B6-8268D04396E5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Management.Tracing", "Management\src\Tracing\Steeltoe.Management.Tracing.csproj", "{4EFBD262-86B2-4E21-8608-B1894E04EA99}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Management.Endpoint.Test", "Management\test\Endpoint.Test\Steeltoe.Management.Endpoint.Test.csproj", "{C2EFCD80-C96D-4A09-BFD6-02CB4603961C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Management.Tracing.Test", "Management\test\Tracing.Test\Steeltoe.Management.Tracing.Test.csproj", "{C437628B-43EE-4AAD-83ED-DDE7499E705B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Management.Tasks.Test", "Management\test\Tasks.Test\Steeltoe.Management.Tasks.Test.csproj", "{A4D6D08A-BC0C-4CAD-97E6-F4BAAA03928C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.DataProtection.Redis", "Security\src\DataProtection.Redis\Steeltoe.Security.DataProtection.Redis.csproj", "{31E52250-3422-49E9-9605-EBE786CC1BE3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.DataProtection.Redis.Test", "Security\test\DataProtection.Redis.Test\Steeltoe.Security.DataProtection.Redis.Test.csproj", "{6D09B0D3-AF28-4FC4-A67D-424E7746682B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.TestResources", "Common\test\TestResources\Steeltoe.Common.TestResources.csproj", "{65CF716D-9215-475C-B37F-CA943F5CD6A3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Hosting", "Common\src\Hosting\Steeltoe.Common.Hosting.csproj", "{BEB156E9-4E06-403A-B778-E7B092B79962}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Hosting.Test", "Common\test\Hosting.Test\Steeltoe.Common.Hosting.Test.csproj", "{BA80CEAD-02A2-480D-93F4-907B01BDB219}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Management.Abstractions", "Management\src\Abstractions\Steeltoe.Management.Abstractions.csproj", "{F9095412-B7FB-4A8A-A291-141458A988AF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.Abstractions", "Configuration\src\Abstractions\Steeltoe.Configuration.Abstractions.csproj", "{920BDA8F-094B-4D81-A03C-664C0F808DC7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Logging.Abstractions", "Logging\src\Abstractions\Steeltoe.Logging.Abstractions.csproj", "{86FBBCC7-97F5-4795-A61E-FA7E0CE0514D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Connectors.Test", "Connectors\test\Connectors.Test\Steeltoe.Connectors.Test.csproj", "{C3232459-EE86-430B-B310-EB32C991A11A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Logging.DynamicSerilog", "Logging\src\DynamicSerilog\Steeltoe.Logging.DynamicSerilog.csproj", "{E0B1415D-8D97-4BA5-9315-82E7D33E3933}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Logging.DynamicSerilog.Test", "Logging\test\DynamicSerilog.Test\Steeltoe.Logging.DynamicSerilog.Test.csproj", "{6C116C3E-177A-44C0-A149-8DDB62812DC3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Discovery.HttpClients", "Discovery\src\HttpClients\Steeltoe.Discovery.HttpClients.csproj", "{E2BE4A3F-1C35-41ED-99D1-7630B5369942}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Discovery.HttpClients.Test", "Discovery\test\HttpClients.Test\Steeltoe.Discovery.HttpClients.Test.csproj", "{C5DDEE8C-E3EF-49BC-AEEE-B35271FC512A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.SpringBoot", "Configuration\src\SpringBoot\Steeltoe.Configuration.SpringBoot.csproj", "{D94706F7-7622-4D4C-87BD-84FB40D4E6BD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.SpringBoot.Test", "Configuration\test\SpringBoot.Test\Steeltoe.Configuration.SpringBoot.Test.csproj", "{BA38127A-DA5A-437F-9C84-5997FBE0B6A0}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Bootstrap", "Bootstrap", "{EA9C3A73-3F31-4DC9-982C-963CE613E119}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Bootstrap.AutoConfiguration", "Bootstrap\src\AutoConfiguration\Steeltoe.Bootstrap.AutoConfiguration.csproj", "{38EFC635-0C56-442D-8CA3-20AEC1930D7B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Bootstrap.AutoConfiguration.Test", "Bootstrap\test\AutoConfiguration.Test\Steeltoe.Bootstrap.AutoConfiguration.Test.csproj", "{6499285A-88CF-426A-909D-307382B91AA3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.Kubernetes.ServiceBindings", "Configuration\src\Kubernetes.ServiceBindings\Steeltoe.Configuration.Kubernetes.ServiceBindings.csproj", "{6A064B5A-21F7-452A-AAFF-3C0A68286FDF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.Kubernetes.ServiceBindings.Test", "Configuration\test\Kubernetes.ServiceBindings.Test\Steeltoe.Configuration.Kubernetes.ServiceBindings.Test.csproj", "{0D68FC03-7FD4-4280-852B-0E620872CE22}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.Encryption.Test", "Configuration\test\Encryption.Test\Steeltoe.Configuration.Encryption.Test.csproj", "{BCC3F7DC-080D-4A7E-8060-40ED65073EB3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.Encryption", "Configuration\src\Encryption\Steeltoe.Configuration.Encryption.csproj", "{764B3355-FD20-42E3-B452-357FCC20A27D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Management.Prometheus", "Management\src\Prometheus\Steeltoe.Management.Prometheus.csproj", "{7603B7DF-0297-4CF7-BB31-D5B8BABF7D5B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Bootstrap.EmptyAutoConfiguration.Test", "Bootstrap\test\EmptyAutoConfiguration.Test\Steeltoe.Bootstrap.EmptyAutoConfiguration.Test.csproj", "{C821DCC5-880F-4F85-97B1-6D9A56DAD6F0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Management.Prometheus.Test", "Management\test\Prometheus.Test\Steeltoe.Management.Prometheus.Test.csproj", "{7866263B-936E-4604-A821-73C46BAB1657}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Discovery.Configuration", "Discovery\src\Configuration\Steeltoe.Discovery.Configuration.csproj", "{EA3AFC13-2399-411E-AD20-C6677505615A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Discovery.Configuration.Test", "Discovery\test\Configuration.Test\Steeltoe.Discovery.Configuration.Test.csproj", "{5031A608-281E-4FAF-9501-1DBDAB645907}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.ConfigServer.Discovery.Test", "Configuration\test\ConfigServer.Discovery.Test\Steeltoe.Configuration.ConfigServer.Discovery.Test.csproj", "{10EC7705-BE9E-4E2A-B174-E74D1CC9852F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.OpenIdConnect", "Security\src\Authentication.OpenIdConnect\Steeltoe.Security.Authentication.OpenIdConnect.csproj", "{AF26B4AC-35B5-4954-ADE8-97BA2DC31250}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.JwtBearer", "Security\src\Authentication.JwtBearer\Steeltoe.Security.Authentication.JwtBearer.csproj", "{5EBE664D-D071-43A9-9B53-346BE3DCCB41}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.JwtBearer.Test", "Security\test\Authentication.JwtBearer.Test\Steeltoe.Security.Authentication.JwtBearer.Test.csproj", "{196A7EF1-56CE-481E-BB49-97BD0FF096BE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authorization.Certificate", "Security\src\Authorization.Certificate\Steeltoe.Security.Authorization.Certificate.csproj", "{B0AC88EC-EB6C-4E73-B9D5-51DD213931B4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authorization.Certificate.Test", "Security\test\Authorization.Certificate.Test\Steeltoe.Security.Authorization.Certificate.Test.csproj", "{AAA51F15-E5BE-4E77-92DF-1992434D557A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.OpenIdConnect.Test", "Security\test\Authentication.OpenIdConnect.Test\Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj", "{9E83FDF7-D838-4E7E-A767-6FEAC6BC1C5D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_tools", "_tools", "{DF26D677-72A3-442D-B556-46E3D0EF4A77}" - ProjectSection(SolutionItems) = preProject - Tools\src\package.targets = Tools\src\package.targets - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationSchemaGenerator", "Tools\src\ConfigurationSchemaGenerator\ConfigurationSchemaGenerator.csproj", "{2A975FB7-401B-41BB-96A4-1DF0036888A9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationSchemaGenerator.Tests", "Tools\test\ConfigurationSchemaGenerator.Tests\ConfigurationSchemaGenerator.Tests.csproj", "{C4C38F83-8410-443C-9599-ACFB5FA7CD2D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Logging", "Common\src\Logging\Steeltoe.Common.Logging.csproj", "{738AFF97-B4C4-4EAC-B9C5-C405D481C92B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Logging.Test", "Common\test\Logging.Test\Steeltoe.Common.Logging.Test.csproj", "{A9CCC214-212A-4296-98F5-65ADDB2BB8B4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Management.Endpoint.RazorPagesTestWebApp", "Management\test\RazorPagesTestWebApp\Steeltoe.Management.Endpoint.RazorPagesTestWebApp.csproj", "{51DD3135-1EAA-4640-82F1-9FBECA421708}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {61812938-5132-4AB6-B48D-2DF4189B3E37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {61812938-5132-4AB6-B48D-2DF4189B3E37}.Debug|Any CPU.Build.0 = Debug|Any CPU - {61812938-5132-4AB6-B48D-2DF4189B3E37}.Release|Any CPU.ActiveCfg = Release|Any CPU - {61812938-5132-4AB6-B48D-2DF4189B3E37}.Release|Any CPU.Build.0 = Release|Any CPU - {12BB796A-63C5-40D0-A2DE-80D9996DE571}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {12BB796A-63C5-40D0-A2DE-80D9996DE571}.Debug|Any CPU.Build.0 = Debug|Any CPU - {12BB796A-63C5-40D0-A2DE-80D9996DE571}.Release|Any CPU.ActiveCfg = Release|Any CPU - {12BB796A-63C5-40D0-A2DE-80D9996DE571}.Release|Any CPU.Build.0 = Release|Any CPU - {E58EB8CA-2389-4A28-B0EF-34C2B57BB270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E58EB8CA-2389-4A28-B0EF-34C2B57BB270}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E58EB8CA-2389-4A28-B0EF-34C2B57BB270}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E58EB8CA-2389-4A28-B0EF-34C2B57BB270}.Release|Any CPU.Build.0 = Release|Any CPU - {8F27A2D4-FEF2-4783-99C4-6B2ABA3D9431}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8F27A2D4-FEF2-4783-99C4-6B2ABA3D9431}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8F27A2D4-FEF2-4783-99C4-6B2ABA3D9431}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8F27A2D4-FEF2-4783-99C4-6B2ABA3D9431}.Release|Any CPU.Build.0 = Release|Any CPU - {DFE642E6-1CD0-4485-AC86-43CEBC451484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DFE642E6-1CD0-4485-AC86-43CEBC451484}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DFE642E6-1CD0-4485-AC86-43CEBC451484}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DFE642E6-1CD0-4485-AC86-43CEBC451484}.Release|Any CPU.Build.0 = Release|Any CPU - {C67D2028-D637-4D81-84A2-5D64F2E389DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C67D2028-D637-4D81-84A2-5D64F2E389DA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C67D2028-D637-4D81-84A2-5D64F2E389DA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C67D2028-D637-4D81-84A2-5D64F2E389DA}.Release|Any CPU.Build.0 = Release|Any CPU - {1ACC6991-7D4C-48B2-A41C-4B179B19A85C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1ACC6991-7D4C-48B2-A41C-4B179B19A85C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1ACC6991-7D4C-48B2-A41C-4B179B19A85C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1ACC6991-7D4C-48B2-A41C-4B179B19A85C}.Release|Any CPU.Build.0 = Release|Any CPU - {880208DF-05C6-4763-A447-744D6C8DBDEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {880208DF-05C6-4763-A447-744D6C8DBDEA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {880208DF-05C6-4763-A447-744D6C8DBDEA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {880208DF-05C6-4763-A447-744D6C8DBDEA}.Release|Any CPU.Build.0 = Release|Any CPU - {3084B070-6F72-4673-889F-EF00DADB468B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3084B070-6F72-4673-889F-EF00DADB468B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3084B070-6F72-4673-889F-EF00DADB468B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3084B070-6F72-4673-889F-EF00DADB468B}.Release|Any CPU.Build.0 = Release|Any CPU - {727CC05F-A99D-474F-9C66-A9E57D6B25CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {727CC05F-A99D-474F-9C66-A9E57D6B25CC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {727CC05F-A99D-474F-9C66-A9E57D6B25CC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {727CC05F-A99D-474F-9C66-A9E57D6B25CC}.Release|Any CPU.Build.0 = Release|Any CPU - {EBFDDFDE-BF5D-4607-AADA-7039445E2AB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EBFDDFDE-BF5D-4607-AADA-7039445E2AB7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EBFDDFDE-BF5D-4607-AADA-7039445E2AB7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EBFDDFDE-BF5D-4607-AADA-7039445E2AB7}.Release|Any CPU.Build.0 = Release|Any CPU - {C51FBF31-BFEE-460B-B182-49CD65F8F8EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C51FBF31-BFEE-460B-B182-49CD65F8F8EA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C51FBF31-BFEE-460B-B182-49CD65F8F8EA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C51FBF31-BFEE-460B-B182-49CD65F8F8EA}.Release|Any CPU.Build.0 = Release|Any CPU - {0E5746A9-C13B-4BB9-BF3B-C524F4E5BC3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0E5746A9-C13B-4BB9-BF3B-C524F4E5BC3A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0E5746A9-C13B-4BB9-BF3B-C524F4E5BC3A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0E5746A9-C13B-4BB9-BF3B-C524F4E5BC3A}.Release|Any CPU.Build.0 = Release|Any CPU - {25F1BF2F-1B6A-466A-A3BF-8963B0D040F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {25F1BF2F-1B6A-466A-A3BF-8963B0D040F4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {25F1BF2F-1B6A-466A-A3BF-8963B0D040F4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {25F1BF2F-1B6A-466A-A3BF-8963B0D040F4}.Release|Any CPU.Build.0 = Release|Any CPU - {5802F580-3E70-45F4-AF60-4F4E5F8FAADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5802F580-3E70-45F4-AF60-4F4E5F8FAADA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5802F580-3E70-45F4-AF60-4F4E5F8FAADA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5802F580-3E70-45F4-AF60-4F4E5F8FAADA}.Release|Any CPU.Build.0 = Release|Any CPU - {1E9A51CC-1E5E-4BB1-9E47-58D6C2DB8450}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E9A51CC-1E5E-4BB1-9E47-58D6C2DB8450}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E9A51CC-1E5E-4BB1-9E47-58D6C2DB8450}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E9A51CC-1E5E-4BB1-9E47-58D6C2DB8450}.Release|Any CPU.Build.0 = Release|Any CPU - {98489BC4-B03B-4703-B25D-A89D617E761F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {98489BC4-B03B-4703-B25D-A89D617E761F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {98489BC4-B03B-4703-B25D-A89D617E761F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {98489BC4-B03B-4703-B25D-A89D617E761F}.Release|Any CPU.Build.0 = Release|Any CPU - {B1B7522F-6EF2-4935-BE9F-66B6DB974303}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B1B7522F-6EF2-4935-BE9F-66B6DB974303}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B1B7522F-6EF2-4935-BE9F-66B6DB974303}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B1B7522F-6EF2-4935-BE9F-66B6DB974303}.Release|Any CPU.Build.0 = Release|Any CPU - {34891C2A-FAF2-466A-90A6-F25DBD8283E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {34891C2A-FAF2-466A-90A6-F25DBD8283E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {34891C2A-FAF2-466A-90A6-F25DBD8283E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {34891C2A-FAF2-466A-90A6-F25DBD8283E8}.Release|Any CPU.Build.0 = Release|Any CPU - {72C7AD35-98E4-478E-B594-06A3DB2E61D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {72C7AD35-98E4-478E-B594-06A3DB2E61D6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {72C7AD35-98E4-478E-B594-06A3DB2E61D6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {72C7AD35-98E4-478E-B594-06A3DB2E61D6}.Release|Any CPU.Build.0 = Release|Any CPU - {C3098547-C3B5-4136-9CEB-30466EE1FA53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C3098547-C3B5-4136-9CEB-30466EE1FA53}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C3098547-C3B5-4136-9CEB-30466EE1FA53}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C3098547-C3B5-4136-9CEB-30466EE1FA53}.Release|Any CPU.Build.0 = Release|Any CPU - {E6DBF77D-04A3-4422-A7D2-D3131DF5B829}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E6DBF77D-04A3-4422-A7D2-D3131DF5B829}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E6DBF77D-04A3-4422-A7D2-D3131DF5B829}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E6DBF77D-04A3-4422-A7D2-D3131DF5B829}.Release|Any CPU.Build.0 = Release|Any CPU - {B5D0C674-1958-43BF-B644-0865E9DDCCBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B5D0C674-1958-43BF-B644-0865E9DDCCBD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B5D0C674-1958-43BF-B644-0865E9DDCCBD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B5D0C674-1958-43BF-B644-0865E9DDCCBD}.Release|Any CPU.Build.0 = Release|Any CPU - {23B95817-4183-4B42-BF27-EA575184DB2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23B95817-4183-4B42-BF27-EA575184DB2D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23B95817-4183-4B42-BF27-EA575184DB2D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23B95817-4183-4B42-BF27-EA575184DB2D}.Release|Any CPU.Build.0 = Release|Any CPU - {43DC1B99-588C-4984-AEAC-43AA5B47D84C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {43DC1B99-588C-4984-AEAC-43AA5B47D84C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {43DC1B99-588C-4984-AEAC-43AA5B47D84C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {43DC1B99-588C-4984-AEAC-43AA5B47D84C}.Release|Any CPU.Build.0 = Release|Any CPU - {04BC571A-0055-4B9B-805E-D2B0823C9D8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {04BC571A-0055-4B9B-805E-D2B0823C9D8D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {04BC571A-0055-4B9B-805E-D2B0823C9D8D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {04BC571A-0055-4B9B-805E-D2B0823C9D8D}.Release|Any CPU.Build.0 = Release|Any CPU - {990DCEE9-D88F-4B9F-8298-678B524AC4D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {990DCEE9-D88F-4B9F-8298-678B524AC4D6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {990DCEE9-D88F-4B9F-8298-678B524AC4D6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {990DCEE9-D88F-4B9F-8298-678B524AC4D6}.Release|Any CPU.Build.0 = Release|Any CPU - {557AFE26-B89D-497D-92B6-8268D04396E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {557AFE26-B89D-497D-92B6-8268D04396E5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {557AFE26-B89D-497D-92B6-8268D04396E5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {557AFE26-B89D-497D-92B6-8268D04396E5}.Release|Any CPU.Build.0 = Release|Any CPU - {4EFBD262-86B2-4E21-8608-B1894E04EA99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4EFBD262-86B2-4E21-8608-B1894E04EA99}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4EFBD262-86B2-4E21-8608-B1894E04EA99}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4EFBD262-86B2-4E21-8608-B1894E04EA99}.Release|Any CPU.Build.0 = Release|Any CPU - {C2EFCD80-C96D-4A09-BFD6-02CB4603961C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C2EFCD80-C96D-4A09-BFD6-02CB4603961C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C2EFCD80-C96D-4A09-BFD6-02CB4603961C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C2EFCD80-C96D-4A09-BFD6-02CB4603961C}.Release|Any CPU.Build.0 = Release|Any CPU - {C437628B-43EE-4AAD-83ED-DDE7499E705B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C437628B-43EE-4AAD-83ED-DDE7499E705B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C437628B-43EE-4AAD-83ED-DDE7499E705B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C437628B-43EE-4AAD-83ED-DDE7499E705B}.Release|Any CPU.Build.0 = Release|Any CPU - {A4D6D08A-BC0C-4CAD-97E6-F4BAAA03928C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4D6D08A-BC0C-4CAD-97E6-F4BAAA03928C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4D6D08A-BC0C-4CAD-97E6-F4BAAA03928C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A4D6D08A-BC0C-4CAD-97E6-F4BAAA03928C}.Release|Any CPU.Build.0 = Release|Any CPU - {31E52250-3422-49E9-9605-EBE786CC1BE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {31E52250-3422-49E9-9605-EBE786CC1BE3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {31E52250-3422-49E9-9605-EBE786CC1BE3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {31E52250-3422-49E9-9605-EBE786CC1BE3}.Release|Any CPU.Build.0 = Release|Any CPU - {6D09B0D3-AF28-4FC4-A67D-424E7746682B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D09B0D3-AF28-4FC4-A67D-424E7746682B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D09B0D3-AF28-4FC4-A67D-424E7746682B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D09B0D3-AF28-4FC4-A67D-424E7746682B}.Release|Any CPU.Build.0 = Release|Any CPU - {65CF716D-9215-475C-B37F-CA943F5CD6A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {65CF716D-9215-475C-B37F-CA943F5CD6A3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {65CF716D-9215-475C-B37F-CA943F5CD6A3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {65CF716D-9215-475C-B37F-CA943F5CD6A3}.Release|Any CPU.Build.0 = Release|Any CPU - {BEB156E9-4E06-403A-B778-E7B092B79962}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BEB156E9-4E06-403A-B778-E7B092B79962}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BEB156E9-4E06-403A-B778-E7B092B79962}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BEB156E9-4E06-403A-B778-E7B092B79962}.Release|Any CPU.Build.0 = Release|Any CPU - {BA80CEAD-02A2-480D-93F4-907B01BDB219}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BA80CEAD-02A2-480D-93F4-907B01BDB219}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BA80CEAD-02A2-480D-93F4-907B01BDB219}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BA80CEAD-02A2-480D-93F4-907B01BDB219}.Release|Any CPU.Build.0 = Release|Any CPU - {F9095412-B7FB-4A8A-A291-141458A988AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F9095412-B7FB-4A8A-A291-141458A988AF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F9095412-B7FB-4A8A-A291-141458A988AF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F9095412-B7FB-4A8A-A291-141458A988AF}.Release|Any CPU.Build.0 = Release|Any CPU - {920BDA8F-094B-4D81-A03C-664C0F808DC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {920BDA8F-094B-4D81-A03C-664C0F808DC7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {920BDA8F-094B-4D81-A03C-664C0F808DC7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {920BDA8F-094B-4D81-A03C-664C0F808DC7}.Release|Any CPU.Build.0 = Release|Any CPU - {86FBBCC7-97F5-4795-A61E-FA7E0CE0514D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {86FBBCC7-97F5-4795-A61E-FA7E0CE0514D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {86FBBCC7-97F5-4795-A61E-FA7E0CE0514D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {86FBBCC7-97F5-4795-A61E-FA7E0CE0514D}.Release|Any CPU.Build.0 = Release|Any CPU - {C3232459-EE86-430B-B310-EB32C991A11A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C3232459-EE86-430B-B310-EB32C991A11A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C3232459-EE86-430B-B310-EB32C991A11A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C3232459-EE86-430B-B310-EB32C991A11A}.Release|Any CPU.Build.0 = Release|Any CPU - {E0B1415D-8D97-4BA5-9315-82E7D33E3933}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E0B1415D-8D97-4BA5-9315-82E7D33E3933}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E0B1415D-8D97-4BA5-9315-82E7D33E3933}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E0B1415D-8D97-4BA5-9315-82E7D33E3933}.Release|Any CPU.Build.0 = Release|Any CPU - {6C116C3E-177A-44C0-A149-8DDB62812DC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6C116C3E-177A-44C0-A149-8DDB62812DC3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6C116C3E-177A-44C0-A149-8DDB62812DC3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6C116C3E-177A-44C0-A149-8DDB62812DC3}.Release|Any CPU.Build.0 = Release|Any CPU - {E2BE4A3F-1C35-41ED-99D1-7630B5369942}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E2BE4A3F-1C35-41ED-99D1-7630B5369942}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E2BE4A3F-1C35-41ED-99D1-7630B5369942}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E2BE4A3F-1C35-41ED-99D1-7630B5369942}.Release|Any CPU.Build.0 = Release|Any CPU - {C5DDEE8C-E3EF-49BC-AEEE-B35271FC512A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C5DDEE8C-E3EF-49BC-AEEE-B35271FC512A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C5DDEE8C-E3EF-49BC-AEEE-B35271FC512A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C5DDEE8C-E3EF-49BC-AEEE-B35271FC512A}.Release|Any CPU.Build.0 = Release|Any CPU - {D94706F7-7622-4D4C-87BD-84FB40D4E6BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D94706F7-7622-4D4C-87BD-84FB40D4E6BD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D94706F7-7622-4D4C-87BD-84FB40D4E6BD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D94706F7-7622-4D4C-87BD-84FB40D4E6BD}.Release|Any CPU.Build.0 = Release|Any CPU - {BA38127A-DA5A-437F-9C84-5997FBE0B6A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BA38127A-DA5A-437F-9C84-5997FBE0B6A0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BA38127A-DA5A-437F-9C84-5997FBE0B6A0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BA38127A-DA5A-437F-9C84-5997FBE0B6A0}.Release|Any CPU.Build.0 = Release|Any CPU - {38EFC635-0C56-442D-8CA3-20AEC1930D7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {38EFC635-0C56-442D-8CA3-20AEC1930D7B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {38EFC635-0C56-442D-8CA3-20AEC1930D7B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {38EFC635-0C56-442D-8CA3-20AEC1930D7B}.Release|Any CPU.Build.0 = Release|Any CPU - {6499285A-88CF-426A-909D-307382B91AA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6499285A-88CF-426A-909D-307382B91AA3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6499285A-88CF-426A-909D-307382B91AA3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6499285A-88CF-426A-909D-307382B91AA3}.Release|Any CPU.Build.0 = Release|Any CPU - {6A064B5A-21F7-452A-AAFF-3C0A68286FDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6A064B5A-21F7-452A-AAFF-3C0A68286FDF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6A064B5A-21F7-452A-AAFF-3C0A68286FDF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6A064B5A-21F7-452A-AAFF-3C0A68286FDF}.Release|Any CPU.Build.0 = Release|Any CPU - {0D68FC03-7FD4-4280-852B-0E620872CE22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0D68FC03-7FD4-4280-852B-0E620872CE22}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0D68FC03-7FD4-4280-852B-0E620872CE22}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0D68FC03-7FD4-4280-852B-0E620872CE22}.Release|Any CPU.Build.0 = Release|Any CPU - {BCC3F7DC-080D-4A7E-8060-40ED65073EB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BCC3F7DC-080D-4A7E-8060-40ED65073EB3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BCC3F7DC-080D-4A7E-8060-40ED65073EB3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BCC3F7DC-080D-4A7E-8060-40ED65073EB3}.Release|Any CPU.Build.0 = Release|Any CPU - {764B3355-FD20-42E3-B452-357FCC20A27D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {764B3355-FD20-42E3-B452-357FCC20A27D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {764B3355-FD20-42E3-B452-357FCC20A27D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {764B3355-FD20-42E3-B452-357FCC20A27D}.Release|Any CPU.Build.0 = Release|Any CPU - {7603B7DF-0297-4CF7-BB31-D5B8BABF7D5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7603B7DF-0297-4CF7-BB31-D5B8BABF7D5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7603B7DF-0297-4CF7-BB31-D5B8BABF7D5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7603B7DF-0297-4CF7-BB31-D5B8BABF7D5B}.Release|Any CPU.Build.0 = Release|Any CPU - {C821DCC5-880F-4F85-97B1-6D9A56DAD6F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C821DCC5-880F-4F85-97B1-6D9A56DAD6F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C821DCC5-880F-4F85-97B1-6D9A56DAD6F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C821DCC5-880F-4F85-97B1-6D9A56DAD6F0}.Release|Any CPU.Build.0 = Release|Any CPU - {7866263B-936E-4604-A821-73C46BAB1657}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7866263B-936E-4604-A821-73C46BAB1657}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7866263B-936E-4604-A821-73C46BAB1657}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7866263B-936E-4604-A821-73C46BAB1657}.Release|Any CPU.Build.0 = Release|Any CPU - {EA3AFC13-2399-411E-AD20-C6677505615A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EA3AFC13-2399-411E-AD20-C6677505615A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA3AFC13-2399-411E-AD20-C6677505615A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EA3AFC13-2399-411E-AD20-C6677505615A}.Release|Any CPU.Build.0 = Release|Any CPU - {5031A608-281E-4FAF-9501-1DBDAB645907}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5031A608-281E-4FAF-9501-1DBDAB645907}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5031A608-281E-4FAF-9501-1DBDAB645907}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5031A608-281E-4FAF-9501-1DBDAB645907}.Release|Any CPU.Build.0 = Release|Any CPU - {10EC7705-BE9E-4E2A-B174-E74D1CC9852F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {10EC7705-BE9E-4E2A-B174-E74D1CC9852F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {10EC7705-BE9E-4E2A-B174-E74D1CC9852F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {10EC7705-BE9E-4E2A-B174-E74D1CC9852F}.Release|Any CPU.Build.0 = Release|Any CPU - {AF26B4AC-35B5-4954-ADE8-97BA2DC31250}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AF26B4AC-35B5-4954-ADE8-97BA2DC31250}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AF26B4AC-35B5-4954-ADE8-97BA2DC31250}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AF26B4AC-35B5-4954-ADE8-97BA2DC31250}.Release|Any CPU.Build.0 = Release|Any CPU - {5EBE664D-D071-43A9-9B53-346BE3DCCB41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5EBE664D-D071-43A9-9B53-346BE3DCCB41}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5EBE664D-D071-43A9-9B53-346BE3DCCB41}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5EBE664D-D071-43A9-9B53-346BE3DCCB41}.Release|Any CPU.Build.0 = Release|Any CPU - {196A7EF1-56CE-481E-BB49-97BD0FF096BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {196A7EF1-56CE-481E-BB49-97BD0FF096BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {196A7EF1-56CE-481E-BB49-97BD0FF096BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {196A7EF1-56CE-481E-BB49-97BD0FF096BE}.Release|Any CPU.Build.0 = Release|Any CPU - {B0AC88EC-EB6C-4E73-B9D5-51DD213931B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B0AC88EC-EB6C-4E73-B9D5-51DD213931B4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B0AC88EC-EB6C-4E73-B9D5-51DD213931B4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B0AC88EC-EB6C-4E73-B9D5-51DD213931B4}.Release|Any CPU.Build.0 = Release|Any CPU - {AAA51F15-E5BE-4E77-92DF-1992434D557A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AAA51F15-E5BE-4E77-92DF-1992434D557A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AAA51F15-E5BE-4E77-92DF-1992434D557A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AAA51F15-E5BE-4E77-92DF-1992434D557A}.Release|Any CPU.Build.0 = Release|Any CPU - {9E83FDF7-D838-4E7E-A767-6FEAC6BC1C5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9E83FDF7-D838-4E7E-A767-6FEAC6BC1C5D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9E83FDF7-D838-4E7E-A767-6FEAC6BC1C5D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9E83FDF7-D838-4E7E-A767-6FEAC6BC1C5D}.Release|Any CPU.Build.0 = Release|Any CPU - {2A975FB7-401B-41BB-96A4-1DF0036888A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2A975FB7-401B-41BB-96A4-1DF0036888A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A975FB7-401B-41BB-96A4-1DF0036888A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2A975FB7-401B-41BB-96A4-1DF0036888A9}.Release|Any CPU.Build.0 = Release|Any CPU - {C4C38F83-8410-443C-9599-ACFB5FA7CD2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4C38F83-8410-443C-9599-ACFB5FA7CD2D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4C38F83-8410-443C-9599-ACFB5FA7CD2D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4C38F83-8410-443C-9599-ACFB5FA7CD2D}.Release|Any CPU.Build.0 = Release|Any CPU - {738AFF97-B4C4-4EAC-B9C5-C405D481C92B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {738AFF97-B4C4-4EAC-B9C5-C405D481C92B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {738AFF97-B4C4-4EAC-B9C5-C405D481C92B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {738AFF97-B4C4-4EAC-B9C5-C405D481C92B}.Release|Any CPU.Build.0 = Release|Any CPU - {A9CCC214-212A-4296-98F5-65ADDB2BB8B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A9CCC214-212A-4296-98F5-65ADDB2BB8B4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A9CCC214-212A-4296-98F5-65ADDB2BB8B4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A9CCC214-212A-4296-98F5-65ADDB2BB8B4}.Release|Any CPU.Build.0 = Release|Any CPU - {51DD3135-1EAA-4640-82F1-9FBECA421708}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {51DD3135-1EAA-4640-82F1-9FBECA421708}.Debug|Any CPU.Build.0 = Debug|Any CPU - {51DD3135-1EAA-4640-82F1-9FBECA421708}.Release|Any CPU.ActiveCfg = Release|Any CPU - {51DD3135-1EAA-4640-82F1-9FBECA421708}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {61812938-5132-4AB6-B48D-2DF4189B3E37} = {59874241-E276-4035-B31D-14924889A1C9} - {12BB796A-63C5-40D0-A2DE-80D9996DE571} = {59874241-E276-4035-B31D-14924889A1C9} - {E58EB8CA-2389-4A28-B0EF-34C2B57BB270} = {59874241-E276-4035-B31D-14924889A1C9} - {8F27A2D4-FEF2-4783-99C4-6B2ABA3D9431} = {59874241-E276-4035-B31D-14924889A1C9} - {DFE642E6-1CD0-4485-AC86-43CEBC451484} = {59874241-E276-4035-B31D-14924889A1C9} - {C67D2028-D637-4D81-84A2-5D64F2E389DA} = {59874241-E276-4035-B31D-14924889A1C9} - {1ACC6991-7D4C-48B2-A41C-4B179B19A85C} = {59874241-E276-4035-B31D-14924889A1C9} - {880208DF-05C6-4763-A447-744D6C8DBDEA} = {59874241-E276-4035-B31D-14924889A1C9} - {3084B070-6F72-4673-889F-EF00DADB468B} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {727CC05F-A99D-474F-9C66-A9E57D6B25CC} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {EBFDDFDE-BF5D-4607-AADA-7039445E2AB7} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {C51FBF31-BFEE-460B-B182-49CD65F8F8EA} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {0E5746A9-C13B-4BB9-BF3B-C524F4E5BC3A} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {25F1BF2F-1B6A-466A-A3BF-8963B0D040F4} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {5802F580-3E70-45F4-AF60-4F4E5F8FAADA} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {1E9A51CC-1E5E-4BB1-9E47-58D6C2DB8450} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {98489BC4-B03B-4703-B25D-A89D617E761F} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {B1B7522F-6EF2-4935-BE9F-66B6DB974303} = {3CBE0336-C3C8-4DC2-AE45-86FC1D1B3C25} - {34891C2A-FAF2-466A-90A6-F25DBD8283E8} = {3CBE0336-C3C8-4DC2-AE45-86FC1D1B3C25} - {72C7AD35-98E4-478E-B594-06A3DB2E61D6} = {3CBE0336-C3C8-4DC2-AE45-86FC1D1B3C25} - {C3098547-C3B5-4136-9CEB-30466EE1FA53} = {31BAEBB1-696E-44A1-B1EF-0D150E6D2559} - {E6DBF77D-04A3-4422-A7D2-D3131DF5B829} = {31BAEBB1-696E-44A1-B1EF-0D150E6D2559} - {B5D0C674-1958-43BF-B644-0865E9DDCCBD} = {31BAEBB1-696E-44A1-B1EF-0D150E6D2559} - {23B95817-4183-4B42-BF27-EA575184DB2D} = {31BAEBB1-696E-44A1-B1EF-0D150E6D2559} - {43DC1B99-588C-4984-AEAC-43AA5B47D84C} = {DDB99068-891F-4937-98B0-E72CC3F4964B} - {04BC571A-0055-4B9B-805E-D2B0823C9D8D} = {DDB99068-891F-4937-98B0-E72CC3F4964B} - {990DCEE9-D88F-4B9F-8298-678B524AC4D6} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} - {557AFE26-B89D-497D-92B6-8268D04396E5} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} - {4EFBD262-86B2-4E21-8608-B1894E04EA99} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} - {C2EFCD80-C96D-4A09-BFD6-02CB4603961C} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} - {C437628B-43EE-4AAD-83ED-DDE7499E705B} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} - {A4D6D08A-BC0C-4CAD-97E6-F4BAAA03928C} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} - {31E52250-3422-49E9-9605-EBE786CC1BE3} = {5128206A-242E-4069-AD30-910EDC40B165} - {6D09B0D3-AF28-4FC4-A67D-424E7746682B} = {5128206A-242E-4069-AD30-910EDC40B165} - {65CF716D-9215-475C-B37F-CA943F5CD6A3} = {59874241-E276-4035-B31D-14924889A1C9} - {BEB156E9-4E06-403A-B778-E7B092B79962} = {59874241-E276-4035-B31D-14924889A1C9} - {BA80CEAD-02A2-480D-93F4-907B01BDB219} = {59874241-E276-4035-B31D-14924889A1C9} - {F9095412-B7FB-4A8A-A291-141458A988AF} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} - {920BDA8F-094B-4D81-A03C-664C0F808DC7} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {86FBBCC7-97F5-4795-A61E-FA7E0CE0514D} = {DDB99068-891F-4937-98B0-E72CC3F4964B} - {C3232459-EE86-430B-B310-EB32C991A11A} = {3CBE0336-C3C8-4DC2-AE45-86FC1D1B3C25} - {E0B1415D-8D97-4BA5-9315-82E7D33E3933} = {DDB99068-891F-4937-98B0-E72CC3F4964B} - {6C116C3E-177A-44C0-A149-8DDB62812DC3} = {DDB99068-891F-4937-98B0-E72CC3F4964B} - {E2BE4A3F-1C35-41ED-99D1-7630B5369942} = {31BAEBB1-696E-44A1-B1EF-0D150E6D2559} - {C5DDEE8C-E3EF-49BC-AEEE-B35271FC512A} = {31BAEBB1-696E-44A1-B1EF-0D150E6D2559} - {D94706F7-7622-4D4C-87BD-84FB40D4E6BD} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {BA38127A-DA5A-437F-9C84-5997FBE0B6A0} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {38EFC635-0C56-442D-8CA3-20AEC1930D7B} = {EA9C3A73-3F31-4DC9-982C-963CE613E119} - {6499285A-88CF-426A-909D-307382B91AA3} = {EA9C3A73-3F31-4DC9-982C-963CE613E119} - {6A064B5A-21F7-452A-AAFF-3C0A68286FDF} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {0D68FC03-7FD4-4280-852B-0E620872CE22} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {BCC3F7DC-080D-4A7E-8060-40ED65073EB3} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {764B3355-FD20-42E3-B452-357FCC20A27D} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {7603B7DF-0297-4CF7-BB31-D5B8BABF7D5B} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} - {C821DCC5-880F-4F85-97B1-6D9A56DAD6F0} = {EA9C3A73-3F31-4DC9-982C-963CE613E119} - {7866263B-936E-4604-A821-73C46BAB1657} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} - {EA3AFC13-2399-411E-AD20-C6677505615A} = {31BAEBB1-696E-44A1-B1EF-0D150E6D2559} - {5031A608-281E-4FAF-9501-1DBDAB645907} = {31BAEBB1-696E-44A1-B1EF-0D150E6D2559} - {10EC7705-BE9E-4E2A-B174-E74D1CC9852F} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} - {AF26B4AC-35B5-4954-ADE8-97BA2DC31250} = {5128206A-242E-4069-AD30-910EDC40B165} - {5EBE664D-D071-43A9-9B53-346BE3DCCB41} = {5128206A-242E-4069-AD30-910EDC40B165} - {196A7EF1-56CE-481E-BB49-97BD0FF096BE} = {5128206A-242E-4069-AD30-910EDC40B165} - {B0AC88EC-EB6C-4E73-B9D5-51DD213931B4} = {5128206A-242E-4069-AD30-910EDC40B165} - {AAA51F15-E5BE-4E77-92DF-1992434D557A} = {5128206A-242E-4069-AD30-910EDC40B165} - {9E83FDF7-D838-4E7E-A767-6FEAC6BC1C5D} = {5128206A-242E-4069-AD30-910EDC40B165} - {2A975FB7-401B-41BB-96A4-1DF0036888A9} = {DF26D677-72A3-442D-B556-46E3D0EF4A77} - {C4C38F83-8410-443C-9599-ACFB5FA7CD2D} = {DF26D677-72A3-442D-B556-46E3D0EF4A77} - {738AFF97-B4C4-4EAC-B9C5-C405D481C92B} = {59874241-E276-4035-B31D-14924889A1C9} - {A9CCC214-212A-4296-98F5-65ADDB2BB8B4} = {59874241-E276-4035-B31D-14924889A1C9} - {51DD3135-1EAA-4640-82F1-9FBECA421708} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {AB0245B9-2464-47F8-BE15-D80A7A2FA965} - EndGlobalSection -EndGlobal diff --git a/src/Steeltoe.All.slnx b/src/Steeltoe.All.slnx new file mode 100644 index 0000000000..dbe0f1cafb --- /dev/null +++ b/src/Steeltoe.All.slnx @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Steeltoe.All.sln.DotSettings b/src/Steeltoe.All.slnx.DotSettings similarity index 96% rename from src/Steeltoe.All.sln.DotSettings rename to src/Steeltoe.All.slnx.DotSettings index aceff66495..405dcb143e 100644 --- a/src/Steeltoe.All.sln.DotSettings +++ b/src/Steeltoe.All.slnx.DotSettings @@ -3,17 +3,14 @@ 2000 3000 False - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - 2A975FB7-401B-41BB-96A4-1DF0036888A9 - 61812938-5132-4AB6-B48D-2DF4189B3E37/f:ConfigurationSchemaAttributes.cs - C4C38F83-8410-443C-9599-ACFB5FA7CD2D - C4C38F83-8410-443C-9599-ACFB5FA7CD2D/f:ConfigurationSchemaAttributes.cs/l:..?..?..?Common?src?Abstractions?ConfigurationSchemaAttributes.cs - DC1BC61A-E0FA-4CF9-9F24-D4C564A07836/f:Directory.Build.targets/l:..?Directory.Build.targets + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + 2C62D385-0462-A9A1-B49F-11B2CA4C133B/f:Directory.Build.targets/l:..?Directory.Build.targets + 790DD63C-8905-7556-AEBC-3CA429B0A44A + 790DD63C-8905-7556-AEBC-3CA429B0A44A/f:ConfigurationSchemaAttributes.cs/l:..?..?..?Common?src?Common?ConfigurationSchemaAttributes.cs + 9D8CC586-97FA-57A6-1702-125D9D03645D/f:ConfigurationSchemaAttributes.cs + CFB09DDD-8A94-3860-BFC2-111EC05320C3 SOLUTION True SUGGESTION diff --git a/src/Steeltoe.Common.slnf b/src/Steeltoe.Common.slnf index 66c2b96d6b..99f121b7cc 100644 --- a/src/Steeltoe.Common.slnf +++ b/src/Steeltoe.Common.slnf @@ -1,6 +1,6 @@ { "solution": { - "path": "Steeltoe.All.sln", + "path": "Steeltoe.All.slnx", "projects": [ "Common\\src\\Certificates\\Steeltoe.Common.Certificates.csproj", "Common\\src\\Common\\Steeltoe.Common.csproj", diff --git a/src/Steeltoe.Configuration.slnf b/src/Steeltoe.Configuration.slnf index 0eb8a847ae..e471516cf7 100644 --- a/src/Steeltoe.Configuration.slnf +++ b/src/Steeltoe.Configuration.slnf @@ -1,6 +1,6 @@ { "solution": { - "path": "Steeltoe.All.sln", + "path": "Steeltoe.All.slnx", "projects": [ "Common\\src\\Certificates\\Steeltoe.Common.Certificates.csproj", "Common\\src\\Common\\Steeltoe.Common.csproj", diff --git a/src/Steeltoe.Connectors.slnf b/src/Steeltoe.Connectors.slnf index 68b3607897..c631a0bea4 100644 --- a/src/Steeltoe.Connectors.slnf +++ b/src/Steeltoe.Connectors.slnf @@ -1,6 +1,6 @@ { "solution": { - "path": "Steeltoe.All.sln", + "path": "Steeltoe.All.slnx", "projects": [ "Common\\src\\Common\\Steeltoe.Common.csproj", "Common\\src\\Hosting\\Steeltoe.Common.Hosting.csproj", diff --git a/src/Steeltoe.Discovery.slnf b/src/Steeltoe.Discovery.slnf index 4b0800bfa3..206ca6c486 100644 --- a/src/Steeltoe.Discovery.slnf +++ b/src/Steeltoe.Discovery.slnf @@ -1,6 +1,6 @@ { "solution": { - "path": "Steeltoe.All.sln", + "path": "Steeltoe.All.slnx", "projects": [ "Common\\src\\Certificates\\Steeltoe.Common.Certificates.csproj", "Common\\src\\Common\\Steeltoe.Common.csproj", diff --git a/src/Steeltoe.Logging.slnf b/src/Steeltoe.Logging.slnf index ddb9815af3..9b8554a5d1 100644 --- a/src/Steeltoe.Logging.slnf +++ b/src/Steeltoe.Logging.slnf @@ -1,6 +1,6 @@ { "solution": { - "path": "Steeltoe.All.sln", + "path": "Steeltoe.All.slnx", "projects": [ "Common\\src\\Common\\Steeltoe.Common.csproj", "Common\\src\\Hosting\\Steeltoe.Common.Hosting.csproj", diff --git a/src/Steeltoe.Management.slnf b/src/Steeltoe.Management.slnf index ef0472875f..121eea921c 100644 --- a/src/Steeltoe.Management.slnf +++ b/src/Steeltoe.Management.slnf @@ -1,6 +1,6 @@ { "solution": { - "path": "Steeltoe.All.sln", + "path": "Steeltoe.All.slnx", "projects": [ "Common\\src\\Certificates\\Steeltoe.Common.Certificates.csproj", "Common\\src\\Common\\Steeltoe.Common.csproj", diff --git a/src/Steeltoe.Security.slnf b/src/Steeltoe.Security.slnf index 072a044572..7efff59059 100644 --- a/src/Steeltoe.Security.slnf +++ b/src/Steeltoe.Security.slnf @@ -1,6 +1,6 @@ { "solution": { - "path": "Steeltoe.All.sln", + "path": "Steeltoe.All.slnx", "projects": [ "Common\\src\\Certificates\\Steeltoe.Common.Certificates.csproj", "Common\\src\\Common\\Steeltoe.Common.csproj", From e061cc0bd3066e75267bca5734c4d88769d7e26b Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Fri, 24 Apr 2026 12:20:47 +0200 Subject: [PATCH 52/81] Revert "R#: Move inline suppressions for AccessToDisposedClosure in tests to .editorconfig (#1682)" Reverting because this breaks the R# formatting engine. By reading R# settings from .editorconfig (which takes precedence over .DotSettings), many R# rule severities are lowered or turned off entirely. The lowered severities in .editorconfig exist as a fallback for other IDEs only. The removal of the `public` modifier on IDiscoveryClient.InstancesFetched` is not reverted by this commit. --- .editorconfig | 4 ---- src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs | 4 ++++ src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs | 1 + .../CloudfoundryConfigurationProviderTest.cs | 1 + .../ConfigServerClientOptionsTest.cs | 2 ++ .../test/ConfigServer.Test/ConfigServerClientOptionsTest.cs | 2 ++ .../ConfigServerConfigurationProviderTest.Settings.cs | 4 ++-- .../ConfigServerConfigurationProviderTest.cs | 3 +++ .../Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs | 2 ++ .../Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs | 1 + .../Connectors.Test/RabbitMQ/RabbitMQHealthContributorTest.cs | 2 ++ .../test/Connectors.Test/Redis/RedisHealthContributorTest.cs | 2 ++ .../RelationalDatabaseHealthContributorTest.cs | 2 ++ .../Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs | 1 + src/Discovery/test/Consul.Test/Discovery/TtlSchedulerTest.cs | 2 ++ .../test/Consul.Test/Registry/ConsulServiceRegistryTest.cs | 1 + src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs | 2 ++ .../DiscoveryHostApplicationBuilderExtensionsTest.cs | 1 + .../DiscoveryWebApplicationBuilderExtensionsTest.cs | 1 + .../LoadBalancers/DiscoveryHttpClientHandlerTest.cs | 2 ++ .../LoadBalancers/DiscoveryHttpDelegatingHandlerTest.cs | 2 ++ .../test/Endpoint.Test/Actuators/All/AllActuatorsTest.cs | 3 +++ .../Actuators/CloudFoundry/CloudFoundryActuatorTest.cs | 2 ++ .../CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs | 2 ++ .../Actuators/DbMigrations/DbMigrationsActuatorTest.cs | 1 + .../Actuators/Environment/EnvironmentActuatorTest.cs | 1 + .../test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs | 1 + .../Endpoint.Test/Actuators/Health/HealthAggregationTest.cs | 3 +++ .../Endpoint.Test/Actuators/HeapDump/HeapDumpActuatorTest.cs | 1 + .../Actuators/HttpExchanges/HttpExchangesActuatorTest.cs | 1 + .../Actuators/Hypermedia/HypermediaActuatorTest.cs | 1 + .../test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs | 1 + .../Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs | 1 + .../Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs | 1 + .../Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs | 1 + .../Actuators/RouteMappings/RouteMappingsActuatorTest.cs | 1 + .../Endpoint.Test/Actuators/Services/ServicesActuatorTest.cs | 1 + .../Actuators/ThreadDump/ThreadDumpActuatorTest.cs | 1 + .../Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs | 1 + src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs | 1 + src/Steeltoe.All.slnx.DotSettings | 1 + 41 files changed, 62 insertions(+), 6 deletions(-) diff --git a/.editorconfig b/.editorconfig index ae71398246..adf8ab64ba 100644 --- a/.editorconfig +++ b/.editorconfig @@ -162,7 +162,3 @@ dotnet_diagnostic.JSON002.severity = silent # CA1848: Use the LoggerMessage delegates (depends on https://github.com/SteeltoeOSS/Steeltoe/issues/969) dotnet_diagnostic.CA1848.severity = silent - -[*Test.cs] -resharper_access_to_disposed_closure_highlighting = none -resharper_access_to_modified_closure_highlighting = none diff --git a/src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs b/src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs index 1e5b7ea4d1..65e138ca66 100644 --- a/src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs +++ b/src/Common/test/Hosting.Test/HostBuilderWrapperTest.cs @@ -29,6 +29,7 @@ public async Task WebApplicationBuilder_Wraps() HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); wrapper.ConfigureServices(services => services.AddSingleton()); wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); + // ReSharper disable once AccessToDisposedClosure wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddProvider(capturingLoggerProvider)); wrapper.ConfigureWebHost(hostBuilder => hostBuilder.UseUrls("http://*:8888")); wrapper.ConfigureServices((contextWrapper, _) => contextWrapper.HostEnvironment.ApplicationName = "TestApp"); @@ -64,6 +65,7 @@ public void HostApplicationBuilder_Wraps() HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); wrapper.ConfigureServices(services => services.AddSingleton()); wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); + // ReSharper disable once AccessToDisposedClosure wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddProvider(capturingLoggerProvider)); wrapper.ConfigureServices((contextWrapper, _) => contextWrapper.HostEnvironment.ApplicationName = "TestApp"); @@ -98,6 +100,7 @@ public void WebHostBuilder_Wraps() HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); wrapper.ConfigureServices(services => services.AddSingleton()); wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); + // ReSharper disable once AccessToDisposedClosure wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddProvider(capturingLoggerProvider)); wrapper.ConfigureWebHost(hostBuilder => hostBuilder.UseUrls("http://*:8888")); wrapper.ConfigureServices((contextWrapper, _) => contextWrapper.HostEnvironment.ApplicationName = "TestApp"); @@ -135,6 +138,7 @@ public void GenericHostBuilder_Wraps() HostBuilderWrapper wrapper = HostBuilderWrapper.Wrap(builder); wrapper.ConfigureServices(services => services.AddSingleton()); wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddInMemoryCollection(appSettings)); + // ReSharper disable once AccessToDisposedClosure wrapper.ConfigureLogging(loggingBuilder => loggingBuilder.AddProvider(capturingLoggerProvider)); wrapper.ConfigureWebHost(hostBuilder => hostBuilder.UseUrls("http://*:8888")); wrapper.ConfigureServices((contextWrapper, _) => contextWrapper.HostEnvironment.ApplicationName = "TestApp"); diff --git a/src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs b/src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs index 434e7feaeb..79ea85acc6 100644 --- a/src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs +++ b/src/Common/test/Logging.Test/BootstrapperLoggerFactoryTest.cs @@ -20,6 +20,7 @@ public async Task Upgrades_existing_loggers() var bootstrapLoggerFactory = BootstrapLoggerFactory.CreateEmpty(loggingBuilder => { loggingBuilder.SetMinimumLevel(LogLevel.Trace); + // ReSharper disable once AccessToDisposedClosure loggingBuilder.AddProvider(capturingLoggerProvider); }); diff --git a/src/Configuration/test/CloudFoundry.Test/CloudfoundryConfigurationProviderTest.cs b/src/Configuration/test/CloudFoundry.Test/CloudfoundryConfigurationProviderTest.cs index 5566efc479..d461d659a7 100644 --- a/src/Configuration/test/CloudFoundry.Test/CloudfoundryConfigurationProviderTest.cs +++ b/src/Configuration/test/CloudFoundry.Test/CloudfoundryConfigurationProviderTest.cs @@ -216,6 +216,7 @@ public void Load_VCAP_APPLICATION_Allows_Reload_Without_Throwing_Exception() _ = Task.Run(() => { + // ReSharper disable once AccessToDisposedClosure while (!tokenSource.IsCancellationRequested) { configurationRoot.Reload(); diff --git a/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerClientOptionsTest.cs b/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerClientOptionsTest.cs index 61c57e5a36..5ca2eab3c0 100644 --- a/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerClientOptionsTest.cs +++ b/src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerClientOptionsTest.cs @@ -90,6 +90,7 @@ public void Config_Server_URI_is_resolved_from_discovery_and_survives_changes_in var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + // ReSharper disable once AccessToDisposedClosure configurationBuilder.AddConfigServer(new ConfigServerClientOptions(), configureOptions, () => handler, NullLoggerFactory.Instance); IConfigurationRoot configuration = configurationBuilder.Build(); @@ -305,6 +306,7 @@ public void Updates_discovered_Config_Server_URI_on_provider_reload() var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + // ReSharper disable once AccessToDisposedClosure configurationBuilder.AddConfigServer(new ConfigServerClientOptions(), configureOptions, () => handler, NullLoggerFactory.Instance); IConfigurationRoot configuration = configurationBuilder.Build(); diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerClientOptionsTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerClientOptionsTest.cs index 27d0f98929..2c648dc0b3 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerClientOptionsTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerClientOptionsTest.cs @@ -265,6 +265,7 @@ public void Certificate_configuration_survives_options_reload() var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); + // ReSharper disable once AccessToDisposedClosure configurationBuilder.AddConfigServer(new ConfigServerClientOptions(), null, () => handler, NullLoggerFactory.Instance); IConfigurationRoot configuration = configurationBuilder.Build(); @@ -367,6 +368,7 @@ public void Changes_in_IConfiguration_update_provider_options_and_injected_optio var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddInMemoryAppSettingsJsonFile(fileProvider); configurationBuilder.AddPlaceholderResolver(); + // ReSharper disable once AccessToDisposedClosure configurationBuilder.AddConfigServer(defaultOptions, configureOptions, () => handler, NullLoggerFactory.Instance); IConfigurationRoot configuration = configurationBuilder.Build(); diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Settings.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Settings.cs index d043acfc9c..1cd6f664f6 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Settings.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.Settings.cs @@ -7,8 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; -// ReSharper disable AccessToDisposedClosure - namespace Steeltoe.Configuration.ConfigServer.Test; public sealed partial class ConfigServerConfigurationProviderTest @@ -67,7 +65,9 @@ public void GetConfigServerUri_NoBaseUri_Throws() using var provider = new ConfigServerConfigurationProvider(options, null, null, null, NullLoggerFactory.Instance); + // ReSharper disable AccessToDisposedClosure Action action = () => provider.BuildConfigServerUri(provider.ClientOptions, null!, null); + // ReSharper restore AccessToDisposedClosure action.Should().ThrowExactly(); } diff --git a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs index 7b588ee87a..7b8c950a39 100644 --- a/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs +++ b/src/Configuration/test/ConfigServer.Test/ConfigServerConfigurationProviderTest.cs @@ -264,6 +264,7 @@ public async Task GetRequestMessage_AddsBearerToken_WhenAccessTokenUriIsSet() } """); + // ReSharper disable once AccessToDisposedClosure using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); Uri requestUri = provider.BuildConfigServerUri(provider.ClientOptions, new Uri(options.Uri), null); @@ -293,6 +294,7 @@ public async Task RefreshVaultToken_Succeeds() handler.Mock.Expect(HttpMethod.Post, "http://localhost:8888/vault/v1/auth/token/renew-self").WithHeaders("X-Vault-Token", "MyVaultToken") .WithContent("{\"increment\":300}").Respond(HttpStatusCode.NoContent); + // ReSharper disable once AccessToDisposedClosure using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); provider.Load(); @@ -322,6 +324,7 @@ public async Task RefreshVaultToken_With_AccessTokenUri_Succeeds() handler.Mock.Expect(HttpMethod.Post, "http://localhost:8888/vault/v1/auth/token/renew-self").WithHeaders("X-Vault-Token", "MyVaultToken") .WithHeaders("Authorization", "Bearer secret").WithContent("{\"increment\":300}").Respond(HttpStatusCode.NoContent); + // ReSharper disable once AccessToDisposedClosure using var provider = new ConfigServerConfigurationProvider(options, null, null, () => handler, NullLoggerFactory.Instance); await provider.RefreshVaultTokenAsync(provider.ClientOptions, TestContext.Current.CancellationToken); diff --git a/src/Connectors/test/Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs index 990db81109..303f5c0c21 100644 --- a/src/Connectors/test/Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/CosmosDb/CosmosDbHealthContributorTest.cs @@ -116,7 +116,9 @@ public async Task Canceled_Throws() using var source = new CancellationTokenSource(); await source.CancelAsync(); + // ReSharper disable AccessToDisposedClosure Func action = async () => await healthContributor.CheckHealthAsync(source.Token); + // ReSharper restore AccessToDisposedClosure await action.Should().ThrowExactlyAsync(); } diff --git a/src/Connectors/test/Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs index d57155154b..abf1a00a0a 100644 --- a/src/Connectors/test/Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/MongoDb/MongoDbHealthContributorTest.cs @@ -86,6 +86,7 @@ public async Task Canceled_Throws() using var source = new CancellationTokenSource(); await source.CancelAsync(); + // ReSharper disable once AccessToDisposedClosure Func action = async () => await healthContributor.CheckHealthAsync(source.Token); await action.Should().ThrowExactlyAsync(); diff --git a/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQHealthContributorTest.cs index 7f3290395e..7d8ee14216 100644 --- a/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQHealthContributorTest.cs @@ -109,7 +109,9 @@ public async Task Canceled_Throws() using var source = new CancellationTokenSource(); await source.CancelAsync(); + // ReSharper disable AccessToDisposedClosure Func action = async () => await healthContributor.CheckHealthAsync(source.Token); + // ReSharper restore AccessToDisposedClosure await action.Should().ThrowExactlyAsync(); } diff --git a/src/Connectors/test/Connectors.Test/Redis/RedisHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/Redis/RedisHealthContributorTest.cs index aa0bcf3988..f2dc8540d4 100644 --- a/src/Connectors/test/Connectors.Test/Redis/RedisHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/Redis/RedisHealthContributorTest.cs @@ -74,7 +74,9 @@ public async Task Canceled_Throws() using var source = new CancellationTokenSource(); await source.CancelAsync(); + // ReSharper disable AccessToDisposedClosure Func action = async () => await healthContributor.CheckHealthAsync(source.Token); + // ReSharper restore AccessToDisposedClosure await action.Should().ThrowExactlyAsync(); } diff --git a/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs index de229c4d91..267d293dac 100644 --- a/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs @@ -175,7 +175,9 @@ public async Task Canceled_Throws() using var source = new CancellationTokenSource(); await source.CancelAsync(); + // ReSharper disable AccessToDisposedClosure Func action = async () => await healthContributor.CheckHealthAsync(source.Token); + // ReSharper restore AccessToDisposedClosure await action.Should().ThrowExactlyAsync(); } diff --git a/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs b/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs index 320feb7679..17a40ea71e 100644 --- a/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs +++ b/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs @@ -88,6 +88,7 @@ public async Task Throws_for_missing_connection_string_with_version_detection() await using WebApplication app = builder.Build(); await using AsyncServiceScope scope = app.Services.CreateAsyncScope(); + // ReSharper disable once AccessToDisposedClosure Action action = () => _ = scope.ServiceProvider.GetRequiredService(); action.Should().ThrowExactly().WithMessage("Server version must be specified when no connection string is provided."); diff --git a/src/Discovery/test/Consul.Test/Discovery/TtlSchedulerTest.cs b/src/Discovery/test/Consul.Test/Discovery/TtlSchedulerTest.cs index 7736b6bb7d..4799f45024 100644 --- a/src/Discovery/test/Consul.Test/Discovery/TtlSchedulerTest.cs +++ b/src/Discovery/test/Consul.Test/Discovery/TtlSchedulerTest.cs @@ -20,6 +20,7 @@ public async Task Add_Throws_Invalid_InstanceId() var optionsMonitor = new TestOptionsMonitor(); await using var scheduler = new TtlScheduler(optionsMonitor, clientMoq.Object, NullLoggerFactory.Instance); + // ReSharper disable once AccessToDisposedClosure Action action = () => scheduler.Add(string.Empty); action.Should().ThrowExactly(); @@ -96,6 +97,7 @@ public async Task Remove_Throws_Invalid_InstanceId() var optionsMonitor = new TestOptionsMonitor(); await using var scheduler = new TtlScheduler(optionsMonitor, clientMoq.Object, NullLoggerFactory.Instance); + // ReSharper disable once AccessToDisposedClosure Func action = async () => await scheduler.RemoveAsync(string.Empty); await action.Should().ThrowExactlyAsync(); diff --git a/src/Discovery/test/Consul.Test/Registry/ConsulServiceRegistryTest.cs b/src/Discovery/test/Consul.Test/Registry/ConsulServiceRegistryTest.cs index 0a9c15d66f..a654543c51 100644 --- a/src/Discovery/test/Consul.Test/Registry/ConsulServiceRegistryTest.cs +++ b/src/Discovery/test/Consul.Test/Registry/ConsulServiceRegistryTest.cs @@ -87,6 +87,7 @@ public async Task SetStatusAsync_ThrowsInvalidStatus() await using var registry = new ConsulServiceRegistry(clientMoq.Object, optionsMonitor, scheduler, NullLogger.Instance); + // ReSharper disable once AccessToDisposedClosure Func action = async () => await registry.SetStatusAsync(registration, string.Empty, TestContext.Current.CancellationToken); await action.Should().ThrowExactlyAsync(); diff --git a/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs b/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs index af880a4275..0b6ece9106 100644 --- a/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs +++ b/src/Discovery/test/Eureka.Test/Transport/EurekaClientTest.cs @@ -13,6 +13,8 @@ using Steeltoe.Discovery.Eureka.Configuration; using Steeltoe.Discovery.Eureka.Transport; +// ReSharper disable AccessToDisposedClosure + namespace Steeltoe.Discovery.Eureka.Test.Transport; public sealed class EurekaClientTest diff --git a/src/Discovery/test/HttpClients.Test/DiscoveryHostApplicationBuilderExtensionsTest.cs b/src/Discovery/test/HttpClients.Test/DiscoveryHostApplicationBuilderExtensionsTest.cs index f16be05f32..48435d1d15 100644 --- a/src/Discovery/test/HttpClients.Test/DiscoveryHostApplicationBuilderExtensionsTest.cs +++ b/src/Discovery/test/HttpClients.Test/DiscoveryHostApplicationBuilderExtensionsTest.cs @@ -51,6 +51,7 @@ public async Task AddEurekaDiscoveryClient_HostApplicationBuilder_StartsUp() using IHost host = hostBuilder.Build(); + // ReSharper disable once AccessToDisposedClosure Func action = async () => await host.StartAsync(TestContext.Current.CancellationToken); await action.Should().NotThrowAsync(); diff --git a/src/Discovery/test/HttpClients.Test/DiscoveryWebApplicationBuilderExtensionsTest.cs b/src/Discovery/test/HttpClients.Test/DiscoveryWebApplicationBuilderExtensionsTest.cs index 1d347f30bc..358958b738 100644 --- a/src/Discovery/test/HttpClients.Test/DiscoveryWebApplicationBuilderExtensionsTest.cs +++ b/src/Discovery/test/HttpClients.Test/DiscoveryWebApplicationBuilderExtensionsTest.cs @@ -73,6 +73,7 @@ public async Task AddEurekaDiscoveryClient_WorksWithGlobalServiceDiscovery() await using WebApplication host = builder.Build(); + // ReSharper disable once AccessToDisposedClosure Task resolveTask = Task.Run(() => _ = host.Services.GetServices().OfType().Single()); Func action = async () => await resolveTask.WaitAsync(5.Seconds(), TestContext.Current.CancellationToken); diff --git a/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpClientHandlerTest.cs b/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpClientHandlerTest.cs index 00343415b7..37589f843d 100644 --- a/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpClientHandlerTest.cs +++ b/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpClientHandlerTest.cs @@ -17,6 +17,7 @@ public async Task DoesNotTrackStatistics_WhenResolutionFails_WithProvidedLoadBal var handler = new DiscoveryHttpClientHandler(loadBalancer, TimeProvider.System); using var invoker = new HttpMessageInvoker(handler); + // ReSharper disable once AccessToDisposedClosure Func action = async () => _ = await invoker.SendAsync(httpRequestMessage, TestContext.Current.CancellationToken); await action.Should().ThrowExactlyAsync(); @@ -32,6 +33,7 @@ public async Task TracksStatistics_WhenRequestsGoWrong_WithProvidedLoadBalancer( var handler = new DiscoveryHttpClientHandler(loadBalancer, TimeProvider.System); using var invoker = new HttpMessageInvoker(handler); + // ReSharper disable once AccessToDisposedClosure Func action = async () => _ = await invoker.SendAsync(httpRequestMessage, TestContext.Current.CancellationToken); await action.Should().ThrowExactlyAsync(); diff --git a/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpDelegatingHandlerTest.cs b/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpDelegatingHandlerTest.cs index a7cd3eb814..19d55c7db3 100644 --- a/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpDelegatingHandlerTest.cs +++ b/src/Discovery/test/HttpClients.Test/LoadBalancers/DiscoveryHttpDelegatingHandlerTest.cs @@ -55,6 +55,7 @@ public async Task DoesNotTrackStatistics_WhenRequestIsCanceled() using var invoker = new HttpMessageInvoker(handler); + // ReSharper disable once AccessToDisposedClosure Func action = async () => await invoker.SendAsync(httpRequestMessage, TestContext.Current.CancellationToken); await action.Should().ThrowExactlyAsync(); @@ -79,6 +80,7 @@ public async Task DoesNotTrackStatistics_WhenResolutionFails_WithProvidedLoadBal using var invoker = new HttpMessageInvoker(handler); + // ReSharper disable once AccessToDisposedClosure Func action = async () => await invoker.SendAsync(httpRequestMessage, TestContext.Current.CancellationToken); await action.Should().ThrowExactlyAsync(); diff --git a/src/Management/test/Endpoint.Test/Actuators/All/AllActuatorsTest.cs b/src/Management/test/Endpoint.Test/Actuators/All/AllActuatorsTest.cs index c28a325a4d..1efdbebb63 100644 --- a/src/Management/test/Endpoint.Test/Actuators/All/AllActuatorsTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/All/AllActuatorsTest.cs @@ -62,6 +62,7 @@ public async Task Registers_dependent_services(bool platformIsCloudFoundry) foreach (Type middlewareType in middlewareTypes) { + // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(middlewareType); action.Should().NotThrow(); @@ -71,8 +72,10 @@ public async Task Registers_dependent_services(bool platformIsCloudFoundry) { Action action = () => { + // ReSharper disable AccessToDisposedClosure _ = serviceProvider.GetRequiredService(); _ = serviceProvider.GetRequiredService(); + // ReSharper restore AccessToDisposedClosure }; action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs index 4e0b82d74d..6a4e565aea 100644 --- a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundryActuatorTest.cs @@ -43,9 +43,11 @@ public async Task Registers_dependent_services() services.AddCloudFoundryActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + // ReSharper disable once AccessToDisposedClosure Action middlewareAction = () => serviceProvider.GetRequiredService(); middlewareAction.Should().NotThrow(); + // ReSharper disable once AccessToDisposedClosure Action providerAction = () => serviceProvider.GetRequiredService(); providerAction.Should().NotThrow(); } diff --git a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs index 44bf05c385..1197f57d97 100644 --- a/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/CloudFoundry/CloudFoundrySecurityMiddlewareTest.cs @@ -316,6 +316,7 @@ public async Task ThrowsWhenAddMethodNotCalled() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); await using WebApplication app = builder.Build(); + // ReSharper disable once AccessToDisposedClosure Action action = () => app.UseCloudFoundrySecurity(); action.Should().ThrowExactly().WithMessage("Please call IServiceCollection.AddCloudFoundryActuator first."); @@ -336,6 +337,7 @@ public async Task RedactsHttpHeaders() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); builder.Configuration.AddCloudFoundry(); + // ReSharper disable once AccessToDisposedClosure builder.Services.AddLogging(options => options.SetMinimumLevel(LogLevel.Trace).AddProvider(capturingLoggerProvider)); builder.Services.AddCloudFoundryActuator(); await using WebApplication app = builder.Build(); diff --git a/src/Management/test/Endpoint.Test/Actuators/DbMigrations/DbMigrationsActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/DbMigrations/DbMigrationsActuatorTest.cs index 4413007092..491af47e67 100644 --- a/src/Management/test/Endpoint.Test/Actuators/DbMigrations/DbMigrationsActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/DbMigrations/DbMigrationsActuatorTest.cs @@ -30,6 +30,7 @@ public async Task Registers_dependent_services() services.AddDbMigrationsActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Environment/EnvironmentActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Environment/EnvironmentActuatorTest.cs index 4afda96b82..897202ec36 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Environment/EnvironmentActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Environment/EnvironmentActuatorTest.cs @@ -32,6 +32,7 @@ public async Task Registers_dependent_services() services.AddEnvironmentActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs index cbd260f5fc..4a5e408466 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Health/HealthActuatorTest.cs @@ -40,6 +40,7 @@ public async Task Registers_dependent_services() services.AddHealthActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs b/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs index 68f77a7bb3..ad76b9eaad 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs @@ -450,7 +450,9 @@ public async Task Propagates_cancellation() using var source = new CancellationTokenSource(); source.CancelAfter(1.Seconds()); + // ReSharper disable AccessToDisposedClosure Func action = async () => await aggregator.AggregateAsync(contributors, [], emptyServiceProvider, source.Token); + // ReSharper restore AccessToDisposedClosure await action.Should().ThrowExactlyAsync(); } @@ -572,6 +574,7 @@ public async Task Can_use_scoped_AspNet_health_check() builder.Services.AddHealthActuator(); await using WebApplication host = builder.Build(); + // ReSharper disable once AccessToDisposedClosure Action action = () => host.Services.GetRequiredService(); action.Should().ThrowExactly(); diff --git a/src/Management/test/Endpoint.Test/Actuators/HeapDump/HeapDumpActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/HeapDump/HeapDumpActuatorTest.cs index a8080ab2e1..da6de1a61d 100644 --- a/src/Management/test/Endpoint.Test/Actuators/HeapDump/HeapDumpActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/HeapDump/HeapDumpActuatorTest.cs @@ -29,6 +29,7 @@ public async Task Registers_dependent_services() services.AddHeapDumpActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/HttpExchangesActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/HttpExchangesActuatorTest.cs index 3b2ca41c24..476a670815 100644 --- a/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/HttpExchangesActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/HttpExchanges/HttpExchangesActuatorTest.cs @@ -33,6 +33,7 @@ public async Task Registers_dependent_services() services.AddHttpExchangesActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs index 243f494b93..7325d9b4b1 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Hypermedia/HypermediaActuatorTest.cs @@ -30,6 +30,7 @@ public async Task Registers_dependent_services() services.AddHypermediaActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs index 5eb8ce37eb..7d7756a273 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Info/InfoActuatorTest.cs @@ -50,6 +50,7 @@ public async Task Registers_dependent_services() services.AddInfoActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs index be018bc879..0e28b20870 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Loggers/LoggersActuatorTest.cs @@ -33,6 +33,7 @@ public async Task Registers_dependent_services() services.AddLoggersActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs b/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs index fc3370d09b..bb571357e7 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Refresh/HttpVerbInConventionalRoutingTest.cs @@ -255,6 +255,7 @@ public async Task ThrowsForCallback() builder.Services.AddMvc(options => options.EnableEndpointRouting = false); await using WebApplication host = builder.Build(); + // ReSharper disable once AccessToDisposedClosure Action action = () => host.UseActuatorEndpoints(configureEndpointsCallback); action.Should().ThrowExactly().WithMessage("Customizing endpoints is only supported when using endpoint routing."); diff --git a/src/Management/test/Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs index 377765c18c..7ede0f15bd 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Refresh/RefreshActuatorTest.cs @@ -31,6 +31,7 @@ public async Task Registers_dependent_services() services.AddRefreshActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RouteMappingsActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RouteMappingsActuatorTest.cs index aaa52b889c..6c63c31926 100644 --- a/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RouteMappingsActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/RouteMappings/RouteMappingsActuatorTest.cs @@ -39,6 +39,7 @@ public async Task Registers_dependent_services() services.AddRouteMappingsActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/Services/ServicesActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/Services/ServicesActuatorTest.cs index 7f0f22dce2..5dfae76140 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Services/ServicesActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Services/ServicesActuatorTest.cs @@ -35,6 +35,7 @@ public async Task Registers_dependent_services() services.AddServicesActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/Actuators/ThreadDump/ThreadDumpActuatorTest.cs b/src/Management/test/Endpoint.Test/Actuators/ThreadDump/ThreadDumpActuatorTest.cs index 0d10ceba71..a6887e2ad5 100644 --- a/src/Management/test/Endpoint.Test/Actuators/ThreadDump/ThreadDumpActuatorTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/ThreadDump/ThreadDumpActuatorTest.cs @@ -27,6 +27,7 @@ public async Task Registers_dependent_services() services.AddThreadDumpActuator(); await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); + // ReSharper disable once AccessToDisposedClosure Action action = () => serviceProvider.GetRequiredService(); action.Should().NotThrow(); diff --git a/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs b/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs index 8d161276df..81dfc18dee 100644 --- a/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs +++ b/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs @@ -515,6 +515,7 @@ public async Task RecoversFromSpringBootAdminServerRestarts() bool isServerOnline = false; using var handler = new DelegateToMockHttpClientHandler(); + // ReSharper disable once AccessToModifiedClosure MockedRequest registerMock = handler.Mock.When(HttpMethod.Post, "http://sba-server.com/instances").Respond(_ => isServerOnline ? new HttpResponseMessage(HttpStatusCode.OK) { diff --git a/src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs b/src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs index 278c99f366..82bb71d688 100644 --- a/src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs +++ b/src/Management/test/Tasks.Test/TaskHostExtensionsTest.cs @@ -199,6 +199,7 @@ public async Task WebApplication_LogsErrorOnUnknownTask() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(args); using var capturingLoggerProvider = new CapturingLoggerProvider(category => category.StartsWith("Steeltoe.", StringComparison.Ordinal)); + // ReSharper disable once AccessToDisposedClosure builder.Services.AddLogging(options => options.AddProvider(capturingLoggerProvider)); WebApplication app = builder.Build(); diff --git a/src/Steeltoe.All.slnx.DotSettings b/src/Steeltoe.All.slnx.DotSettings index 405dcb143e..1eb9393ada 100644 --- a/src/Steeltoe.All.slnx.DotSettings +++ b/src/Steeltoe.All.slnx.DotSettings @@ -597,6 +597,7 @@ UseVarWhenEvident False False + False Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the Apache 2.0 License. See the LICENSE file in the project root for more information. From c4c6820b247636c3433e83401925ccda5e855ab4 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:22:52 +0200 Subject: [PATCH 53/81] Connectors: add option to turn off the built-in post-processors (#1680) * Add property to disable built-in post-processors, including sample code for binding third-party brokers * Update links to supported service brokers * Review feedback: adapt documentation * Bugfix: preserve custom query string parameters in local RabbitMQ URI; relax other parsers to allow unknown parameters to support third-party brokers * Refactor to allow multiple CloudFoundryServiceBindingConfigurationSources in configuration, each scoped to a subset of post-processors * Fix broken build * Review feedback: filter already registered post-processors --- .../HostBuilderExtensionsTest.cs | 2 +- .../src/CloudFoundry/PublicAPI.Unshipped.txt | 13 + ...oundryServiceBindingConfigurationSource.cs | 12 +- .../CloudFoundryServiceBrokerTypes.cs | 62 ++++ .../ConfigurationBuilderExtensions.cs | 130 +++++++-- .../MongoDbCloudFoundryPostProcessor.cs | 2 +- .../MySqlCloudFoundryPostProcessor.cs | 6 +- .../PostgreSqlCloudFoundryPostProcessor.cs | 6 +- .../RabbitMQCloudFoundryPostProcessor.cs | 2 +- .../RedisCloudFoundryPostProcessor.cs | 7 +- .../SqlServerCloudFoundryPostProcessor.cs | 4 +- .../ConfigurationBuilderExtensions.cs | 2 +- ...ServiceBindingConfigurationProviderTest.cs | 6 +- .../ConfigurationBuilderExtensionsTest.cs | 29 ++ .../ConnectorConfigureOptionsBuilder.cs | 87 ++++++ .../src/Connectors/ConnectorConfigurer.cs | 17 +- .../CosmosDbConfigurationBuilderExtensions.cs | 10 +- .../MongoDbConfigurationBuilderExtensions.cs | 18 +- .../MongoDb/MongoDbConnectionStringBuilder.cs | 57 +--- ...MongoDbHostApplicationBuilderExtensions.cs | 9 +- .../MySqlConfigurationBuilderExtensions.cs | 18 +- .../MySqlHostApplicationBuilderExtensions.cs | 9 +- ...ostgreSqlConfigurationBuilderExtensions.cs | 22 +- ...tgreSqlHostApplicationBuilderExtensions.cs | 10 +- .../src/Connectors/PublicAPI.Unshipped.txt | 2 + .../RabbitMQConfigurationBuilderExtensions.cs | 20 +- .../RabbitMQConnectionStringBuilder.cs | 34 ++- ...abbitMQHostApplicationBuilderExtensions.cs | 9 +- .../RedisConfigurationBuilderExtensions.cs | 18 +- .../Redis/RedisConnectionStringBuilder.cs | 20 +- .../RedisHostApplicationBuilderExtensions.cs | 9 +- ...SqlServerConfigurationBuilderExtensions.cs | 21 +- ...lServerHostApplicationBuilderExtensions.cs | 9 +- .../test/Connectors.Test/CustomBrokerTests.cs | 266 ++++++++++++++++++ .../MongoDbConnectionStringBuilderTest.cs | 6 +- .../MongoDb/MongoDbConnectorTest.cs | 8 +- .../MySqlConnector/MySqlConnectorTest.cs | 14 +- .../MySql/Oracle/MySqlConnectorTest.cs | 14 +- .../PostgreSql/PostgreSqlConnectorTest.cs | 20 +- .../RabbitMQConnectionStringBuilderTest.cs | 33 ++- .../RabbitMQ/RabbitMQConnectorTest.cs | 18 +- .../Redis/RedisConnectionStringBuilderTest.cs | 6 +- .../Redis/RedisConnectorTest.cs | 6 +- .../MicrosoftData/SqlServerConnectorTest.cs | 14 +- .../SystemData/SqlServerConnectorTest.cs | 14 +- ...qlDbContextOptionsBuilderExtensionsTest.cs | 4 +- ...qlDbContextOptionsBuilderExtensionsTest.cs | 6 +- ...erDbContextOptionsBuilderExtensionsTest.cs | 4 +- .../test/Eureka.Test/CloudFoundryTest.cs | 10 +- .../RegisterMultipleDiscoveryClientsTest.cs | 11 +- .../PostConfigureJwtBearerOptionsTest.cs | 4 +- .../PostConfigureOpenIdConnectOptionsTest.cs | 2 +- 52 files changed, 904 insertions(+), 238 deletions(-) create mode 100644 src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBrokerTypes.cs create mode 100644 src/Connectors/test/Connectors.Test/CustomBrokerTests.cs diff --git a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs index 8dda994220..158756d68a 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs @@ -308,7 +308,7 @@ private static void AssertConnectorsAreAutowired(HostWrapper hostWrapper) var configuration = hostWrapper.Services.GetRequiredService(); configuration.EnumerateProviders().Should().NotBeEmpty(); - configuration.EnumerateProviders().Should().ContainSingle(); + configuration.EnumerateProviders().Should().NotBeEmpty(); hostWrapper.Services.GetService>().Should().NotBeNull(); hostWrapper.Services.GetService>().Should().NotBeNull(); diff --git a/src/Configuration/src/CloudFoundry/PublicAPI.Unshipped.txt b/src/Configuration/src/CloudFoundry/PublicAPI.Unshipped.txt index 7dc5c58110..f953312ae5 100644 --- a/src/Configuration/src/CloudFoundry/PublicAPI.Unshipped.txt +++ b/src/Configuration/src/CloudFoundry/PublicAPI.Unshipped.txt @@ -1 +1,14 @@ #nullable enable +static Steeltoe.Configuration.CloudFoundry.ServiceBindings.ConfigurationBuilderExtensions.AddCloudFoundryServiceBindings(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder, Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes brokerTypes) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! +static Steeltoe.Configuration.CloudFoundry.ServiceBindings.ConfigurationBuilderExtensions.AddCloudFoundryServiceBindings(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder, System.Predicate! ignoreKeyPredicate, Steeltoe.Configuration.CloudFoundry.ServiceBindings.IServiceBindingsReader? serviceBindingsReader, Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes brokerTypes, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! +Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes +Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.All = Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.Eureka | Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.Identity | Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.MongoDb | Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.MySql | Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.PostgreSql | Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.RabbitMQ | Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.Redis | Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.SqlServer -> Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes +Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.Eureka = 1 -> Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes +Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.Identity = 2 -> Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes +Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.MongoDb = 4 -> Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes +Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.MySql = 8 -> Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes +Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.None = 0 -> Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes +Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.PostgreSql = 16 -> Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes +Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.RabbitMQ = 32 -> Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes +Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.Redis = 64 -> Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes +Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes.SqlServer = 128 -> Steeltoe.Configuration.CloudFoundry.ServiceBindings.CloudFoundryServiceBrokerTypes diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBindingConfigurationSource.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBindingConfigurationSource.cs index ade7b18591..812960c4ce 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBindingConfigurationSource.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBindingConfigurationSource.cs @@ -2,19 +2,24 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Diagnostics; using Microsoft.Extensions.Configuration; namespace Steeltoe.Configuration.CloudFoundry.ServiceBindings; +[DebuggerDisplay("{DebuggerToString(),nq}")] internal sealed class CloudFoundryServiceBindingConfigurationSource : PostProcessorConfigurationSource, IConfigurationSource { private readonly IServiceBindingsReader _serviceBindingsReader; - public CloudFoundryServiceBindingConfigurationSource(IServiceBindingsReader serviceBindingsReader) + public CloudFoundryServiceBrokerTypes BrokerTypes { get; } + + public CloudFoundryServiceBindingConfigurationSource(IServiceBindingsReader serviceBindingsReader, CloudFoundryServiceBrokerTypes brokerTypes) { ArgumentNullException.ThrowIfNull(serviceBindingsReader); _serviceBindingsReader = serviceBindingsReader; + BrokerTypes = brokerTypes; } public IConfigurationProvider Build(IConfigurationBuilder builder) @@ -24,4 +29,9 @@ public IConfigurationProvider Build(IConfigurationBuilder builder) CaptureConfigurationBuilder(builder); return new CloudFoundryServiceBindingConfigurationProvider(this, _serviceBindingsReader); } + + private string DebuggerToString() + { + return $"{GetType().FullName} ({BrokerTypes})"; + } } diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBrokerTypes.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBrokerTypes.cs new file mode 100644 index 0000000000..187b30de3b --- /dev/null +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBrokerTypes.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +namespace Steeltoe.Configuration.CloudFoundry.ServiceBindings; + +/// +/// Lists the built-in Cloud Foundry service brokers. +/// +[Flags] +public enum CloudFoundryServiceBrokerTypes +{ + /// + /// Don't use any of the built-in brokers. + /// + None = 0x0, + + /// + /// Use the built-in brokers for Netflix Eureka. + /// + Eureka = 0x1, + + /// + /// Use the built-in brokers for JWT and OpenID Connect. + /// + Identity = 0x2, + + /// + /// Use the built-in brokers for MongoDB. + /// + MongoDb = 0x4, + + /// + /// Use the built-in brokers for MySQL. + /// + MySql = 0x8, + + /// + /// Use the built-in brokers for PostgreSQL. + /// + PostgreSql = 0x10, + + /// + /// Use the built-in brokers for RabbitMQ. + /// + RabbitMQ = 0x20, + + /// + /// Use the built-in brokers for Redis/Valkey. + /// + Redis = 0x40, + + /// + /// Use the built-in brokers for Microsoft SQL Server. + /// + SqlServer = 0x80, + + /// + /// Use all built-in brokers. + /// + All = Eureka | Identity | MongoDb | MySql | PostgreSql | RabbitMQ | Redis | SqlServer +} diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/ConfigurationBuilderExtensions.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/ConfigurationBuilderExtensions.cs index a7756115f9..8cdebbc329 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/ConfigurationBuilderExtensions.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/ConfigurationBuilderExtensions.cs @@ -29,7 +29,24 @@ public static class ConfigurationBuilderExtensions /// public static IConfigurationBuilder AddCloudFoundryServiceBindings(this IConfigurationBuilder builder) { - return builder.AddCloudFoundryServiceBindings(DefaultIgnoreKeyPredicate, DefaultReader, NullLoggerFactory.Instance); + return builder.AddCloudFoundryServiceBindings(DefaultIgnoreKeyPredicate, null, CloudFoundryServiceBrokerTypes.All, NullLoggerFactory.Instance); + } + + /// + /// Adds CloudFoundry service bindings from the JSON provided by the specified reader. + /// + /// + /// The to add configuration to. + /// + /// + /// The set of broker types to read service bindings for. + /// + /// + /// The incoming so that additional calls can be chained. + /// + public static IConfigurationBuilder AddCloudFoundryServiceBindings(this IConfigurationBuilder builder, CloudFoundryServiceBrokerTypes brokerTypes) + { + return builder.AddCloudFoundryServiceBindings(DefaultIgnoreKeyPredicate, null, brokerTypes, NullLoggerFactory.Instance); } /// @@ -46,7 +63,8 @@ public static IConfigurationBuilder AddCloudFoundryServiceBindings(this IConfigu /// public static IConfigurationBuilder AddCloudFoundryServiceBindings(this IConfigurationBuilder builder, IServiceBindingsReader serviceBindingsReader) { - return builder.AddCloudFoundryServiceBindings(DefaultIgnoreKeyPredicate, serviceBindingsReader, NullLoggerFactory.Instance); + return builder.AddCloudFoundryServiceBindings(DefaultIgnoreKeyPredicate, serviceBindingsReader, CloudFoundryServiceBrokerTypes.All, + NullLoggerFactory.Instance); } /// @@ -56,7 +74,7 @@ public static IConfigurationBuilder AddCloudFoundryServiceBindings(this IConfigu /// The to add configuration to. /// /// - /// A predicate which is called before adding a key to the configuration. If it returns false, the key will be ignored. + /// A predicate that is called before adding a key to the configuration. If it returns false, the key will be ignored. /// /// /// The source to read JSON service bindings from. @@ -69,15 +87,43 @@ public static IConfigurationBuilder AddCloudFoundryServiceBindings(this IConfigu /// public static IConfigurationBuilder AddCloudFoundryServiceBindings(this IConfigurationBuilder builder, Predicate ignoreKeyPredicate, IServiceBindingsReader serviceBindingsReader, ILoggerFactory loggerFactory) + { + return AddCloudFoundryServiceBindings(builder, ignoreKeyPredicate, serviceBindingsReader, CloudFoundryServiceBrokerTypes.All, loggerFactory); + } + + /// + /// Adds CloudFoundry service bindings from the JSON provided by the specified reader. + /// + /// + /// The to add configuration to. + /// + /// + /// A predicate that is called before adding a key to the configuration. If it returns false, the key will be ignored. + /// + /// + /// The source to read JSON service bindings from. + /// + /// + /// The set of broker types to read service bindings for. + /// + /// + /// Used for internal logging. Pass to disable logging. + /// + /// + /// The incoming so that additional calls can be chained. + /// + public static IConfigurationBuilder AddCloudFoundryServiceBindings(this IConfigurationBuilder builder, Predicate ignoreKeyPredicate, + IServiceBindingsReader? serviceBindingsReader, CloudFoundryServiceBrokerTypes brokerTypes, ILoggerFactory loggerFactory) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(ignoreKeyPredicate); - ArgumentNullException.ThrowIfNull(serviceBindingsReader); ArgumentNullException.ThrowIfNull(loggerFactory); - if (!builder.EnumerateSources().Any()) + CloudFoundryServiceBrokerTypes missingBrokerTypes = GetMissingBrokerTypes(builder, brokerTypes); + + if (missingBrokerTypes != CloudFoundryServiceBrokerTypes.None) { - var source = new CloudFoundryServiceBindingConfigurationSource(serviceBindingsReader) + var source = new CloudFoundryServiceBindingConfigurationSource(serviceBindingsReader ?? DefaultReader, missingBrokerTypes) { IgnoreKeyPredicate = ignoreKeyPredicate }; @@ -86,25 +132,71 @@ public static IConfigurationBuilder AddCloudFoundryServiceBindings(this IConfigu // WebApplicationBuilder immediately builds the configuration provider and loads it, which executes the post-processors. // Therefore, adding post-processors afterward is a no-op. - RegisterPostProcessors(source, loggerFactory); + RegisterPostProcessors(source, missingBrokerTypes, loggerFactory); builder.Add(source); } return builder; } - private static void RegisterPostProcessors(CloudFoundryServiceBindingConfigurationSource source, ILoggerFactory loggerFactory) + private static CloudFoundryServiceBrokerTypes GetMissingBrokerTypes(IConfigurationBuilder builder, CloudFoundryServiceBrokerTypes brokerTypesRequested) { - ILogger eurekaLogger = loggerFactory.CreateLogger(); - ILogger identityLogger = loggerFactory.CreateLogger(); - - source.RegisterPostProcessor(new EurekaCloudFoundryPostProcessor(eurekaLogger)); - source.RegisterPostProcessor(new IdentityCloudFoundryPostProcessor(identityLogger)); - source.RegisterPostProcessor(new MongoDbCloudFoundryPostProcessor()); - source.RegisterPostProcessor(new MySqlCloudFoundryPostProcessor()); - source.RegisterPostProcessor(new PostgreSqlCloudFoundryPostProcessor()); - source.RegisterPostProcessor(new RabbitMQCloudFoundryPostProcessor()); - source.RegisterPostProcessor(new RedisCloudFoundryPostProcessor()); - source.RegisterPostProcessor(new SqlServerCloudFoundryPostProcessor()); + CloudFoundryServiceBrokerTypes missingBrokerTypes = brokerTypesRequested; + + if (brokerTypesRequested != CloudFoundryServiceBrokerTypes.None) + { + foreach (CloudFoundryServiceBindingConfigurationSource existingSource in builder.EnumerateSources()) + { + missingBrokerTypes &= ~existingSource.BrokerTypes; + } + } + + return missingBrokerTypes; + } + + private static void RegisterPostProcessors(CloudFoundryServiceBindingConfigurationSource source, CloudFoundryServiceBrokerTypes brokerTypes, + ILoggerFactory loggerFactory) + { + if (brokerTypes.HasFlag(CloudFoundryServiceBrokerTypes.Eureka)) + { + ILogger eurekaLogger = loggerFactory.CreateLogger(); + source.RegisterPostProcessor(new EurekaCloudFoundryPostProcessor(eurekaLogger)); + } + + if (brokerTypes.HasFlag(CloudFoundryServiceBrokerTypes.Identity)) + { + ILogger identityLogger = loggerFactory.CreateLogger(); + source.RegisterPostProcessor(new IdentityCloudFoundryPostProcessor(identityLogger)); + } + + if (brokerTypes.HasFlag(CloudFoundryServiceBrokerTypes.MongoDb)) + { + source.RegisterPostProcessor(new MongoDbCloudFoundryPostProcessor()); + } + + if (brokerTypes.HasFlag(CloudFoundryServiceBrokerTypes.MySql)) + { + source.RegisterPostProcessor(new MySqlCloudFoundryPostProcessor()); + } + + if (brokerTypes.HasFlag(CloudFoundryServiceBrokerTypes.PostgreSql)) + { + source.RegisterPostProcessor(new PostgreSqlCloudFoundryPostProcessor()); + } + + if (brokerTypes.HasFlag(CloudFoundryServiceBrokerTypes.RabbitMQ)) + { + source.RegisterPostProcessor(new RabbitMQCloudFoundryPostProcessor()); + } + + if (brokerTypes.HasFlag(CloudFoundryServiceBrokerTypes.Redis)) + { + source.RegisterPostProcessor(new RedisCloudFoundryPostProcessor()); + } + + if (brokerTypes.HasFlag(CloudFoundryServiceBrokerTypes.SqlServer)) + { + source.RegisterPostProcessor(new SqlServerCloudFoundryPostProcessor()); + } } } diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/MongoDbCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/MongoDbCloudFoundryPostProcessor.cs index 604e3c1fcd..85426606fe 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/MongoDbCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/MongoDbCloudFoundryPostProcessor.cs @@ -16,7 +16,7 @@ public override void PostProcessConfiguration(PostProcessorConfigurationProvider // Mapping from CloudFoundry service binding credentials to driver-specific connection string parameters. // The available credentials are documented at: - // - Azure Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/tanzu-cloud-service-broker-for-microsoft-azure/1-13/csb-azure/reference-azure-cosmosdb-mongo.html#binding-creds + // - Azure Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/cloud-service-broker-azure/1-13/csb-azure/reference-azure-cosmosdb-mongo.html#binding-creds mapper.MapFromTo("credentials:uri", "url"); diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/MySqlCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/MySqlCloudFoundryPostProcessor.cs index b70f530628..bca767b744 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/MySqlCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/MySqlCloudFoundryPostProcessor.cs @@ -16,9 +16,9 @@ public override void PostProcessConfiguration(PostProcessorConfigurationProvider // Mapping from CloudFoundry service binding credentials to driver-specific connection string parameters. // The available credentials are documented at: - // - Tanzu Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/data-solutions/tanzu-for-mysql-on-cloud-foundry/10-0/mysql-for-tpcf/use.html#vcap - // - GCP Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/tanzu-cloud-service-broker-for-gcp/1-9/csb-gcp/reference-gcp-mysql.html#binding-creds - // - AWS Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/tanzu-cloud-service-broker-for-aws/1-14/csb-aws/reference-aws-mysql.html#binding-creds + // - Tanzu Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/tanzu-mysql-tanzu-platform/10-1/mysql-tp/use.html#vcap + // - GCP Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/cloud-service-broker-gcp/1-8/csb-gcp/reference-gcp-mysql.html#binding-creds + // - AWS Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/cloud-service-broker-aws/1-15/csb-aws/reference-aws-mysql.html#binding-creds mapper.MapFromTo("credentials:hostname", "host"); mapper.MapFromTo("credentials:port", "port"); diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/PostgreSqlCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/PostgreSqlCloudFoundryPostProcessor.cs index e044a20892..2e54c28d5a 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/PostgreSqlCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/PostgreSqlCloudFoundryPostProcessor.cs @@ -22,9 +22,9 @@ public override void PostProcessConfiguration(PostProcessorConfigurationProvider // Mapping from CloudFoundry service binding credentials to driver-specific connection string parameters. // The available credentials are documented at: - // - Tanzu Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/data-solutions/tanzu-for-postgres-on-cloud-foundry/10-1/postgres/app-setup-single-instance-service-guide.html - // - GCP Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/tanzu-cloud-service-broker-for-gcp/1-9/csb-gcp/reference-gcp-postgresql.html#binding-creds - // - AWS Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/tanzu-cloud-service-broker-for-aws/1-14/csb-aws/reference-aws-postgres.html#binding-creds + // - Tanzu Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/tanzu-postgres-tanzu-platform/10-2/postgres-tp/app-setup-single-instance-service-guide.html + // - GCP Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/cloud-service-broker-gcp/1-8/csb-gcp/reference-gcp-postgresql.html#binding-creds + // - AWS Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/cloud-service-broker-aws/1-15/csb-aws/reference-aws-postgres.html#binding-creds string? hosts = mapper.MapArrayFromTo("credentials:hosts", "host", HostsSeparator); diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/RabbitMQCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/RabbitMQCloudFoundryPostProcessor.cs index 8849fc6598..b5afd85194 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/RabbitMQCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/RabbitMQCloudFoundryPostProcessor.cs @@ -16,7 +16,7 @@ public override void PostProcessConfiguration(PostProcessorConfigurationProvider // Mapping from CloudFoundry service binding credentials to driver-specific connection string parameters. // The available credentials are documented at: - // - Tanzu Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/data-solutions/tanzu-rabbitmq-on-cloud-foundry/10-0/tanzu-rabbitmq-cloud-foundry/reference.html + // - Tanzu Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/tanzu-rabbitmq-tanzu-platform/10-0/rabbitmq-tp/use.html#call string? useTlsValue = mapper.MapFromTo("credentials:ssl", "useTls"); string fromProtocol = bool.TryParse(useTlsValue, out bool useTls) && useTls ? "amqp+ssl" : "amqp"; diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/RedisCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/RedisCloudFoundryPostProcessor.cs index 81880bc28c..56ceb8890d 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/RedisCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/RedisCloudFoundryPostProcessor.cs @@ -16,10 +16,9 @@ public override void PostProcessConfiguration(PostProcessorConfigurationProvider // Mapping from CloudFoundry service binding credentials to driver-specific connection string parameters. // The available credentials are documented at: - // - Tanzu Broker (Redis): https://techdocs.broadcom.com/us/en/vmware-tanzu/data-solutions/redis-for-tanzu-application-service/3-5/redis-for-tas/using.html#use-redis-service-in-app - // - Tanzu Broker (Valkey): https://techdocs.broadcom.com/us/en/vmware-tanzu/data-solutions/tanzu-for-valkey-on-cloud-foundry/4-0/valkey-on-cf/using.html#use-valkey-service-in-app - // - Azure Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/tanzu-cloud-service-broker-for-microsoft-azure/1-13/csb-azure/reference-azure-redis.html#binding-creds - // - AWS Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/tanzu-cloud-service-broker-for-aws/1-14/csb-aws/reference-aws-redis.html#binding-creds + // - Tanzu Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/tanzu-valkey-tanzu-platform/10-2/valkey-tp/using.html#use-valkey-service-in-app + // - Azure Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/cloud-service-broker-azure/1-13/csb-azure/reference-azure-redis.html + // - AWS Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/cloud-service-broker-aws/1-15/csb-aws/reference-aws-redis.html mapper.MapFromTo("credentials:host", "host"); mapper.MapFromTo("credentials:port", "port"); diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/SqlServerCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/SqlServerCloudFoundryPostProcessor.cs index 4b595cf7d6..f4f38827fa 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/SqlServerCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/PostProcessors/SqlServerCloudFoundryPostProcessor.cs @@ -16,8 +16,8 @@ public override void PostProcessConfiguration(PostProcessorConfigurationProvider // Mapping from CloudFoundry service binding credentials to driver-specific connection string parameters. // The available credentials are documented at: - // - Azure Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/tanzu-cloud-service-broker-for-microsoft-azure/1-13/csb-azure/reference-azure-mssql-db.html#binding-creds - // - AWS Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/tanzu-cloud-service-broker-for-aws/1-14/csb-aws/reference-aws-mssql.html#binding-creds + // - Azure Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/cloud-service-broker-azure/1-13/csb-azure/reference-azure-mssql-db.html#binding-creds + // - AWS Service Broker: https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/cloud-service-broker-aws/1-15/csb-aws/reference-aws-mssql.html#binding-creds mapper.MapFromTo("credentials:hostname", "Data Source"); mapper.MapFromAppendTo("credentials:port", "Data Source", ","); diff --git a/src/Configuration/src/Kubernetes.ServiceBindings/ConfigurationBuilderExtensions.cs b/src/Configuration/src/Kubernetes.ServiceBindings/ConfigurationBuilderExtensions.cs index c946f40952..3b0ffa3cd8 100644 --- a/src/Configuration/src/Kubernetes.ServiceBindings/ConfigurationBuilderExtensions.cs +++ b/src/Configuration/src/Kubernetes.ServiceBindings/ConfigurationBuilderExtensions.cs @@ -68,7 +68,7 @@ public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigura /// Whether the configuration should be reloaded if the files are changed, added or removed. /// /// - /// A predicate which is called before adding a key to the configuration. If it returns false, the key will be ignored. + /// A predicate that is called before adding a key to the configuration. If it returns false, the key will be ignored. /// /// /// The source to read Kubernetes secret files on disk from. diff --git a/src/Configuration/test/CloudFoundry.Test/ServiceBindings/CloudFoundryServiceBindingConfigurationProviderTest.cs b/src/Configuration/test/CloudFoundry.Test/ServiceBindings/CloudFoundryServiceBindingConfigurationProviderTest.cs index 91aa24f7fd..ce973ec42f 100644 --- a/src/Configuration/test/CloudFoundry.Test/ServiceBindings/CloudFoundryServiceBindingConfigurationProviderTest.cs +++ b/src/Configuration/test/CloudFoundry.Test/ServiceBindings/CloudFoundryServiceBindingConfigurationProviderTest.cs @@ -118,7 +118,7 @@ public void PostProcessors_OnByDefault() var postProcessor = new TestPostProcessor(); var reader = new StringServiceBindingsReader(VcapServicesJson); - var source = new CloudFoundryServiceBindingConfigurationSource(reader); + var source = new CloudFoundryServiceBindingConfigurationSource(reader, CloudFoundryServiceBrokerTypes.All); source.RegisterPostProcessor(postProcessor); var builder = new ConfigurationBuilder(); @@ -137,7 +137,7 @@ public void Build_CapturesParentConfiguration() }; var reader = new StringServiceBindingsReader(string.Empty); - var source = new CloudFoundryServiceBindingConfigurationSource(reader); + var source = new CloudFoundryServiceBindingConfigurationSource(reader, CloudFoundryServiceBrokerTypes.All); var builder = new ConfigurationBuilder(); builder.Add(source); @@ -153,7 +153,7 @@ public void Build_CapturesParentConfiguration() public void Build_LoadsServiceBindings() { var reader = new StringServiceBindingsReader(VcapServicesJson); - var source = new CloudFoundryServiceBindingConfigurationSource(reader); + var source = new CloudFoundryServiceBindingConfigurationSource(reader, CloudFoundryServiceBrokerTypes.All); var builder = new ConfigurationBuilder(); builder.Add(source); diff --git a/src/Configuration/test/CloudFoundry.Test/ServiceBindings/ConfigurationBuilderExtensionsTest.cs b/src/Configuration/test/CloudFoundry.Test/ServiceBindings/ConfigurationBuilderExtensionsTest.cs index 0511f2e41b..672b54db16 100644 --- a/src/Configuration/test/CloudFoundry.Test/ServiceBindings/ConfigurationBuilderExtensionsTest.cs +++ b/src/Configuration/test/CloudFoundry.Test/ServiceBindings/ConfigurationBuilderExtensionsTest.cs @@ -57,6 +57,35 @@ public void AddCloudFoundryServiceBindings_RegistersProcessors() source.PostProcessors.Should().NotBeEmpty(); } + [Fact] + public void AddCloudFoundryServiceBindings_RegistersSubsetOfProcessors() + { + var builder = new ConfigurationBuilder(); + builder.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.PostgreSql | CloudFoundryServiceBrokerTypes.MySql); + + builder.Sources.Should().ContainSingle(); + CloudFoundryServiceBindingConfigurationSource source = builder.Sources[0].Should().BeOfType().Subject; + source.PostProcessors.Should().HaveCount(2); + } + + [Fact] + public void AddCloudFoundryServiceBindings_DoesNotAddMultipleSourcesForSamePostProcessor() + { + var builder = new ConfigurationBuilder(); + builder.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.None); + builder.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.PostgreSql | CloudFoundryServiceBrokerTypes.MySql); + builder.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.PostgreSql | CloudFoundryServiceBrokerTypes.SqlServer); + builder.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.MySql | CloudFoundryServiceBrokerTypes.RabbitMQ); + builder.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.SqlServer | CloudFoundryServiceBrokerTypes.RabbitMQ); + + CloudFoundryServiceBindingConfigurationSource[] sources = [.. builder.Sources.OfType()]; + + sources.Should().HaveCount(3); + sources[0].BrokerTypes.Should().Be(CloudFoundryServiceBrokerTypes.PostgreSql | CloudFoundryServiceBrokerTypes.MySql); + sources[1].BrokerTypes.Should().Be(CloudFoundryServiceBrokerTypes.SqlServer); + sources[2].BrokerTypes.Should().Be(CloudFoundryServiceBrokerTypes.RabbitMQ); + } + [Fact] public void AddCloudFoundryServiceBindings_EnvironmentVariableSet_LoadsServiceBindings() { diff --git a/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs b/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs index 85f4a87b56..ce887382a1 100644 --- a/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs +++ b/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs @@ -2,10 +2,14 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; + namespace Steeltoe.Connectors; public sealed class ConnectorConfigureOptionsBuilder { + internal CloudFoundryServiceBrokerTypes CloudFoundryBrokerTypes { get; set; } + /// /// Gets or sets a value indicating whether connection string changes are detected while the application is running. This is false by default to /// optimize startup performance. When set to true, existing configuration providers may get reloaded multiple times, potentially resulting in @@ -13,4 +17,87 @@ public sealed class ConnectorConfigureOptionsBuilder /// is false. /// public bool DetectConfigurationChanges { get; set; } + + /// + /// Gets or sets a value indicating whether to turn off the built-in service broker support. This is false by default, but should be set to + /// true when using custom logic to convert platform-based credentials to driver-specific configuration keys. + /// + /// + /// For example, to use a third-party Cloud Foundry service broker that sets the + /// + /// VCAP_SERVICES + /// + /// environment variable to: + /// + /// { + /// "custom-postgres-broker": [ + /// { + /// "name": "products-db", + /// "credentials": { + /// "custom-hostname-key": "example.cloud.com", + /// "custom-port-key": 2345, + /// "custom-username-key": "products-user", + /// "custom-password-key": "products-secret", + /// "custom-database-name-key": "product-database" + /// } + /// }, + /// { + /// "name": "orders-db", + /// "credentials": { + /// "custom-hostname-key": "example.cloud.com", + /// "custom-port-key": 2345, + /// "custom-username-key": "orders-user", + /// "custom-password-key": "orders-secret", + /// "custom-database-name-key": "order-database" + /// } + /// } + /// ] + /// } + /// + /// The following code can be used to map the PostgreSQL credentials to the format that + /// + /// NpgsqlConnectionStringBuilder + /// + /// expects: + /// configure.SkipDefaultServiceBindings = true, null); + /// var app = builder.Build(); + /// + /// var factory = app.Services.GetRequiredService>(); + /// + /// PostgreSqlOptions productsDbOptions = factory.Get("products-db").Options; + /// Console.WriteLine(productsDbOptions.ConnectionString); + /// // Database=product-database;Host=example.cloud.com;Password=products-secret;Port=2345;Username=products-user + /// + /// PostgreSqlOptions ordersDbOptions = factory.Get("orders-db").Options; + /// Console.WriteLine(ordersDbOptions.ConnectionString); + /// // Database=order-database;Host=example.cloud.com;Password=orders-secret;Port=2345;Username=orders-user + /// + /// void MapCustomServiceBindings(string brokerName) + /// { + /// var options = builder.Configuration.GetSection("vcap").Get(); + /// + /// foreach (CloudFoundryService service in options?.Services + /// .Where(pair => pair.Key == brokerName) + /// .SelectMany(pair => pair.Value) ?? []) + /// { + /// builder.Configuration.AddInMemoryCollection(new Dictionary + /// { + /// // Map credentials into the property names expected by NpgsqlConnectionStringBuilder. + /// [$"steeltoe:service-bindings:postgresql:{service.Name}:host"] = service.Credentials["custom-hostname-key"].Value, + /// [$"steeltoe:service-bindings:postgresql:{service.Name}:port"] = service.Credentials["custom-port-key"].Value, + /// [$"steeltoe:service-bindings:postgresql:{service.Name}:username"] = service.Credentials["custom-username-key"].Value, + /// [$"steeltoe:service-bindings:postgresql:{service.Name}:password"] = service.Credentials["custom-password-key"].Value, + /// [$"steeltoe:service-bindings:postgresql:{service.Name}:database"] = service.Credentials["custom-database-name-key"].Value + /// }); + /// } + /// } + /// ]]> + /// + /// + /// + public bool SkipDefaultServiceBindings { get; set; } } diff --git a/src/Connectors/src/Connectors/ConnectorConfigurer.cs b/src/Connectors/src/Connectors/ConnectorConfigurer.cs index 2bb05f61b5..5b5c489be1 100644 --- a/src/Connectors/src/Connectors/ConnectorConfigurer.cs +++ b/src/Connectors/src/Connectors/ConnectorConfigurer.cs @@ -3,25 +3,32 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using Steeltoe.Configuration; using Steeltoe.Configuration.CloudFoundry.ServiceBindings; using Steeltoe.Configuration.Kubernetes.ServiceBindings; +using IServiceBindingsReader = Steeltoe.Configuration.CloudFoundry.ServiceBindings.IServiceBindingsReader; namespace Steeltoe.Connectors; internal static class ConnectorConfigurer { - public static void Configure(IConfigurationBuilder builder, Action? configureAction, - TPostProcessor connectionStringPostProcessor) + private static readonly Predicate DefaultIgnoreKeyPredicate = _ => false; + + public static void Configure(IConfigurationBuilder builder, Action configureAction, + TPostProcessor connectionStringPostProcessor, IServiceBindingsReader? serviceBindingsReader, ILoggerFactory loggerFactory) where TPostProcessor : ConnectionStringPostProcessor { if (!IsConfigured(builder)) { var optionsBuilder = new ConnectorConfigureOptionsBuilder(); - configureAction?.Invoke(optionsBuilder); + configureAction.Invoke(optionsBuilder); - builder.AddCloudFoundryServiceBindings(); - builder.AddKubernetesServiceBindings(); + if (!optionsBuilder.SkipDefaultServiceBindings) + { + builder.AddCloudFoundryServiceBindings(DefaultIgnoreKeyPredicate, serviceBindingsReader, optionsBuilder.CloudFoundryBrokerTypes, loggerFactory); + builder.AddKubernetesServiceBindings(); + } RegisterPostProcessor(connectionStringPostProcessor, builder, optionsBuilder.DetectConfigurationChanges); } diff --git a/src/Connectors/src/Connectors/CosmosDb/CosmosDbConfigurationBuilderExtensions.cs b/src/Connectors/src/Connectors/CosmosDb/CosmosDbConfigurationBuilderExtensions.cs index 2d5ea2513b..24bf87d6a5 100644 --- a/src/Connectors/src/Connectors/CosmosDb/CosmosDbConfigurationBuilderExtensions.cs +++ b/src/Connectors/src/Connectors/CosmosDb/CosmosDbConfigurationBuilderExtensions.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging.Abstractions; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; namespace Steeltoe.Connectors.CosmosDb; @@ -38,7 +40,13 @@ public static IConfigurationBuilder ConfigureCosmosDb(this IConfigurationBuilder { ArgumentNullException.ThrowIfNull(builder); - ConnectorConfigurer.Configure(builder, configureAction, new CosmosDbConnectionStringPostProcessor()); + Action overrideConfigureAction = options => + { + configureAction?.Invoke(options); + options.CloudFoundryBrokerTypes = CloudFoundryServiceBrokerTypes.None; + }; + + ConnectorConfigurer.Configure(builder, overrideConfigureAction, new CosmosDbConnectionStringPostProcessor(), null, NullLoggerFactory.Instance); return builder; } } diff --git a/src/Connectors/src/Connectors/MongoDb/MongoDbConfigurationBuilderExtensions.cs b/src/Connectors/src/Connectors/MongoDb/MongoDbConfigurationBuilderExtensions.cs index ef5fd61509..30e6d43a1e 100644 --- a/src/Connectors/src/Connectors/MongoDb/MongoDbConfigurationBuilderExtensions.cs +++ b/src/Connectors/src/Connectors/MongoDb/MongoDbConfigurationBuilderExtensions.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging.Abstractions; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; namespace Steeltoe.Connectors.MongoDb; @@ -35,10 +37,24 @@ public static IConfigurationBuilder ConfigureMongoDb(this IConfigurationBuilder /// The incoming so that additional calls can be chained. /// public static IConfigurationBuilder ConfigureMongoDb(this IConfigurationBuilder builder, Action? configureAction) + { + return ConfigureMongoDb(builder, configureAction, null); + } + + internal static IConfigurationBuilder ConfigureMongoDb(this IConfigurationBuilder builder, Action? configureAction, + IServiceBindingsReader? serviceBindingsReader) { ArgumentNullException.ThrowIfNull(builder); - ConnectorConfigurer.Configure(builder, configureAction, new MongoDbConnectionStringPostProcessor()); + Action overrideConfigureAction = options => + { + configureAction?.Invoke(options); + options.CloudFoundryBrokerTypes = options.SkipDefaultServiceBindings ? CloudFoundryServiceBrokerTypes.None : CloudFoundryServiceBrokerTypes.MongoDb; + }; + + ConnectorConfigurer.Configure(builder, overrideConfigureAction, new MongoDbConnectionStringPostProcessor(), serviceBindingsReader, + NullLoggerFactory.Instance); + return builder; } } diff --git a/src/Connectors/src/Connectors/MongoDb/MongoDbConnectionStringBuilder.cs b/src/Connectors/src/Connectors/MongoDb/MongoDbConnectionStringBuilder.cs index f70ac70a79..ec3a8c6c73 100644 --- a/src/Connectors/src/Connectors/MongoDb/MongoDbConnectionStringBuilder.cs +++ b/src/Connectors/src/Connectors/MongoDb/MongoDbConnectionStringBuilder.cs @@ -22,7 +22,7 @@ internal sealed class MongoDbConnectionStringBuilder : IConnectionStringBuilder public string ConnectionString { get => ToConnectionString(); - set => FromConnectionString(value, false); + set => FromConnectionString(value); } /// @@ -37,14 +37,8 @@ public object? this[string keyword] return ConnectionString; } - // Allow getting unknown keyword, if it was set earlier. We don't pretend to know all valid query string parameters. - if (_settings.TryGetValue(keyword, out string? value)) - { - return value; - } - - AssertIsKnownKeyword(keyword); - return null; + // Allow getting unknown keyword. We don't pretend to know all valid query string parameters. + return _settings.GetValueOrDefault(keyword); } set { @@ -52,7 +46,7 @@ public object? this[string keyword] if (string.Equals(keyword, KnownKeywords.Url, StringComparison.OrdinalIgnoreCase)) { - FromConnectionString(value?.ToString(), true); + ConnectionString = value?.ToString(); } else { @@ -106,9 +100,9 @@ private string ToConnectionString() var queryString = default(QueryString); - foreach ((string keyword, string value) in _settings.Where(pair => !KnownKeywords.Exists(pair.Key))) + foreach ((string name, string value) in _settings.Where(pair => !KnownKeywords.Exists(pair.Key))) { - queryString = queryString.Add(keyword, value); + queryString = queryString.Add(name, value); } builder.Query = queryString.Value; @@ -116,19 +110,9 @@ private string ToConnectionString() return builder.Uri.AbsoluteUri; } - private void FromConnectionString(string? connectionString, bool preserveUnknownSettings) + private void FromConnectionString(string? connectionString) { - if (preserveUnknownSettings) - { - foreach (string keywordToRemove in _settings.Keys.Where(KnownKeywords.Exists).ToArray()) - { - _settings.Remove(keywordToRemove); - } - } - else - { - _settings.Clear(); - } + _settings.Clear(); if (!string.IsNullOrEmpty(connectionString)) { @@ -140,7 +124,7 @@ private void FromConnectionString(string? connectionString, bool preserveUnknown #pragma warning restore S3717 // Track use of "NotImplementedException" } - // MongoDB allows semicolon as separator for query string parameters, to provide backwards compatibility. + // MongoDB allows semicolon as separator between query string parameters for backward compatibility. connectionString = connectionString.Replace(';', '&'); var uri = new Uri(connectionString); @@ -169,31 +153,20 @@ private void FromConnectionString(string? connectionString, bool preserveUnknown _settings[KnownKeywords.AuthenticationDatabase] = Uri.UnescapeDataString(uri.AbsolutePath[1..]); } - NameValueCollection queryCollection = HttpUtility.ParseQueryString(uri.Query); + NameValueCollection queryString = HttpUtility.ParseQueryString(uri.Query); - foreach (string? key in queryCollection.AllKeys) + foreach (string remainingKeyword in queryString.AllKeys.Where(key => key != null && !KnownKeywords.Exists(key)).Cast()) { - if (key != null) - { - string? value = queryCollection.Get(key); + string? value = queryString.Get(remainingKeyword); - if (value != null) - { - _settings[key] = value; - } + if (value != null) + { + _settings[remainingKeyword] = value; } } } } - private static void AssertIsKnownKeyword(string keyword) - { - if (!KnownKeywords.Exists(keyword)) - { - throw new ArgumentException($"Keyword not supported: '{keyword}'.", nameof(keyword)); - } - } - private static class KnownKeywords { public const string Url = "url"; diff --git a/src/Connectors/src/Connectors/MongoDb/MongoDbHostApplicationBuilderExtensions.cs b/src/Connectors/src/Connectors/MongoDb/MongoDbHostApplicationBuilderExtensions.cs index 021c8b4288..336673591f 100644 --- a/src/Connectors/src/Connectors/MongoDb/MongoDbHostApplicationBuilderExtensions.cs +++ b/src/Connectors/src/Connectors/MongoDb/MongoDbHostApplicationBuilderExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Hosting; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; namespace Steeltoe.Connectors.MongoDb; @@ -41,10 +42,16 @@ public static IHostApplicationBuilder AddMongoDb(this IHostApplicationBuilder bu /// public static IHostApplicationBuilder AddMongoDb(this IHostApplicationBuilder builder, Action? configureAction, Action? addAction) + { + return AddMongoDb(builder, configureAction, addAction, null); + } + + internal static IHostApplicationBuilder AddMongoDb(this IHostApplicationBuilder builder, Action? configureAction, + Action? addAction, IServiceBindingsReader? serviceBindingsReader) { ArgumentNullException.ThrowIfNull(builder); - builder.Configuration.ConfigureMongoDb(configureAction); + builder.Configuration.ConfigureMongoDb(configureAction, serviceBindingsReader); builder.Services.AddMongoDb(builder.Configuration, addAction); return builder; } diff --git a/src/Connectors/src/Connectors/MySql/MySqlConfigurationBuilderExtensions.cs b/src/Connectors/src/Connectors/MySql/MySqlConfigurationBuilderExtensions.cs index 5eda76fffc..7e37ffe389 100644 --- a/src/Connectors/src/Connectors/MySql/MySqlConfigurationBuilderExtensions.cs +++ b/src/Connectors/src/Connectors/MySql/MySqlConfigurationBuilderExtensions.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging.Abstractions; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; using Steeltoe.Connectors.MySql.DynamicTypeAccess; namespace Steeltoe.Connectors.MySql; @@ -20,7 +22,7 @@ public static class MySqlConfigurationBuilderExtensions /// public static IConfigurationBuilder ConfigureMySql(this IConfigurationBuilder builder) { - return ConfigureMySql(builder, MySqlPackageResolver.Default); + return ConfigureMySql(builder, null); } /// @@ -37,16 +39,24 @@ public static IConfigurationBuilder ConfigureMySql(this IConfigurationBuilder bu /// public static IConfigurationBuilder ConfigureMySql(this IConfigurationBuilder builder, Action? configureAction) { - return ConfigureMySql(builder, MySqlPackageResolver.Default, configureAction); + return ConfigureMySql(builder, MySqlPackageResolver.Default, configureAction, null); } internal static IConfigurationBuilder ConfigureMySql(this IConfigurationBuilder builder, MySqlPackageResolver packageResolver, - Action? configureAction = null) + Action? configureAction, IServiceBindingsReader? serviceBindingsReader) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(packageResolver); - ConnectorConfigurer.Configure(builder, configureAction, new MySqlConnectionStringPostProcessor(packageResolver)); + Action overrideConfigureAction = options => + { + configureAction?.Invoke(options); + options.CloudFoundryBrokerTypes = options.SkipDefaultServiceBindings ? CloudFoundryServiceBrokerTypes.None : CloudFoundryServiceBrokerTypes.MySql; + }; + + ConnectorConfigurer.Configure(builder, overrideConfigureAction, new MySqlConnectionStringPostProcessor(packageResolver), serviceBindingsReader, + NullLoggerFactory.Instance); + return builder; } } diff --git a/src/Connectors/src/Connectors/MySql/MySqlHostApplicationBuilderExtensions.cs b/src/Connectors/src/Connectors/MySql/MySqlHostApplicationBuilderExtensions.cs index d5454e4b24..56f4335355 100644 --- a/src/Connectors/src/Connectors/MySql/MySqlHostApplicationBuilderExtensions.cs +++ b/src/Connectors/src/Connectors/MySql/MySqlHostApplicationBuilderExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Hosting; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; using Steeltoe.Connectors.MySql.DynamicTypeAccess; namespace Steeltoe.Connectors.MySql; @@ -21,7 +22,7 @@ public static class MySqlHostApplicationBuilderExtensions /// public static IHostApplicationBuilder AddMySql(this IHostApplicationBuilder builder) { - return AddMySql(builder, MySqlPackageResolver.Default); + return AddMySql(builder, null, null); } /// @@ -43,16 +44,16 @@ public static IHostApplicationBuilder AddMySql(this IHostApplicationBuilder buil public static IHostApplicationBuilder AddMySql(this IHostApplicationBuilder builder, Action? configureAction, Action? addAction) { - return AddMySql(builder, MySqlPackageResolver.Default, configureAction, addAction); + return AddMySql(builder, MySqlPackageResolver.Default, configureAction, addAction, null); } internal static IHostApplicationBuilder AddMySql(this IHostApplicationBuilder builder, MySqlPackageResolver packageResolver, - Action? configureAction = null, Action? addAction = null) + Action? configureAction, Action? addAction, IServiceBindingsReader? serviceBindingsReader) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(packageResolver); - builder.Configuration.ConfigureMySql(packageResolver, configureAction); + builder.Configuration.ConfigureMySql(packageResolver, configureAction, serviceBindingsReader); builder.Services.AddMySql(builder.Configuration, packageResolver, addAction); return builder; } diff --git a/src/Connectors/src/Connectors/PostgreSql/PostgreSqlConfigurationBuilderExtensions.cs b/src/Connectors/src/Connectors/PostgreSql/PostgreSqlConfigurationBuilderExtensions.cs index cff6a541be..06ee495a8f 100644 --- a/src/Connectors/src/Connectors/PostgreSql/PostgreSqlConfigurationBuilderExtensions.cs +++ b/src/Connectors/src/Connectors/PostgreSql/PostgreSqlConfigurationBuilderExtensions.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging.Abstractions; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; using Steeltoe.Connectors.PostgreSql.DynamicTypeAccess; namespace Steeltoe.Connectors.PostgreSql; @@ -20,7 +22,7 @@ public static class PostgreSqlConfigurationBuilderExtensions /// public static IConfigurationBuilder ConfigurePostgreSql(this IConfigurationBuilder builder) { - return ConfigurePostgreSql(builder, PostgreSqlPackageResolver.Default); + return ConfigurePostgreSql(builder, null); } /// @@ -37,16 +39,26 @@ public static IConfigurationBuilder ConfigurePostgreSql(this IConfigurationBuild /// public static IConfigurationBuilder ConfigurePostgreSql(this IConfigurationBuilder builder, Action? configureAction) { - return ConfigurePostgreSql(builder, PostgreSqlPackageResolver.Default, configureAction); + return ConfigurePostgreSql(builder, PostgreSqlPackageResolver.Default, configureAction, null); } - private static IConfigurationBuilder ConfigurePostgreSql(this IConfigurationBuilder builder, PostgreSqlPackageResolver packageResolver, - Action? configureAction = null) + internal static IConfigurationBuilder ConfigurePostgreSql(this IConfigurationBuilder builder, PostgreSqlPackageResolver packageResolver, + Action? configureAction, IServiceBindingsReader? serviceBindingsReader) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(packageResolver); - ConnectorConfigurer.Configure(builder, configureAction, new PostgreSqlConnectionStringPostProcessor(packageResolver)); + Action overrideConfigureAction = options => + { + configureAction?.Invoke(options); + + options.CloudFoundryBrokerTypes = + options.SkipDefaultServiceBindings ? CloudFoundryServiceBrokerTypes.None : CloudFoundryServiceBrokerTypes.PostgreSql; + }; + + ConnectorConfigurer.Configure(builder, overrideConfigureAction, new PostgreSqlConnectionStringPostProcessor(packageResolver), serviceBindingsReader, + NullLoggerFactory.Instance); + return builder; } } diff --git a/src/Connectors/src/Connectors/PostgreSql/PostgreSqlHostApplicationBuilderExtensions.cs b/src/Connectors/src/Connectors/PostgreSql/PostgreSqlHostApplicationBuilderExtensions.cs index 76c4f70101..8f51288222 100644 --- a/src/Connectors/src/Connectors/PostgreSql/PostgreSqlHostApplicationBuilderExtensions.cs +++ b/src/Connectors/src/Connectors/PostgreSql/PostgreSqlHostApplicationBuilderExtensions.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Hosting; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; +using Steeltoe.Connectors.PostgreSql.DynamicTypeAccess; namespace Steeltoe.Connectors.PostgreSql; @@ -41,10 +43,16 @@ public static IHostApplicationBuilder AddPostgreSql(this IHostApplicationBuilder /// public static IHostApplicationBuilder AddPostgreSql(this IHostApplicationBuilder builder, Action? configureAction, Action? addAction) + { + return AddPostgreSql(builder, configureAction, addAction, null); + } + + internal static IHostApplicationBuilder AddPostgreSql(this IHostApplicationBuilder builder, Action? configureAction, + Action? addAction, IServiceBindingsReader? serviceBindingsReader) { ArgumentNullException.ThrowIfNull(builder); - builder.Configuration.ConfigurePostgreSql(configureAction); + builder.Configuration.ConfigurePostgreSql(PostgreSqlPackageResolver.Default, configureAction, serviceBindingsReader); builder.Services.AddPostgreSql(builder.Configuration, addAction); return builder; } diff --git a/src/Connectors/src/Connectors/PublicAPI.Unshipped.txt b/src/Connectors/src/Connectors/PublicAPI.Unshipped.txt index 7dc5c58110..9269e14767 100644 --- a/src/Connectors/src/Connectors/PublicAPI.Unshipped.txt +++ b/src/Connectors/src/Connectors/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Steeltoe.Connectors.ConnectorConfigureOptionsBuilder.SkipDefaultServiceBindings.get -> bool +Steeltoe.Connectors.ConnectorConfigureOptionsBuilder.SkipDefaultServiceBindings.set -> void diff --git a/src/Connectors/src/Connectors/RabbitMQ/RabbitMQConfigurationBuilderExtensions.cs b/src/Connectors/src/Connectors/RabbitMQ/RabbitMQConfigurationBuilderExtensions.cs index 41f6ecccd2..fd7f1e2b20 100644 --- a/src/Connectors/src/Connectors/RabbitMQ/RabbitMQConfigurationBuilderExtensions.cs +++ b/src/Connectors/src/Connectors/RabbitMQ/RabbitMQConfigurationBuilderExtensions.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging.Abstractions; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; namespace Steeltoe.Connectors.RabbitMQ; @@ -35,10 +37,26 @@ public static IConfigurationBuilder ConfigureRabbitMQ(this IConfigurationBuilder /// The incoming so that additional calls can be chained. /// public static IConfigurationBuilder ConfigureRabbitMQ(this IConfigurationBuilder builder, Action? configureAction) + { + return ConfigureRabbitMQ(builder, configureAction, null); + } + + internal static IConfigurationBuilder ConfigureRabbitMQ(this IConfigurationBuilder builder, Action? configureAction, + IServiceBindingsReader? serviceBindingsReader) { ArgumentNullException.ThrowIfNull(builder); - ConnectorConfigurer.Configure(builder, configureAction, new RabbitMQConnectionStringPostProcessor()); + Action overrideConfigureAction = options => + { + configureAction?.Invoke(options); + + options.CloudFoundryBrokerTypes = + options.SkipDefaultServiceBindings ? CloudFoundryServiceBrokerTypes.None : CloudFoundryServiceBrokerTypes.RabbitMQ; + }; + + ConnectorConfigurer.Configure(builder, overrideConfigureAction, new RabbitMQConnectionStringPostProcessor(), serviceBindingsReader, + NullLoggerFactory.Instance); + return builder; } } diff --git a/src/Connectors/src/Connectors/RabbitMQ/RabbitMQConnectionStringBuilder.cs b/src/Connectors/src/Connectors/RabbitMQ/RabbitMQConnectionStringBuilder.cs index 9fe7382933..ccc369197f 100644 --- a/src/Connectors/src/Connectors/RabbitMQ/RabbitMQConnectionStringBuilder.cs +++ b/src/Connectors/src/Connectors/RabbitMQ/RabbitMQConnectionStringBuilder.cs @@ -2,8 +2,11 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Web; +using Microsoft.AspNetCore.Http; namespace Steeltoe.Connectors.RabbitMQ; @@ -28,19 +31,18 @@ public object? this[string keyword] get { ArgumentException.ThrowIfNullOrWhiteSpace(keyword); - AssertIsKnownKeyword(keyword); if (string.Equals(keyword, KnownKeywords.Url, StringComparison.OrdinalIgnoreCase)) { return ConnectionString; } + // Allow getting unknown keyword. We don't pretend to know all valid query string parameters. return _settings.GetValueOrDefault(keyword); } set { ArgumentException.ThrowIfNullOrWhiteSpace(keyword); - AssertIsKnownKeyword(keyword); if (string.Equals(keyword, KnownKeywords.Url, StringComparison.OrdinalIgnoreCase)) { @@ -56,6 +58,7 @@ public object? this[string keyword] } else { + // Allow setting unknown keyword. We don't pretend to know all valid query string parameters. _settings[keyword] = stringValue; } } @@ -97,6 +100,15 @@ private string ToConnectionString() builder.Path = Uri.EscapeDataString(virtualHost); } + var queryString = default(QueryString); + + foreach ((string name, string value) in _settings.Where(pair => !KnownKeywords.Exists(pair.Key))) + { + queryString = queryString.Add(name, value); + } + + builder.Query = queryString.Value; + return builder.Uri.AbsoluteUri; } @@ -132,14 +144,18 @@ private void FromConnectionString(string? connectionString) { _settings[KnownKeywords.VirtualHost] = Uri.UnescapeDataString(uri.AbsolutePath[1..]); } - } - } - private static void AssertIsKnownKeyword(string keyword) - { - if (!KnownKeywords.Exists(keyword)) - { - throw new ArgumentException($"Keyword not supported: '{keyword}'.", nameof(keyword)); + NameValueCollection queryString = HttpUtility.ParseQueryString(uri.Query); + + foreach (string remainingKeyword in queryString.AllKeys.Where(key => key != null && !KnownKeywords.Exists(key)).Cast()) + { + string? value = queryString.Get(remainingKeyword); + + if (value != null) + { + _settings[remainingKeyword] = value; + } + } } } diff --git a/src/Connectors/src/Connectors/RabbitMQ/RabbitMQHostApplicationBuilderExtensions.cs b/src/Connectors/src/Connectors/RabbitMQ/RabbitMQHostApplicationBuilderExtensions.cs index 23a98f46b6..9a851e8947 100644 --- a/src/Connectors/src/Connectors/RabbitMQ/RabbitMQHostApplicationBuilderExtensions.cs +++ b/src/Connectors/src/Connectors/RabbitMQ/RabbitMQHostApplicationBuilderExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Hosting; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; namespace Steeltoe.Connectors.RabbitMQ; @@ -41,10 +42,16 @@ public static IHostApplicationBuilder AddRabbitMQ(this IHostApplicationBuilder b /// public static IHostApplicationBuilder AddRabbitMQ(this IHostApplicationBuilder builder, Action? configureAction, Action? addAction) + { + return AddRabbitMQ(builder, configureAction, addAction, null); + } + + internal static IHostApplicationBuilder AddRabbitMQ(this IHostApplicationBuilder builder, Action? configureAction, + Action? addAction, IServiceBindingsReader? serviceBindingsReader) { ArgumentNullException.ThrowIfNull(builder); - builder.Configuration.ConfigureRabbitMQ(configureAction); + builder.Configuration.ConfigureRabbitMQ(configureAction, serviceBindingsReader); builder.Services.AddRabbitMQ(builder.Configuration, addAction); return builder; } diff --git a/src/Connectors/src/Connectors/Redis/RedisConfigurationBuilderExtensions.cs b/src/Connectors/src/Connectors/Redis/RedisConfigurationBuilderExtensions.cs index 5fc8caf70b..f912a2f42d 100644 --- a/src/Connectors/src/Connectors/Redis/RedisConfigurationBuilderExtensions.cs +++ b/src/Connectors/src/Connectors/Redis/RedisConfigurationBuilderExtensions.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging.Abstractions; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; namespace Steeltoe.Connectors.Redis; @@ -35,10 +37,24 @@ public static IConfigurationBuilder ConfigureRedis(this IConfigurationBuilder bu /// The incoming so that additional calls can be chained. /// public static IConfigurationBuilder ConfigureRedis(this IConfigurationBuilder builder, Action? configureAction) + { + return ConfigureRedis(builder, configureAction, null); + } + + internal static IConfigurationBuilder ConfigureRedis(this IConfigurationBuilder builder, Action? configureAction, + IServiceBindingsReader? serviceBindingsReader) { ArgumentNullException.ThrowIfNull(builder); - ConnectorConfigurer.Configure(builder, configureAction, new RedisConnectionStringPostProcessor()); + Action overrideConfigureAction = options => + { + configureAction?.Invoke(options); + options.CloudFoundryBrokerTypes = options.SkipDefaultServiceBindings ? CloudFoundryServiceBrokerTypes.None : CloudFoundryServiceBrokerTypes.Redis; + }; + + ConnectorConfigurer.Configure(builder, overrideConfigureAction, new RedisConnectionStringPostProcessor(), serviceBindingsReader, + NullLoggerFactory.Instance); + return builder; } } diff --git a/src/Connectors/src/Connectors/Redis/RedisConnectionStringBuilder.cs b/src/Connectors/src/Connectors/Redis/RedisConnectionStringBuilder.cs index 8d791a0ebf..3b15fb52b9 100644 --- a/src/Connectors/src/Connectors/Redis/RedisConnectionStringBuilder.cs +++ b/src/Connectors/src/Connectors/Redis/RedisConnectionStringBuilder.cs @@ -29,14 +29,8 @@ public object? this[string keyword] { ArgumentException.ThrowIfNullOrWhiteSpace(keyword); - // Allow getting unknown keyword, if it was set earlier. We don't pretend to know all valid keywords. - if (_settings.TryGetValue(keyword, out string? value)) - { - return value; - } - - AssertIsKnownKeyword(keyword); - return null; + // Allow getting unknown keyword. We don't pretend to know all valid query string parameters. + return _settings.GetValueOrDefault(keyword); } set { @@ -50,7 +44,7 @@ public object? this[string keyword] } else { - // Allow setting unknown keyword. We don't pretend to know all valid keywords. + // Allow setting unknown keyword. We don't pretend to know all valid query string parameters. _settings[keyword] = stringValue; } } @@ -120,14 +114,6 @@ private void FromConnectionString(string? connectionString) } } - private static void AssertIsKnownKeyword(string keyword) - { - if (!KnownKeywords.Exists(keyword)) - { - throw new ArgumentException($"Keyword not supported: '{keyword}'.", nameof(keyword)); - } - } - private static class KnownKeywords { public const string Host = "host"; diff --git a/src/Connectors/src/Connectors/Redis/RedisHostApplicationBuilderExtensions.cs b/src/Connectors/src/Connectors/Redis/RedisHostApplicationBuilderExtensions.cs index db17d66be3..c60d508ebb 100644 --- a/src/Connectors/src/Connectors/Redis/RedisHostApplicationBuilderExtensions.cs +++ b/src/Connectors/src/Connectors/Redis/RedisHostApplicationBuilderExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Hosting; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; namespace Steeltoe.Connectors.Redis; @@ -46,10 +47,16 @@ public static IHostApplicationBuilder AddRedis(this IHostApplicationBuilder buil /// public static IHostApplicationBuilder AddRedis(this IHostApplicationBuilder builder, Action? configureAction, Action? addAction) + { + return AddRedis(builder, configureAction, addAction, null); + } + + internal static IHostApplicationBuilder AddRedis(this IHostApplicationBuilder builder, Action? configureAction, + Action? addAction, IServiceBindingsReader? serviceBindingsReader) { ArgumentNullException.ThrowIfNull(builder); - builder.Configuration.ConfigureRedis(configureAction); + builder.Configuration.ConfigureRedis(configureAction, serviceBindingsReader); builder.Services.AddRedis(builder.Configuration, addAction); return builder; } diff --git a/src/Connectors/src/Connectors/SqlServer/SqlServerConfigurationBuilderExtensions.cs b/src/Connectors/src/Connectors/SqlServer/SqlServerConfigurationBuilderExtensions.cs index 8aa20df6df..a3bd5092e8 100644 --- a/src/Connectors/src/Connectors/SqlServer/SqlServerConfigurationBuilderExtensions.cs +++ b/src/Connectors/src/Connectors/SqlServer/SqlServerConfigurationBuilderExtensions.cs @@ -3,7 +3,10 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging.Abstractions; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; using Steeltoe.Connectors.SqlServer.RuntimeTypeAccess; +using IServiceBindingsReader = Steeltoe.Configuration.CloudFoundry.ServiceBindings.IServiceBindingsReader; namespace Steeltoe.Connectors.SqlServer; @@ -20,7 +23,7 @@ public static class SqlServerConfigurationBuilderExtensions /// public static IConfigurationBuilder ConfigureSqlServer(this IConfigurationBuilder builder) { - return ConfigureSqlServer(builder, SqlServerPackageResolver.Default); + return ConfigureSqlServer(builder, null); } /// @@ -37,16 +40,26 @@ public static IConfigurationBuilder ConfigureSqlServer(this IConfigurationBuilde /// public static IConfigurationBuilder ConfigureSqlServer(this IConfigurationBuilder builder, Action? configureAction) { - return ConfigureSqlServer(builder, SqlServerPackageResolver.Default, configureAction); + return ConfigureSqlServer(builder, SqlServerPackageResolver.Default, configureAction, null); } internal static IConfigurationBuilder ConfigureSqlServer(this IConfigurationBuilder builder, SqlServerPackageResolver packageResolver, - Action? configureAction = null) + Action? configureAction, IServiceBindingsReader? serviceBindingsReader) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(packageResolver); - ConnectorConfigurer.Configure(builder, configureAction, new SqlServerConnectionStringPostProcessor(packageResolver)); + Action overrideConfigureAction = options => + { + configureAction?.Invoke(options); + + options.CloudFoundryBrokerTypes = + options.SkipDefaultServiceBindings ? CloudFoundryServiceBrokerTypes.None : CloudFoundryServiceBrokerTypes.SqlServer; + }; + + ConnectorConfigurer.Configure(builder, overrideConfigureAction, new SqlServerConnectionStringPostProcessor(packageResolver), serviceBindingsReader, + NullLoggerFactory.Instance); + return builder; } } diff --git a/src/Connectors/src/Connectors/SqlServer/SqlServerHostApplicationBuilderExtensions.cs b/src/Connectors/src/Connectors/SqlServer/SqlServerHostApplicationBuilderExtensions.cs index e47ff4a6ec..238e0c5aad 100644 --- a/src/Connectors/src/Connectors/SqlServer/SqlServerHostApplicationBuilderExtensions.cs +++ b/src/Connectors/src/Connectors/SqlServer/SqlServerHostApplicationBuilderExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Hosting; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; using Steeltoe.Connectors.SqlServer.RuntimeTypeAccess; namespace Steeltoe.Connectors.SqlServer; @@ -21,7 +22,7 @@ public static class SqlServerHostApplicationBuilderExtensions /// public static IHostApplicationBuilder AddSqlServer(this IHostApplicationBuilder builder) { - return AddSqlServer(builder, SqlServerPackageResolver.Default); + return AddSqlServer(builder, null, null); } /// @@ -43,16 +44,16 @@ public static IHostApplicationBuilder AddSqlServer(this IHostApplicationBuilder public static IHostApplicationBuilder AddSqlServer(this IHostApplicationBuilder builder, Action? configureAction, Action? addAction) { - return AddSqlServer(builder, SqlServerPackageResolver.Default, configureAction, addAction); + return AddSqlServer(builder, SqlServerPackageResolver.Default, configureAction, addAction, null); } internal static IHostApplicationBuilder AddSqlServer(this IHostApplicationBuilder builder, SqlServerPackageResolver packageResolver, - Action? configureAction = null, Action? addAction = null) + Action? configureAction, Action? addAction, IServiceBindingsReader? serviceBindingsReader) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(packageResolver); - builder.Configuration.ConfigureSqlServer(packageResolver, configureAction); + builder.Configuration.ConfigureSqlServer(packageResolver, configureAction, serviceBindingsReader); builder.Services.AddSqlServer(builder.Configuration, packageResolver, addAction); return builder; } diff --git a/src/Connectors/test/Connectors.Test/CustomBrokerTests.cs b/src/Connectors/test/Connectors.Test/CustomBrokerTests.cs new file mode 100644 index 0000000000..e30896fa1a --- /dev/null +++ b/src/Connectors/test/Connectors.Test/CustomBrokerTests.cs @@ -0,0 +1,266 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Steeltoe.Common.TestResources; +using Steeltoe.Configuration.CloudFoundry; +using Steeltoe.Configuration.CloudFoundry.ServiceBindings; +using Steeltoe.Connectors.PostgreSql; +using Steeltoe.Connectors.RabbitMQ; + +namespace Steeltoe.Connectors.Test; + +public sealed class CustomBrokerTests +{ + [Fact] + public async Task Binds_options_with_third_party_service_bindings() + { + var appSettings = new Dictionary + { + ["Steeltoe:Client:PostgreSql:products-db:ConnectionString"] = "Include Error Detail=true;host=localhost", + ["Steeltoe:Client:PostgreSql:orders-db:ConnectionString"] = "Log Parameters=true;port=9999" + }; + + var reader = new CloudFoundryMemorySettingsReader + { + ServicesJson = """ + { + "custom-postgres-broker": [ + { + "name": "products-db", + "credentials": { + "custom-hostname-key": "example.cloud.com", + "custom-port-key": 2345, + "custom-username-key": "products-user", + "custom-password-key": "products-secret", + "custom-database-name-key": "product-database", + "host": "IGNORED" + } + }, + { + "name": "orders-db", + "credentials": { + "custom-hostname-key": "example.cloud.com", + "custom-port-key": 2345, + "custom-username-key": "orders-user", + "custom-password-key": "orders-secret", + "custom-database-name-key": "order-database" + } + } + ] + } + """ + }; + + WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); + builder.Configuration.AddInMemoryCollection(appSettings); + builder.Configuration.AddCloudFoundry(reader); + MapServiceBindingsForCustomPostgreSqlBroker("custom-postgres-broker"); + builder.AddPostgreSql(options => options.SkipDefaultServiceBindings = true, null); + await using WebApplication app = builder.Build(); + + var optionsMonitor = app.Services.GetRequiredService>(); + PostgreSqlOptions productsDbOptions = optionsMonitor.Get("products-db"); + PostgreSqlOptions ordersDbOptions = optionsMonitor.Get("orders-db"); + + ExtractConnectionStringParameters(productsDbOptions.ConnectionString).Should().BeEquivalentTo(new List + { + "Include Error Detail=True", + "Host=example.cloud.com", + "Port=2345", + "Database=product-database", + "Username=products-user", + "Password=products-secret" + }, options => options.WithoutStrictOrdering()); + + ExtractConnectionStringParameters(ordersDbOptions.ConnectionString).Should().BeEquivalentTo(new List + { + "Log Parameters=True", + "Host=example.cloud.com", + "Port=2345", + "Database=order-database", + "Username=orders-user", + "Password=orders-secret" + }, options => options.WithoutStrictOrdering()); + + void MapServiceBindingsForCustomPostgreSqlBroker(string brokerName) + { + var options = builder.Configuration.GetSection("vcap").Get(); + + foreach (CloudFoundryService service in options?.Services.Where(pair => pair.Key == brokerName).SelectMany(pair => pair.Value) ?? []) + { + builder.Configuration.AddInMemoryCollection(new Dictionary + { + // Map credentials into the property names expected by NpgsqlConnectionStringBuilder. + [$"steeltoe:service-bindings:postgresql:{service.Name}:host"] = service.Credentials["custom-hostname-key"].Value, + [$"steeltoe:service-bindings:postgresql:{service.Name}:port"] = service.Credentials["custom-port-key"].Value, + [$"steeltoe:service-bindings:postgresql:{service.Name}:username"] = service.Credentials["custom-username-key"].Value, + [$"steeltoe:service-bindings:postgresql:{service.Name}:password"] = service.Credentials["custom-password-key"].Value, + [$"steeltoe:service-bindings:postgresql:{service.Name}:database"] = service.Credentials["custom-database-name-key"].Value + }); + } + } + } + + [Fact] + public async Task Third_party_service_bindings_can_be_combined_with_builtin() + { + var appSettings = new Dictionary + { + ["Steeltoe:Client:PostgreSql:products-db:ConnectionString"] = "Include Error Detail=true;host=localhost", + ["Steeltoe:Client:PostgreSql:orders-db:ConnectionString"] = "Log Parameters=true;port=9999", + ["Steeltoe:Client:RabbitMQ:transaction-queue:ConnectionString"] = "amqp://localhost?connection_timeout=5000&heartbeat=5&unknown=local" + }; + + const string vcapServicesJson = """ + { + "postgres": [ + { + "name": "products-db", + "credentials": { + "custom-hostname-key": "example.cloud.com", + "custom-port-key": 2345, + "custom-username-key": "products-user", + "custom-password-key": "products-secret", + "custom-database-name-key": "product-database" + } + }, + { + "name": "orders-db", + "credentials": { + "custom-hostname-key": "example.cloud.com", + "custom-port-key": 2345, + "custom-username-key": "orders-user", + "custom-password-key": "orders-secret", + "custom-database-name-key": "order-database" + } + } + ], + "p.rabbitmq": [ + { + "name": "transaction-queue", + "tags": [ + "rabbitmq" + ], + "credentials": { + "protocols": { + "amqp+ssl": { + "host": "messaging.cloud.com", + "port": 2765, + "username": "app-user", + "password": "secret", + "vhost": "transactions" + } + }, + "ssl": true, + "unknown": "remote" + } + } + ] + } + """; + + var reader = new CloudFoundryMemorySettingsReader + { + ServicesJson = vcapServicesJson + }; + + WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); + builder.Configuration.AddInMemoryCollection(appSettings); + builder.Configuration.AddCloudFoundry(reader); + MapServiceBindingsForCustomPostgreSqlBroker("postgres"); + builder.AddPostgreSql(options => options.SkipDefaultServiceBindings = true, null); + MapExtraRabbitMQServiceBindingsUsingDefaultBroker("p.rabbitmq"); + builder.AddRabbitMQ(null, null, new StringServiceBindingsReader(vcapServicesJson)); + await using WebApplication app = builder.Build(); + + var postgreSqlOptionsMonitor = app.Services.GetRequiredService>(); + PostgreSqlOptions productsDbOptions = postgreSqlOptionsMonitor.Get("products-db"); + PostgreSqlOptions ordersDbOptions = postgreSqlOptionsMonitor.Get("orders-db"); + + var rabbitMQOptionsMonitor = app.Services.GetRequiredService>(); + RabbitMQOptions transactionQueueOptions = rabbitMQOptionsMonitor.Get("transaction-queue"); + + ExtractConnectionStringParameters(productsDbOptions.ConnectionString).Should().BeEquivalentTo(new List + { + "Include Error Detail=True", + "Host=example.cloud.com", + "Port=2345", + "Database=product-database", + "Username=products-user", + "Password=products-secret" + }, options => options.WithoutStrictOrdering()); + + ExtractConnectionStringParameters(ordersDbOptions.ConnectionString).Should().BeEquivalentTo(new List + { + "Log Parameters=True", + "Host=example.cloud.com", + "Port=2345", + "Database=order-database", + "Username=orders-user", + "Password=orders-secret" + }, options => options.WithoutStrictOrdering()); + + transactionQueueOptions.ConnectionString.Should().Be( + "amqps://app-user:secret@messaging.cloud.com:2765/transactions?connection_timeout=5000&heartbeat=5&unknown=remote"); + + void MapServiceBindingsForCustomPostgreSqlBroker(string brokerName) + { + var options = builder.Configuration.GetSection("vcap").Get(); + + foreach (CloudFoundryService service in options?.Services.Where(pair => pair.Key == brokerName).SelectMany(pair => pair.Value) ?? []) + { + builder.Configuration.AddInMemoryCollection(new Dictionary + { + // Map third-party credentials into the property names expected by NpgsqlConnectionStringBuilder. + [$"steeltoe:service-bindings:postgresql:{service.Name}:host"] = service.Credentials["custom-hostname-key"].Value, + [$"steeltoe:service-bindings:postgresql:{service.Name}:port"] = service.Credentials["custom-port-key"].Value, + [$"steeltoe:service-bindings:postgresql:{service.Name}:username"] = service.Credentials["custom-username-key"].Value, + [$"steeltoe:service-bindings:postgresql:{service.Name}:password"] = service.Credentials["custom-password-key"].Value, + [$"steeltoe:service-bindings:postgresql:{service.Name}:database"] = service.Credentials["custom-database-name-key"].Value + }); + } + } + + void MapExtraRabbitMQServiceBindingsUsingDefaultBroker(string brokerName) + { + var options = builder.Configuration.GetSection("vcap").Get(); + + foreach (CloudFoundryService service in options?.Services.Where(pair => pair.Key == brokerName).SelectMany(pair => pair.Value) ?? []) + { + builder.Configuration.AddInMemoryCollection(new Dictionary + { + // Map third-party 'unknown' credential, in addition to built-in broker parameters. + [$"steeltoe:service-bindings:rabbitmq:{service.Name}:unknown"] = service.Credentials["unknown"].Value + }); + } + } + } + + private static List ExtractConnectionStringParameters(string? connectionString) + { + List entries = []; + + if (connectionString != null) + { + foreach (string parameter in connectionString.Split(';')) + { + string[] nameValuePair = parameter.Split('=', 2); + + if (nameValuePair.Length == 2) + { + string name = nameValuePair[0]; + string value = nameValuePair[1]; + + entries.Add($"{name}={value}"); + } + } + } + + return entries; + } +} diff --git a/src/Connectors/test/Connectors.Test/MongoDb/MongoDbConnectionStringBuilderTest.cs b/src/Connectors/test/Connectors.Test/MongoDb/MongoDbConnectionStringBuilderTest.cs index 7c10250f61..a5d339489d 100644 --- a/src/Connectors/test/Connectors.Test/MongoDb/MongoDbConnectionStringBuilderTest.cs +++ b/src/Connectors/test/Connectors.Test/MongoDb/MongoDbConnectionStringBuilderTest.cs @@ -82,13 +82,13 @@ public void Returns_null_when_getting_known_keyword() } [Fact] - public void Throws_when_getting_unknown_keyword() + public void Returns_null_when_getting_unknown_keyword() { var builder = new MongoDbConnectionStringBuilder(); - Action action = () => _ = builder["bad"]; + object? some = builder["some"]; - action.Should().ThrowExactly().WithMessage("Keyword not supported: 'bad'.*"); + some.Should().BeNull(); } [Fact] diff --git a/src/Connectors/test/Connectors.Test/MongoDb/MongoDbConnectorTest.cs b/src/Connectors/test/Connectors.Test/MongoDb/MongoDbConnectorTest.cs index d9935f8902..96765a8d07 100644 --- a/src/Connectors/test/Connectors.Test/MongoDb/MongoDbConnectorTest.cs +++ b/src/Connectors/test/Connectors.Test/MongoDb/MongoDbConnectorTest.cs @@ -148,9 +148,8 @@ public async Task Binds_options_with_CloudFoundry_service_bindings() }; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(MultiVcapServicesJson)); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMongoDb(); + builder.AddMongoDb(null, null, new StringServiceBindingsReader(MultiVcapServicesJson)); await using WebApplication app = builder.Build(); var optionsMonitor = app.Services.GetRequiredService>(); @@ -158,7 +157,7 @@ public async Task Binds_options_with_CloudFoundry_service_bindings() MongoDbOptions optionsOne = optionsMonitor.Get("myMongoDbServiceOne"); optionsOne.ConnectionString.Should().Be( - "mongodb://csb0230eada-2354-4c73-b3e4-8a1aaa996894:AiNtEyASbdXR5neJmTStMzKGItX2xvKuyEkcy65rviKD0ggZR19E1iVFIJ5ZAIY1xvvAiS5tOXsmACDbKDJIhQ%3D%3D@csb0230eada-2354-4c73-b3e4-8a1aaa996894.mongo.cosmos.cloud-hostname.com:10255/csb-db0230eada-2354-4c73-b3e4-8a1aaa996894?connectTimeoutMS=5000&ssl=true&replicaSet=globaldb&retrywrites=false&maxIdleTimeMS=120000&appName=@csb0230eada-2354-4c73-b3e4-8a1aaa996894@"); + "mongodb://csb0230eada-2354-4c73-b3e4-8a1aaa996894:AiNtEyASbdXR5neJmTStMzKGItX2xvKuyEkcy65rviKD0ggZR19E1iVFIJ5ZAIY1xvvAiS5tOXsmACDbKDJIhQ%3D%3D@csb0230eada-2354-4c73-b3e4-8a1aaa996894.mongo.cosmos.cloud-hostname.com:10255/csb-db0230eada-2354-4c73-b3e4-8a1aaa996894?ssl=true&replicaSet=globaldb&retrywrites=false&maxIdleTimeMS=120000&appName=@csb0230eada-2354-4c73-b3e4-8a1aaa996894@"); optionsOne.Database.Should().Be("csb-db0230eada-2354-4c73-b3e4-8a1aaa996894"); @@ -270,8 +269,7 @@ .. app.Services.GetServices().Should().HaveCount(2).And.AllB public async Task Registers_default_connection_string_when_only_single_server_binding_found() { WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(SingleVcapServicesJson)); - builder.AddMongoDb(); + builder.AddMongoDb(null, null, new StringServiceBindingsReader(SingleVcapServicesJson)); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); diff --git a/src/Connectors/test/Connectors.Test/MySql/MySqlConnector/MySqlConnectorTest.cs b/src/Connectors/test/Connectors.Test/MySql/MySqlConnector/MySqlConnectorTest.cs index 4aec658cbf..29a17920e0 100644 --- a/src/Connectors/test/Connectors.Test/MySql/MySqlConnector/MySqlConnectorTest.cs +++ b/src/Connectors/test/Connectors.Test/MySql/MySqlConnector/MySqlConnectorTest.cs @@ -115,7 +115,7 @@ public async Task Binds_options_without_service_bindings() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly); + builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly, null, null, null); builder.Services.Configure("myMySqlServiceOne", options => options.ConnectionString += ";Use Compression=false"); await using WebApplication app = builder.Build(); @@ -153,9 +153,8 @@ public async Task Binds_options_with_CloudFoundry_service_bindings() }; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(MultiVcapServicesJson)); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly); + builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly, null, null, new StringServiceBindingsReader(MultiVcapServicesJson)); await using WebApplication app = builder.Build(); var optionsMonitor = app.Services.GetRequiredService>(); @@ -235,7 +234,7 @@ public async Task Registers_ConnectorFactory() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly); + builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly, null, null, null); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); @@ -262,7 +261,7 @@ public async Task Registers_HealthContributors() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly); + builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly, null, null, null); await using WebApplication app = builder.Build(); RelationalDatabaseHealthContributor[] contributors = @@ -283,8 +282,7 @@ .. app.Services.GetServices().Should().HaveCount(2).And.AllB public async Task Registers_default_connection_string_when_only_single_server_binding_found() { WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(SingleVcapServicesJson)); - builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly); + builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly, null, null, new StringServiceBindingsReader(SingleVcapServicesJson)); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); @@ -312,7 +310,7 @@ public async Task Registers_default_connection_string_when_only_default_client_b WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly); + builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly, null, null, null); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); diff --git a/src/Connectors/test/Connectors.Test/MySql/Oracle/MySqlConnectorTest.cs b/src/Connectors/test/Connectors.Test/MySql/Oracle/MySqlConnectorTest.cs index 05f9d0696c..144e1bd47a 100644 --- a/src/Connectors/test/Connectors.Test/MySql/Oracle/MySqlConnectorTest.cs +++ b/src/Connectors/test/Connectors.Test/MySql/Oracle/MySqlConnectorTest.cs @@ -115,7 +115,7 @@ public async Task Binds_options_without_service_bindings() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.OracleOnly); + builder.AddMySql(MySqlPackageResolver.OracleOnly, null, null, null); builder.Services.Configure("myMySqlServiceOne", options => options.ConnectionString += ";Use Compression=false"); await using WebApplication app = builder.Build(); @@ -153,9 +153,8 @@ public async Task Binds_options_with_CloudFoundry_service_bindings() }; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(MultiVcapServicesJson)); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.OracleOnly); + builder.AddMySql(MySqlPackageResolver.OracleOnly, null, null, new StringServiceBindingsReader(MultiVcapServicesJson)); await using WebApplication app = builder.Build(); var optionsMonitor = app.Services.GetRequiredService>(); @@ -235,7 +234,7 @@ public async Task Registers_ConnectorFactory() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.OracleOnly); + builder.AddMySql(MySqlPackageResolver.OracleOnly, null, null, null); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); @@ -262,7 +261,7 @@ public async Task Registers_HealthContributors() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.OracleOnly); + builder.AddMySql(MySqlPackageResolver.OracleOnly, null, null, null); await using WebApplication app = builder.Build(); RelationalDatabaseHealthContributor[] contributors = @@ -283,8 +282,7 @@ .. app.Services.GetServices().Should().HaveCount(2).And.AllB public async Task Registers_default_connection_string_when_only_single_server_binding_found() { WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(SingleVcapServicesJson)); - builder.AddMySql(MySqlPackageResolver.OracleOnly); + builder.AddMySql(MySqlPackageResolver.OracleOnly, null, null, new StringServiceBindingsReader(SingleVcapServicesJson)); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); @@ -312,7 +310,7 @@ public async Task Registers_default_connection_string_when_only_default_client_b WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.OracleOnly); + builder.AddMySql(MySqlPackageResolver.OracleOnly, null, null, null); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); diff --git a/src/Connectors/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTest.cs b/src/Connectors/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTest.cs index 9a158bb430..8ab17a04ee 100644 --- a/src/Connectors/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTest.cs +++ b/src/Connectors/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTest.cs @@ -232,9 +232,8 @@ public async Task Binds_options_with_CloudFoundry_service_bindings() }; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(MultiVcapServicesJson)); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddPostgreSql(); + builder.AddPostgreSql(null, null, new StringServiceBindingsReader(MultiVcapServicesJson)); await using WebApplication app = builder.Build(); var optionsMonitor = app.Services.GetRequiredService>(); @@ -461,7 +460,7 @@ public async Task Skips_HealthContributors_when_AspNetCore_health_checks_are_reg WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); builder.Services.AddHealthChecks(); - builder.AddPostgreSql(null, null); + builder.AddPostgreSql(); await using WebApplication app = builder.Build(); app.Services.GetServices().Should().BeEmpty(); @@ -493,9 +492,8 @@ public async Task Registers_default_connection_string_when_single_server_binding }; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(SingleVcapServicesJson)); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddPostgreSql(); + builder.AddPostgreSql(null, null, new StringServiceBindingsReader(SingleVcapServicesJson)); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); @@ -526,8 +524,7 @@ public async Task Registers_default_connection_string_when_single_server_binding public async Task Registers_default_connection_string_when_only_single_server_binding_found() { WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(SingleVcapServicesJson)); - builder.AddPostgreSql(); + builder.AddPostgreSql(null, null, new StringServiceBindingsReader(SingleVcapServicesJson)); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); @@ -624,9 +621,8 @@ public async Task Registers_no_default_connection_string_when_multiple_server_bi }; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(MultiVcapServicesJson)); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddPostgreSql(); + builder.AddPostgreSql(null, null, new StringServiceBindingsReader(MultiVcapServicesJson)); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); @@ -652,9 +648,8 @@ public async Task Registers_no_default_connection_string_when_single_server_bind }; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(SingleVcapServicesJson)); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddPostgreSql(); + builder.AddPostgreSql(null, null, new StringServiceBindingsReader(SingleVcapServicesJson)); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); @@ -677,9 +672,8 @@ public async Task Registers_no_default_connection_string_when_service_and_client }; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(SingleVcapServicesJson)); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddPostgreSql(); + builder.AddPostgreSql(null, null, new StringServiceBindingsReader(SingleVcapServicesJson)); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); diff --git a/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQConnectionStringBuilderTest.cs b/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQConnectionStringBuilderTest.cs index a82c343c9a..4dc3cb7800 100644 --- a/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQConnectionStringBuilderTest.cs +++ b/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQConnectionStringBuilderTest.cs @@ -41,6 +41,21 @@ public void Decodes_properties_with_special_characters() builder["virtualHost"].Should().Be("my virtual= host"); } + [Fact] + public void Preserves_query_string_parameters_when_setting_URL() + { + var builder = new RabbitMQConnectionStringBuilder + { + ConnectionString = "amqps://localhost:999/virtual-host-1?first=one&second=two" + }; + + const string url = "amqps://localhost:999/virtual-host-1?first=one&second=number2"; + builder["url"] = url; + + builder.ConnectionString.Should().Be("amqps://localhost:999/virtual-host-1?first=one&second=number2"); + builder["url"].Should().Be(builder.ConnectionString); + } + [Fact] public void Returns_null_when_getting_known_keyword() { @@ -52,22 +67,24 @@ public void Returns_null_when_getting_known_keyword() } [Fact] - public void Throws_when_getting_unknown_keyword() + public void Returns_null_when_getting_unknown_keyword() { var builder = new RabbitMQConnectionStringBuilder(); - Action action = () => _ = builder["bad"]; + object? some = builder["some"]; - action.Should().ThrowExactly().WithMessage("Keyword not supported: 'bad'.*"); + some.Should().BeNull(); } [Fact] - public void Throws_when_setting_unknown_keyword() + public void Can_get_unknown_keyword_that_was_set_earlier() { - var builder = new RabbitMQConnectionStringBuilder(); - - Action action = () => builder["bad"] = "some"; + var builder = new RabbitMQConnectionStringBuilder + { + ["some"] = "other" + }; - action.Should().ThrowExactly().WithMessage("Keyword not supported: 'bad'.*"); + object? value = builder["some"]; + value.Should().Be("other"); } } diff --git a/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQConnectorTest.cs b/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQConnectorTest.cs index ebca84c1fe..6c944dc0b1 100644 --- a/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQConnectorTest.cs +++ b/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQConnectorTest.cs @@ -190,8 +190,8 @@ public async Task Binds_options_without_service_bindings() { var appSettings = new Dictionary { - ["Steeltoe:Client:RabbitMQ:myRabbitMQServiceOne:ConnectionString"] = "amqp://user1:pass1@host1:5672/virtual-host-1", - ["Steeltoe:Client:RabbitMQ:myRabbitMQServiceTwo:ConnectionString"] = "amqps://user2:pass2@host2:5672/virtual-host-2" + ["Steeltoe:Client:RabbitMQ:myRabbitMQServiceOne:ConnectionString"] = "amqp://user1:pass1@host1:5672/virtual-host-1?heartbeat=5", + ["Steeltoe:Client:RabbitMQ:myRabbitMQServiceTwo:ConnectionString"] = "amqps://user2:pass2@host2:5672/virtual-host-2?connection_timeout=5000" }; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); @@ -203,10 +203,10 @@ public async Task Binds_options_without_service_bindings() var optionsSnapshot = scope.ServiceProvider.GetRequiredService>(); RabbitMQOptions optionsOne = optionsSnapshot.Get("myRabbitMQServiceOne"); - optionsOne.ConnectionString.Should().Be("amqp://user1:pass1@host1:5672/virtual-host-1"); + optionsOne.ConnectionString.Should().Be("amqp://user1:pass1@host1:5672/virtual-host-1?heartbeat=5"); RabbitMQOptions optionsTwo = optionsSnapshot.Get("myRabbitMQServiceTwo"); - optionsTwo.ConnectionString.Should().Be("amqps://user2:pass2@host2:5672/virtual-host-2"); + optionsTwo.ConnectionString.Should().Be("amqps://user2:pass2@host2:5672/virtual-host-2?connection_timeout=5000"); } [Fact] @@ -214,13 +214,12 @@ public async Task Binds_options_with_CloudFoundry_service_bindings() { var appSettings = new Dictionary { - ["Steeltoe:Client:RabbitMQ:myRabbitMQServiceOne:ConnectionString"] = "amqps://user:pass@localhost:5672" + ["Steeltoe:Client:RabbitMQ:myRabbitMQServiceOne:ConnectionString"] = "amqps://user:pass@localhost:5672?connection_timeout=5000&heartbeat=5" }; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(MultiVcapServicesJson)); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddRabbitMQ(); + builder.AddRabbitMQ(null, null, new StringServiceBindingsReader(MultiVcapServicesJson)); await using WebApplication app = builder.Build(); var optionsMonitor = app.Services.GetRequiredService>(); @@ -228,7 +227,7 @@ public async Task Binds_options_with_CloudFoundry_service_bindings() RabbitMQOptions optionsOne = optionsMonitor.Get("myRabbitMQServiceOne"); optionsOne.ConnectionString.Should().Be( - "amqp://d2fd2c9d-ef84-406b-8401-f2ffacaafda6:AqntL6IwehKOGssE51psrJYd@q-s0.rabbitmq-server.benicia-services-subnet.service-instance-377d9d72-e951-4a1c-82e8-99c3c4933368.bosh:5672/377d9d72-e951-4a1c-82e8-99c3c4933368"); + "amqp://d2fd2c9d-ef84-406b-8401-f2ffacaafda6:AqntL6IwehKOGssE51psrJYd@q-s0.rabbitmq-server.benicia-services-subnet.service-instance-377d9d72-e951-4a1c-82e8-99c3c4933368.bosh:5672/377d9d72-e951-4a1c-82e8-99c3c4933368?connection_timeout=5000&heartbeat=5"); RabbitMQOptions optionsTwo = optionsMonitor.Get("myRabbitMQServiceTwo"); @@ -371,7 +370,6 @@ public async Task Can_connect_to_running_server() public async Task Registers_default_connection_string_when_only_single_server_binding_found() { WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(SingleVcapServicesJson)); builder.AddRabbitMQ(null, addOptions => { @@ -382,7 +380,7 @@ public async Task Registers_default_connection_string_when_only_single_server_bi return new FakeConnection(options.ConnectionString); }; - }); + }, new StringServiceBindingsReader(SingleVcapServicesJson)); await using WebApplication app = builder.Build(); diff --git a/src/Connectors/test/Connectors.Test/Redis/RedisConnectionStringBuilderTest.cs b/src/Connectors/test/Connectors.Test/Redis/RedisConnectionStringBuilderTest.cs index f04440d3ed..e6dfa388fc 100644 --- a/src/Connectors/test/Connectors.Test/Redis/RedisConnectionStringBuilderTest.cs +++ b/src/Connectors/test/Connectors.Test/Redis/RedisConnectionStringBuilderTest.cs @@ -46,13 +46,13 @@ public void Returns_null_when_getting_known_keyword() } [Fact] - public void Throws_when_getting_unknown_keyword() + public void Returns_null_when_getting_unknown_keyword() { var builder = new RedisConnectionStringBuilder(); - Action action = () => _ = builder["bad"]; + object? some = builder["some"]; - action.Should().ThrowExactly().WithMessage("Keyword not supported: 'bad'.*"); + some.Should().BeNull(); } [Fact] diff --git a/src/Connectors/test/Connectors.Test/Redis/RedisConnectorTest.cs b/src/Connectors/test/Connectors.Test/Redis/RedisConnectorTest.cs index afd08db3f1..5d5e23f609 100644 --- a/src/Connectors/test/Connectors.Test/Redis/RedisConnectorTest.cs +++ b/src/Connectors/test/Connectors.Test/Redis/RedisConnectorTest.cs @@ -131,9 +131,8 @@ public async Task Binds_options_with_CloudFoundry_service_bindings() }; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(MultiVcapServicesJson)); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddRedis(); + builder.AddRedis(null, null, new StringServiceBindingsReader(MultiVcapServicesJson)); await using WebApplication app = builder.Build(); var optionsMonitor = app.Services.GetRequiredService>(); @@ -308,7 +307,6 @@ .. app.Services.GetServices().Should().HaveCount(2).And.AllB public async Task Registers_default_connection_string_when_only_single_server_binding_found() { WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(SingleVcapServicesJson)); builder.AddRedis(null, addOptions => { @@ -319,7 +317,7 @@ public async Task Registers_default_connection_string_when_only_single_server_bi return GetMockedConnectionMultiplexer(options.ConnectionString); }; - }); + }, new StringServiceBindingsReader(SingleVcapServicesJson)); await using WebApplication app = builder.Build(); diff --git a/src/Connectors/test/Connectors.Test/SqlServer/MicrosoftData/SqlServerConnectorTest.cs b/src/Connectors/test/Connectors.Test/SqlServer/MicrosoftData/SqlServerConnectorTest.cs index 506646654f..35ed4b9ad2 100644 --- a/src/Connectors/test/Connectors.Test/SqlServer/MicrosoftData/SqlServerConnectorTest.cs +++ b/src/Connectors/test/Connectors.Test/SqlServer/MicrosoftData/SqlServerConnectorTest.cs @@ -147,7 +147,7 @@ public async Task Binds_options_without_service_bindings() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly, null, null, null); builder.Services.Configure("mySqlServerServiceOne", options => options.ConnectionString += ";Encrypt=false"); await using WebApplication app = builder.Build(); @@ -185,9 +185,8 @@ public async Task Binds_options_with_CloudFoundry_service_bindings() }; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(MultiVcapServicesJson)); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly, null, null, new StringServiceBindingsReader(MultiVcapServicesJson)); await using WebApplication app = builder.Build(); var optionsMonitor = app.Services.GetRequiredService>(); @@ -225,7 +224,7 @@ public async Task Registers_ConnectorFactory() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly, null, null, null); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); @@ -252,7 +251,7 @@ public async Task Registers_HealthContributors() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly, null, null, null); await using WebApplication app = builder.Build(); RelationalDatabaseHealthContributor[] contributors = @@ -273,8 +272,7 @@ .. app.Services.GetServices().Should().HaveCount(2).And.AllB public async Task Registers_default_connection_string_when_only_single_server_binding_found() { WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(SingleVcapServicesJson)); - builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly, null, null, new StringServiceBindingsReader(SingleVcapServicesJson)); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); @@ -302,7 +300,7 @@ public async Task Registers_default_connection_string_when_only_default_client_b WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly, null, null, null); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); diff --git a/src/Connectors/test/Connectors.Test/SqlServer/SystemData/SqlServerConnectorTest.cs b/src/Connectors/test/Connectors.Test/SqlServer/SystemData/SqlServerConnectorTest.cs index 42972a6339..3dd16da833 100644 --- a/src/Connectors/test/Connectors.Test/SqlServer/SystemData/SqlServerConnectorTest.cs +++ b/src/Connectors/test/Connectors.Test/SqlServer/SystemData/SqlServerConnectorTest.cs @@ -149,7 +149,7 @@ public async Task Binds_options_without_service_bindings() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddSqlServer(SqlServerPackageResolver.SystemDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.SystemDataOnly, null, null, null); builder.Services.Configure("mySqlServerServiceOne", options => options.ConnectionString += ";Encrypt=false"); await using WebApplication app = builder.Build(); @@ -187,9 +187,8 @@ public async Task Binds_options_with_CloudFoundry_service_bindings() }; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(MultiVcapServicesJson)); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddSqlServer(SqlServerPackageResolver.SystemDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.SystemDataOnly, null, null, new StringServiceBindingsReader(MultiVcapServicesJson)); await using WebApplication app = builder.Build(); var optionsMonitor = app.Services.GetRequiredService>(); @@ -227,7 +226,7 @@ public async Task Registers_ConnectorFactory() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddSqlServer(SqlServerPackageResolver.SystemDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.SystemDataOnly, null, null, null); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); @@ -254,7 +253,7 @@ public async Task Registers_HealthContributors() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddSqlServer(SqlServerPackageResolver.SystemDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.SystemDataOnly, null, null, null); await using WebApplication app = builder.Build(); RelationalDatabaseHealthContributor[] contributors = @@ -275,8 +274,7 @@ .. app.Services.GetServices().Should().HaveCount(2).And.AllB public async Task Registers_default_connection_string_when_only_single_server_binding_found() { WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.Configuration.AddCloudFoundryServiceBindings(new StringServiceBindingsReader(SingleVcapServicesJson)); - builder.AddSqlServer(SqlServerPackageResolver.SystemDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.SystemDataOnly, null, null, new StringServiceBindingsReader(SingleVcapServicesJson)); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); @@ -304,7 +302,7 @@ public async Task Registers_default_connection_string_when_only_default_client_b WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddSqlServer(SqlServerPackageResolver.SystemDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.SystemDataOnly, null, null, null); await using WebApplication app = builder.Build(); var connectorFactory = app.Services.GetRequiredService>(); diff --git a/src/Connectors/test/EntityFrameworkCore.Test/MySql/Oracle/MySqlDbContextOptionsBuilderExtensionsTest.cs b/src/Connectors/test/EntityFrameworkCore.Test/MySql/Oracle/MySqlDbContextOptionsBuilderExtensionsTest.cs index bbab683b37..735addf178 100644 --- a/src/Connectors/test/EntityFrameworkCore.Test/MySql/Oracle/MySqlDbContextOptionsBuilderExtensionsTest.cs +++ b/src/Connectors/test/EntityFrameworkCore.Test/MySql/Oracle/MySqlDbContextOptionsBuilderExtensionsTest.cs @@ -26,7 +26,7 @@ public async Task Registers_connection_string_for_default_service_binding() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.OracleOnly); + builder.AddMySql(MySqlPackageResolver.OracleOnly, null, null, null); builder.Services.Configure(options => options.ConnectionString += ";Use Compression=false"); builder.Services.AddDbContext((serviceProvider, options) => SteeltoeExtensions.UseMySql(options, serviceProvider, @@ -51,7 +51,7 @@ public async Task Registers_connection_string_for_named_service_binding() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.OracleOnly); + builder.AddMySql(MySqlPackageResolver.OracleOnly, null, null, null); builder.Services.Configure("myMySqlService", options => options.ConnectionString += ";Use Compression=false"); builder.Services.AddDbContext((serviceProvider, options) => SteeltoeExtensions.UseMySql(options, serviceProvider, diff --git a/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs b/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs index 17a40ea71e..7e8e974935 100644 --- a/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs +++ b/src/Connectors/test/EntityFrameworkCore.Test/MySql/Pomelo/MySqlDbContextOptionsBuilderExtensionsTest.cs @@ -30,7 +30,7 @@ public async Task Registers_connection_string_for_default_service_binding() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly); + builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly, null, null, null); builder.Services.Configure(options => options.ConnectionString += ";Use Compression=false"); builder.Services.AddDbContext((serviceProvider, options) => SteeltoeExtensions.UseMySql(options, serviceProvider, @@ -60,7 +60,7 @@ public async Task Registers_connection_string_for_named_service_binding() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly); + builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly, null, null, null); builder.Services.Configure("myMySqlService", options => options.ConnectionString += ";Use Compression=false"); builder.Services.AddDbContext((serviceProvider, options) => SteeltoeExtensions.UseMySql(options, serviceProvider, @@ -80,7 +80,7 @@ public async Task Registers_connection_string_for_named_service_binding() public async Task Throws_for_missing_connection_string_with_version_detection() { WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); - builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly); + builder.AddMySql(MySqlPackageResolver.MySqlConnectorOnly, null, null, null); builder.Services.AddDbContext((serviceProvider, options) => SteeltoeExtensions.UseMySql(options, serviceProvider, MySqlEntityFrameworkCorePackageResolver.PomeloOnly)); diff --git a/src/Connectors/test/EntityFrameworkCore.Test/SqlServer/SqlServerDbContextOptionsBuilderExtensionsTest.cs b/src/Connectors/test/EntityFrameworkCore.Test/SqlServer/SqlServerDbContextOptionsBuilderExtensionsTest.cs index fe02b0faaa..ab6df7b404 100644 --- a/src/Connectors/test/EntityFrameworkCore.Test/SqlServer/SqlServerDbContextOptionsBuilderExtensionsTest.cs +++ b/src/Connectors/test/EntityFrameworkCore.Test/SqlServer/SqlServerDbContextOptionsBuilderExtensionsTest.cs @@ -25,7 +25,7 @@ public async Task Registers_connection_string_for_default_service_binding() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly, null, null, null); builder.Services.Configure(options => options.ConnectionString += ";Encrypt=false"); builder.Services.AddDbContext((serviceProvider, options) => options.UseSqlServer(serviceProvider)); await using WebApplication app = builder.Build(); @@ -47,7 +47,7 @@ public async Task Registers_connection_string_for_named_service_binding() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddInMemoryCollection(appSettings); - builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly); + builder.AddSqlServer(SqlServerPackageResolver.MicrosoftDataOnly, null, null, null); builder.Services.Configure("mySqlServerService", options => options.ConnectionString += ";Encrypt=false"); builder.Services.AddDbContext((serviceProvider, options) => options.UseSqlServer(serviceProvider, "mySqlServerService")); await using WebApplication app = builder.Build(); diff --git a/src/Discovery/test/Eureka.Test/CloudFoundryTest.cs b/src/Discovery/test/Eureka.Test/CloudFoundryTest.cs index 9a86f5a7a4..d0b5129b8b 100644 --- a/src/Discovery/test/Eureka.Test/CloudFoundryTest.cs +++ b/src/Discovery/test/Eureka.Test/CloudFoundryTest.cs @@ -75,7 +75,7 @@ public async Task NoVCAPEnvVariables_ConfiguresEurekaDiscovery_Correctly() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddJsonStream(stream); builder.AddCloudFoundryConfiguration(); - builder.Configuration.AddCloudFoundryServiceBindings(); + builder.Configuration.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.Eureka); builder.Services.AddEurekaDiscoveryClient(); await using WebApplication app = builder.Build(); @@ -269,7 +269,7 @@ public async Task WithVCAPEnvVariables_HostName_ConfiguresEurekaDiscovery_Correc WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddJsonStream(stream); builder.AddCloudFoundryConfiguration(); - builder.Configuration.AddCloudFoundryServiceBindings(); + builder.Configuration.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.Eureka); builder.Services.AddEurekaDiscoveryClient(); await using WebApplication app = builder.Build(); @@ -464,7 +464,7 @@ public async Task WithVCAPEnvVariables_Route_ConfiguresEurekaDiscovery_Correctly WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddJsonStream(stream); builder.AddCloudFoundryConfiguration(); - builder.Configuration.AddCloudFoundryServiceBindings(); + builder.Configuration.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.Eureka); builder.Services.AddEurekaDiscoveryClient(); await using WebApplication app = builder.Build(); @@ -661,7 +661,7 @@ public async Task WithVCAPEnvVariables_AppName_Overrides_VCAPBinding() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddJsonStream(stream); builder.AddCloudFoundryConfiguration(); - builder.Configuration.AddCloudFoundryServiceBindings(); + builder.Configuration.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.Eureka); builder.Services.AddEurekaDiscoveryClient(); await using WebApplication app = builder.Build(); @@ -760,7 +760,7 @@ public async Task WithVCAPEnvVariables_ButNoUri_DoesNotThrow() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.AddCloudFoundryConfiguration(); - builder.Configuration.AddCloudFoundryServiceBindings(); + builder.Configuration.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.Eureka); builder.Services.AddEurekaDiscoveryClient(); await using WebApplication app = builder.Build(); diff --git a/src/Discovery/test/HttpClients.Test/RegisterMultipleDiscoveryClientsTest.cs b/src/Discovery/test/HttpClients.Test/RegisterMultipleDiscoveryClientsTest.cs index a4a3a846f1..fe62959c7c 100644 --- a/src/Discovery/test/HttpClients.Test/RegisterMultipleDiscoveryClientsTest.cs +++ b/src/Discovery/test/HttpClients.Test/RegisterMultipleDiscoveryClientsTest.cs @@ -231,7 +231,7 @@ public async Task SingleEurekaVCAP_AddsEurekaDiscoveryClient() var builder = new ConfigurationBuilder(); builder.AddInMemoryCollection(appSettings); builder.AddCloudFoundry(); - builder.AddCloudFoundryServiceBindings(); + builder.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.Eureka); IConfiguration configuration = builder.Build(); IServiceCollection services = new ServiceCollection(); @@ -328,7 +328,7 @@ public async Task MultipleEurekaVCAPs_AddsEurekaDiscoveryClientForFirstEntry() var builder = new ConfigurationBuilder(); builder.AddInMemoryCollection(appSettings); builder.AddCloudFoundry(); - builder.AddCloudFoundryServiceBindings(); + builder.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.Eureka); IConfiguration configuration = builder.Build(); IServiceCollection services = new ServiceCollection(); @@ -424,7 +424,10 @@ public void MultipleEurekaVCAPs_LogsWarning() using var loggerFactory = new LoggerFactory([capturingLoggerProvider]); var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.AddCloudFoundryServiceBindings(_ => false, new EnvironmentServiceBindingsReader(), loggerFactory); + + configurationBuilder.AddCloudFoundryServiceBindings(_ => false, new EnvironmentServiceBindingsReader(), CloudFoundryServiceBrokerTypes.Eureka, + loggerFactory); + _ = configurationBuilder.Build(); IList logMessages = capturingLoggerProvider.GetAll(); @@ -536,7 +539,7 @@ public async Task EurekaWithAccessTokenUri_SendsAuthTokenRequestFirst() WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); builder.Configuration.AddCloudFoundry(); - builder.Configuration.AddCloudFoundryServiceBindings(); + builder.Configuration.AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.Eureka); builder.Configuration.AddInMemoryCollection(appSettings); builder.Services.AddEurekaDiscoveryClient(); diff --git a/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs index dd118fa349..290a8e55c0 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs @@ -63,7 +63,7 @@ public async Task PostConfigure_ConfiguresForCloudFoundry() """; using var servicesScope = new EnvironmentVariableScope("VCAP_SERVICES", vcapServices); - IConfiguration configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); + IConfiguration configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.Identity).Build(); var services = new ServiceCollection(); services.AddSingleton(configuration); services.AddAuthentication().AddJwtBearer().ConfigureJwtBearerForCloudFoundry(); @@ -111,7 +111,7 @@ public async Task PostConfigure_ConfiguresForCloudFoundry_AllowMultipleIssuers() using var applicationScope = new EnvironmentVariableScope("VCAP_APPLICATION", "{}"); using var servicesScope = new EnvironmentVariableScope("VCAP_SERVICES", vcapServices); - IConfiguration configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); + IConfiguration configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.Identity).Build(); var services = new ServiceCollection(); services.AddSingleton(configuration); services.AddAuthentication().AddJwtBearer().ConfigureJwtBearerForCloudFoundry(); diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs index f478b0ebb5..794ad38518 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs @@ -67,7 +67,7 @@ public async Task PostConfigure_ConfiguresForCloudFoundry() """; using var servicesScope = new EnvironmentVariableScope("VCAP_SERVICES", vcapServices); - IConfiguration configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); + IConfiguration configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings(CloudFoundryServiceBrokerTypes.Identity).Build(); var services = new ServiceCollection(); services.AddSingleton(configuration); From af26973c873833749b40d63aeae6485df70e7732 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 29 Apr 2026 15:38:40 +0200 Subject: [PATCH 54/81] Tweak vulnerability checks (fix broken builds) (#1684) * Turn off vulnerability checks (we have `scan-vulnerable-dependencies.yml` for that) * Use `dotnet list package --vulnerable --include-transitive` to detect vulnerable packages * Make Sonar aware of vulnerable packages --- .github/workflows/Steeltoe.All.yml | 2 +- .github/workflows/component-shared-workflow.yml | 2 +- .github/workflows/package.yml | 2 +- .../workflows/scan-vulnerable-dependencies.yml | 17 ++++++++++++++++- .github/workflows/sonarcube.yml | 9 +++++---- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.github/workflows/Steeltoe.All.yml b/.github/workflows/Steeltoe.All.yml index 0ba18080c8..cf8cd562a7 100644 --- a/.github/workflows/Steeltoe.All.yml +++ b/.github/workflows/Steeltoe.All.yml @@ -84,7 +84,7 @@ jobs: persist-credentials: false - name: Restore packages - run: dotnet restore ${{ env.SOLUTION_FILE }} /p:Configuration=Release --verbosity minimal + run: dotnet restore ${{ env.SOLUTION_FILE }} /p:Configuration=Release /p:NuGetAudit=false --verbosity minimal - name: Build solution run: dotnet build ${{ env.SOLUTION_FILE }} --no-restore --configuration Release --verbosity minimal diff --git a/.github/workflows/component-shared-workflow.yml b/.github/workflows/component-shared-workflow.yml index f7c144a622..449d72af33 100644 --- a/.github/workflows/component-shared-workflow.yml +++ b/.github/workflows/component-shared-workflow.yml @@ -75,7 +75,7 @@ jobs: persist-credentials: false - name: Restore packages - run: dotnet restore ${{ env.SOLUTION_FILE }} /p:Configuration=Release --verbosity minimal + run: dotnet restore ${{ env.SOLUTION_FILE }} /p:Configuration=Release /p:NuGetAudit=false --verbosity minimal - name: Build solution run: dotnet build ${{ env.SOLUTION_FILE }} --no-restore --configuration Release --verbosity minimal diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 82af36ca0f..39dad17f94 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -45,7 +45,7 @@ jobs: persist-credentials: false - name: Restore packages - run: dotnet restore ${{ env.SOLUTION_FILE }} /p:Configuration=Release --verbosity minimal + run: dotnet restore ${{ env.SOLUTION_FILE }} /p:Configuration=Release /p:NuGetAudit=false --verbosity minimal - name: Calculate package version (for release) if: ${{ github.event_name == 'release' }} diff --git a/.github/workflows/scan-vulnerable-dependencies.yml b/.github/workflows/scan-vulnerable-dependencies.yml index 5cfe3f066f..f8e3ee4bcd 100644 --- a/.github/workflows/scan-vulnerable-dependencies.yml +++ b/.github/workflows/scan-vulnerable-dependencies.yml @@ -41,4 +41,19 @@ jobs: persist-credentials: false - name: Report vulnerable dependencies - run: dotnet restore ${{ env.SOLUTION_FILE }} --verbosity minimal /p:NuGetAudit=true /p:NuGetAuditMode=all /p:NuGetAuditLevel=low /p:TreatWarningsAsErrors=True + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $PSNativeCommandUseErrorActionPreference = $true + + $output = dotnet list ${{ env.SOLUTION_FILE }} package --vulnerable --include-transitive --format json --output-version 1 2>&1 + $text = ($output | Out-String).TrimEnd() + $json = $text | ConvertFrom-Json + + foreach ($project in $json.projects) { + if ($project.frameworks) { + Write-Host 'Vulnerable package references were found.' + dotnet list ${{ env.SOLUTION_FILE }} package --vulnerable --include-transitive + exit 1 + } + } diff --git a/.github/workflows/sonarcube.yml b/.github/workflows/sonarcube.yml index 4e3222b613..712dfe326c 100644 --- a/.github/workflows/sonarcube.yml +++ b/.github/workflows/sonarcube.yml @@ -22,6 +22,7 @@ env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: true SOLUTION_FILE: 'src/Steeltoe.All.slnx' + NUGET_VULNERABLE_PACKAGE_WARNINGS: '"NU1901;NU1902;NU1903;NU1904"' SONAR_TEST_ARGS: >- --no-build --configuration Release --collect "XPlat Code Coverage" --logger trx --results-directory ${{ github.workspace }}/TestOutput --settings coverlet.runsettings -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.UseSourceLink=false @@ -66,9 +67,6 @@ jobs: # Sonar: Shallow clones should be disabled for a better relevancy of analysis. fetch-depth: 0 - - name: Restore packages - run: dotnet restore ${{ env.SOLUTION_FILE }} /p:Configuration=Release --verbosity minimal - - name: Begin Sonar .NET scanner id: sonar_begin env: @@ -77,8 +75,11 @@ jobs: dotnet sonarscanner begin /k:"SteeltoeOSS_steeltoe" /o:"steeltoeoss" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths=**/coverage.opencover.xml + - name: Restore packages + run: dotnet restore ${{ env.SOLUTION_FILE }} --verbosity minimal /p:Configuration=Release /p:NuGetAuditLevel=low /p:WarningsNotAsErrors='${{ env.NUGET_VULNERABLE_PACKAGE_WARNINGS }}' + - name: Build solution - run: dotnet build ${{ env.SOLUTION_FILE }} --no-restore --configuration Release --verbosity minimal + run: dotnet build ${{ env.SOLUTION_FILE }} --no-restore --configuration Release --verbosity minimal /p:NuGetAuditLevel=low /p:WarningsNotAsErrors='${{ env.NUGET_VULNERABLE_PACKAGE_WARNINGS }}' - name: Test run: dotnet test ${{ env.SOLUTION_FILE }} --filter "Category!=MemoryDumps" ${{ env.SONAR_TEST_ARGS }} From ef661b4153928aca478c77955149954762619705 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 29 Apr 2026 15:52:30 +0200 Subject: [PATCH 55/81] Bump OpenTelemetry dependencies to fix vulnerabilities (#1685) * Turn off vulnerability checks (we have `scan-vulnerable-dependencies.yml` for that) * Use `dotnet list package --vulnerable --include-transitive` to detect vulnerable packages * Make Sonar aware of vulnerable packages * Bump OpenTelemetry dependencies to fix vulnerabilities --- versions.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/versions.props b/versions.props index bd806b1215..b895c5b545 100644 --- a/versions.props +++ b/versions.props @@ -71,8 +71,8 @@ 8.14.* 0.2.652701 3.1.23 - 1.14.*-* - 1.14.* + 1.15.*-* + 1.15.* 9.0.* 6.1.* From eac8fc8e0be6ccc02c087f9f3a3ec110cfbe6fe6 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Fri, 1 May 2026 18:07:26 +0200 Subject: [PATCH 56/81] Normalize line endings (#1689) --- Steeltoe.Debug.ruleset | 484 +++++++++--------- Steeltoe.Release.ruleset | 432 ++++++++-------- .../src/Endpoint/PublicAPI.Unshipped.txt | 2 +- .../test/Endpoint.Test/xunit.runner.json | 8 +- .../test/Tracing.Test/xunit.runner.json | 8 +- 5 files changed, 467 insertions(+), 467 deletions(-) diff --git a/Steeltoe.Debug.ruleset b/Steeltoe.Debug.ruleset index 6cd9c84940..97dc2006b9 100644 --- a/Steeltoe.Debug.ruleset +++ b/Steeltoe.Debug.ruleset @@ -1,243 +1,243 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Steeltoe.Release.ruleset b/Steeltoe.Release.ruleset index 343ee1b90a..24a6a00a3b 100644 --- a/Steeltoe.Release.ruleset +++ b/Steeltoe.Release.ruleset @@ -1,217 +1,217 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Management/src/Endpoint/PublicAPI.Unshipped.txt b/src/Management/src/Endpoint/PublicAPI.Unshipped.txt index a2ab2cf104..7dc5c58110 100755 --- a/src/Management/src/Endpoint/PublicAPI.Unshipped.txt +++ b/src/Management/src/Endpoint/PublicAPI.Unshipped.txt @@ -1 +1 @@ -#nullable enable +#nullable enable diff --git a/src/Management/test/Endpoint.Test/xunit.runner.json b/src/Management/test/Endpoint.Test/xunit.runner.json index a2f869986e..fdeefaa456 100644 --- a/src/Management/test/Endpoint.Test/xunit.runner.json +++ b/src/Management/test/Endpoint.Test/xunit.runner.json @@ -1,4 +1,4 @@ -{ - "maxParallelThreads": 1, - "parallelizeTestCollections": false -} +{ + "maxParallelThreads": 1, + "parallelizeTestCollections": false +} diff --git a/src/Management/test/Tracing.Test/xunit.runner.json b/src/Management/test/Tracing.Test/xunit.runner.json index a2f869986e..fdeefaa456 100644 --- a/src/Management/test/Tracing.Test/xunit.runner.json +++ b/src/Management/test/Tracing.Test/xunit.runner.json @@ -1,4 +1,4 @@ -{ - "maxParallelThreads": 1, - "parallelizeTestCollections": false -} +{ + "maxParallelThreads": 1, + "parallelizeTestCollections": false +} From f1f03fd47c1d6bba0d8613c254dc436a3141325f Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 6 May 2026 11:03:13 +0200 Subject: [PATCH 57/81] Fix invalid links (#1690) * Fix invalid links * Fix more links --- .github/ISSUE_TEMPLATE/question.md | 2 +- PackageReadme.md | 2 +- README.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index d10452a60b..884bbc6e52 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -1,6 +1,6 @@ --- name: Question -about: Select if you have a question or feedback - Also check out slack.steeltoe.io for assistance. +about: Select if you have a question or feedback - Also check out https://github.com/SteeltoeOSS/Steeltoe/discussions for assistance. title: "[QUESTION] " labels: Type/question assignees: '' diff --git a/PackageReadme.md b/PackageReadme.md index f3871f74eb..688127cfae 100644 --- a/PackageReadme.md +++ b/PackageReadme.md @@ -6,7 +6,7 @@ Key features include: - External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) - Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. +- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/reference/actuator/endpoints.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. - Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) - Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) diff --git a/README.md b/README.md index 051a22ffab..b3a2f6f64c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Key features include: - External (optionally encrypted) configuration using [Spring Cloud Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) - Service discovery with [Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) and [HashiCorp Consul](https://www.consul.io/) -- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. +- Management endpoints (compatible with [actuators](https://docs.spring.io/spring-boot/reference/actuator/endpoints.html)), providing system info (such as versions, configuration, service container contents, mapped routes and HTTP traffic), heap/thread dumps, health checks, exporting metrics to [Prometheus](https://prometheus.io/), and changing log levels at runtime. - Connectivity to databases (such as [SQL Server](https://www.microsoft.com/sql-server)/[Azure SQL](https://azure.microsoft.com/products/azure-sql), [Cosmos DB](https://azure.microsoft.com/products/cosmos-db/), [MongoDB](https://www.mongodb.com/), [Redis](https://redis.io/), [RabbitMQ](https://www.rabbitmq.com/), [PostgreSQL](https://www.postgresql.org/), and [MySQL](https://www.mysql.com/)), including support for [Entity Framework Core](https://learn.microsoft.com/ef/core/) - Single sign-on, JWT and Certificate auth with [Cloud Foundry](https://www.cloudfoundry.org/) @@ -40,7 +40,7 @@ For more details, see [Supported Versions on the Wiki](https://github.com/Steelt ## Support and Feedback -For community support, we recommend [Steeltoe OSS Slack](https://slack.steeltoe.io), [StackOverflow](https://stackoverflow.com/questions/tagged/steeltoe), or [open an issue](https://github.com/SteeltoeOSS/Steeltoe/issues/new/choose). +For community support, we recommend [Discussions on GitHub](https://github.com/SteeltoeOSS/Steeltoe/discussions), [StackOverflow](https://stackoverflow.com/questions/tagged/steeltoe), or [open an issue](https://github.com/SteeltoeOSS/Steeltoe/issues/new/choose). For production support, we recommend that you contact [Broadcom Support](https://support.broadcom.com/). From a1c4bd34bff6a0e93a5742719b8a6f6fc8493b1c Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Mon, 11 May 2026 15:17:11 +0200 Subject: [PATCH 58/81] Hide transitive vulnerabilities in tests (#1692) * Don't report transitive vulnerabilities in tests during local builds * Hide transitive vulnerabilities in tests during cibuild --- .../scan-vulnerable-dependencies.yml | 33 ++++++++++++++++--- shared-test.props | 1 + 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/.github/workflows/scan-vulnerable-dependencies.yml b/.github/workflows/scan-vulnerable-dependencies.yml index f8e3ee4bcd..4d310a4ecc 100644 --- a/.github/workflows/scan-vulnerable-dependencies.yml +++ b/.github/workflows/scan-vulnerable-dependencies.yml @@ -49,11 +49,36 @@ jobs: $output = dotnet list ${{ env.SOLUTION_FILE }} package --vulnerable --include-transitive --format json --output-version 1 2>&1 $text = ($output | Out-String).TrimEnd() $json = $text | ConvertFrom-Json + $hasVulnerabilities = $false foreach ($project in $json.projects) { - if ($project.frameworks) { - Write-Host 'Vulnerable package references were found.' - dotnet list ${{ env.SOLUTION_FILE }} package --vulnerable --include-transitive - exit 1 + if (-not $project.frameworks) { + continue } + + $isTestProject = $project.path -like '*/test/*' + + foreach ($framework in $project.frameworks) { + foreach ($package in $framework.topLevelPackages) { + $hasVulnerabilities = $true + + foreach ($vulnerability in $package.vulnerabilities) { + Write-Host "$($project.path) ($($framework.framework)): top-level $($package.id) $($package.resolvedVersion) – $($vulnerability.severity): $($vulnerability.advisoryurl)" + } + } + + if (-not $isTestProject) { + foreach ($package in $framework.transitivePackages) { + $hasVulnerabilities = $true + + foreach ($vulnerability in $package.vulnerabilities) { + Write-Host "$($project.path) ($($framework.framework)): transitive $($package.id) $($package.resolvedVersion) – $($vulnerability.severity): $($vulnerability.advisoryurl)" + } + } + } + } + } + + if ($hasVulnerabilities) { + exit 1 } diff --git a/shared-test.props b/shared-test.props index 120065dd28..0f08b261d8 100644 --- a/shared-test.props +++ b/shared-test.props @@ -3,6 +3,7 @@ Exe false $(NoWarn);S2094;S3717;SA1602;CA1062;CA1707;NU5104 + direct From 42fff3e8ae2918e787d198556df10b3366f004fb Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Mon, 11 May 2026 10:35:01 -0500 Subject: [PATCH 59/81] Address flaky tests (#1691) - Prevent extra SBA refresh request after refresh is disabled - Address race condition in thread dump test - Catch network-related SQLServer connection error - Remove retry from eureka server timeout - More buffer for infra around health aggregator parallelization test (allow +1s for +3s work) --- ...RelationalDatabaseHealthContributorTest.cs | 5 +++- .../EurekaServiceCollectionExtensionsTest.cs | 2 +- .../SpringBootAdminPeriodicRefresh.cs | 21 +++++++++------- .../Actuators/Health/HealthAggregationTest.cs | 7 +++--- .../ThreadDump/EventPipeThreadDumperTest.cs | 24 ++++++++++++++++--- 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs index 267d293dac..42da1e5d2d 100644 --- a/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs @@ -111,7 +111,10 @@ public async Task SQLServer_Not_Connected_Returns_Down_Status() result.Description.Should().Be("SQL Server health check failed"); result.Details.Should().Contain("host", "localhost"); result.Details.Should().Contain("service", "Example"); - result.Details.Should().ContainKey("error").WhoseValue.As().Should().StartWith("SqlException: Connection Timeout Expired."); + + result.Details.Should().ContainKey("error").WhoseValue.As().Should().Match(exception => + exception.StartsWith("SqlException: Connection Timeout Expired.", StringComparison.Ordinal) || + exception.StartsWith("SqlException: A network-related or instance-specific error", StringComparison.Ordinal)); } [Fact(Skip = "Integration test - Requires local SQL Server instance")] diff --git a/src/Discovery/test/Eureka.Test/EurekaServiceCollectionExtensionsTest.cs b/src/Discovery/test/Eureka.Test/EurekaServiceCollectionExtensionsTest.cs index 6d22358713..745c012afb 100644 --- a/src/Discovery/test/Eureka.Test/EurekaServiceCollectionExtensionsTest.cs +++ b/src/Discovery/test/Eureka.Test/EurekaServiceCollectionExtensionsTest.cs @@ -62,7 +62,7 @@ public async Task AddEurekaDiscoveryClient_UsesServerTimeout() var appSettings = new Dictionary { ["Eureka:Client:EurekaServer:ConnectTimeoutSeconds"] = "1", - ["Eureka:Client:EurekaServer:RetryCount"] = "1" + ["Eureka:Client:EurekaServer:RetryCount"] = "0" }; IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(appSettings).Build(); diff --git a/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminPeriodicRefresh.cs b/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminPeriodicRefresh.cs index 6426993f12..9477238be3 100644 --- a/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminPeriodicRefresh.cs +++ b/src/Management/src/Endpoint/SpringBootAdminClient/SpringBootAdminPeriodicRefresh.cs @@ -44,15 +44,20 @@ private async Task TimerLoopAsync(TimeSpan interval) do { - LogStartingRefreshCycle(); - - try - { - await _runner.RunAsync(isFirstTime, _timerTokenSource.Token); - } - catch (Exception exception) when (!exception.IsCancellation()) + // A tick queued just before periodic refresh was disabled would still be delivered here. + // Checking the period prevents executing a stale tick when refresh has been turned off. + if (isFirstTime || _periodicTimer.Period != Timeout.InfiniteTimeSpan) { - LogRefreshCycleFailed(exception); + LogStartingRefreshCycle(); + + try + { + await _runner.RunAsync(isFirstTime, _timerTokenSource.Token); + } + catch (Exception exception) when (!exception.IsCancellation()) + { + LogRefreshCycleFailed(exception); + } } isFirstTime = false; diff --git a/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs b/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs index ad76b9eaad..172f589c1c 100644 --- a/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs @@ -366,9 +366,9 @@ public async Task Aggregates_contributors_in_parallel() { List contributors = [ - new SlowContributor(1.Seconds()), new SlowContributor(2.Seconds()), - new SlowContributor(3.Seconds()) + new SlowContributor(3.Seconds()), + new SlowContributor(4.Seconds()) ]; WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create(); @@ -408,7 +408,8 @@ public async Task Aggregates_contributors_in_parallel() } """); - stopwatch.Elapsed.Should().BeGreaterThan(500.Milliseconds()).And.BeLessThan(5.Seconds()); + // Upper bound must be less than 2+3+4=9s if contributors ran sequentially. + stopwatch.Elapsed.Should().BeGreaterThan(500.Milliseconds()).And.BeLessThan(9.Seconds()); } [Fact] diff --git a/src/Management/test/Endpoint.Test/Actuators/ThreadDump/EventPipeThreadDumperTest.cs b/src/Management/test/Endpoint.Test/Actuators/ThreadDump/EventPipeThreadDumperTest.cs index 3e46e42afd..43711fff72 100644 --- a/src/Management/test/Endpoint.Test/Actuators/ThreadDump/EventPipeThreadDumperTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/ThreadDump/EventPipeThreadDumperTest.cs @@ -16,19 +16,30 @@ public sealed class EventPipeThreadDumperTest public async Task Can_resolve_source_location_from_pdb() { using var backgroundCancellationSource = new CancellationTokenSource(); + using var threadStarted = new ManualResetEventSlim(false); var backgroundThread = new Thread(NestedType.BackgroundThreadCallback) { IsBackground = true }; - backgroundThread.Start(backgroundCancellationSource.Token); + backgroundThread.Start((backgroundCancellationSource.Token, threadStarted)); + threadStarted.Wait(TestContext.Current.CancellationToken); using var loggerProvider = new CapturingLoggerProvider(); using var loggerFactory = new LoggerFactory([loggerProvider]); ILogger logger = loggerFactory.CreateLogger(); +#if NET8_0 + // Use a longer collection window on .NET 8 to compensate for the Sleep(0) yield. + var optionsMonitor = TestOptionsMonitor.Create(new ThreadDumpEndpointOptions + { + Duration = 100 + }); +#else var optionsMonitor = new TestOptionsMonitor(); +#endif + var dumper = new EventPipeThreadDumper(optionsMonitor, logger); IList threads = await dumper.DumpThreadsAsync(TestContext.Current.CancellationToken); @@ -87,11 +98,18 @@ private static class NestedType { public static void BackgroundThreadCallback(object? argument) { - var cancellationToken = (CancellationToken)argument!; + (CancellationToken cancellationToken, ManualResetEventSlim threadStarted) = ((CancellationToken, ManualResetEventSlim))argument!; + + threadStarted.Set(); while (!cancellationToken.IsCancellationRequested) { - Thread.Sleep(TimeSpan.FromMilliseconds(50)); + // Only actively-running threads are shown in the thread dump, so we need to make sure the CPU is in use. + Thread.SpinWait(250); +#if NET8_0 + // Yield to allow the EventPipe rundown thread to make progress on .NET 8. + Thread.Sleep(0); +#endif } } } From 044c1f1c43e5b0bef2a162b4fab93c9f0f8d7871 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Tue, 12 May 2026 17:02:03 +0200 Subject: [PATCH 60/81] Package updates (#1694) * Update ReSharper * Update MongoDB.Driver * Update coverlet.collector * Update Microsoft.IdentityModel.Protocols.OpenIdConnect / Microsoft.IdentityModel.Tokens These are exposed dependencies. Bumping not to latest, but just a higher minor that fixes sanitizing logs to avoid leaking sensitive data. * Update Microsoft.NET.Test.Sdk * Update MySql.Data * Update SonarAnalyzer.CSharp (new rules: S8367, S8368, S8380, S8381) * TEST: Detect coverage diff in Sonar * Revert "TEST: Detect coverage diff in Sonar" This reverts commit f3fae6a9e938b8c2ed7bf0764178cffb6715c8de. --- .config/dotnet-tools.json | 2 +- .../Contributors/FileSystem/NetworkShareWrapper.cs | 2 -- versions.props | 12 ++++++------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 4c979f3d12..92e3666ac5 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2026.1.0.1", + "version": "2026.1.1", "commands": [ "jb" ], diff --git a/src/Management/src/Endpoint/Actuators/Health/Contributors/FileSystem/NetworkShareWrapper.cs b/src/Management/src/Endpoint/Actuators/Health/Contributors/FileSystem/NetworkShareWrapper.cs index a62227d27d..c39452957d 100644 --- a/src/Management/src/Endpoint/Actuators/Health/Contributors/FileSystem/NetworkShareWrapper.cs +++ b/src/Management/src/Endpoint/Actuators/Health/Contributors/FileSystem/NetworkShareWrapper.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; namespace Steeltoe.Management.Endpoint.Actuators.Health.Contributors.FileSystem; @@ -27,7 +26,6 @@ private NetworkShareWrapper(ulong freeBytesAvailable, ulong totalNumberOfBytes) : null; } - [ExcludeFromCodeCoverage(Justification = "Workaround for https://github.com/coverlet-coverage/coverlet/issues/1762")] private static partial class NativeMethods { [LibraryImport("kernel32.dll", EntryPoint = "GetDiskFreeSpaceExW", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)] diff --git a/versions.props b/versions.props index b895c5b545..c81daec6e1 100644 --- a/versions.props +++ b/versions.props @@ -6,27 +6,27 @@ --> 9.0.* - 8.0.* + 10.0.* 7.2.* 3.58.* 5.0.* 7.0.* 7.0.* - 3.7.* + 3.8.* 4.20.69 2.5.* - 9.6.* + 9.7.* 13.0.* 3.3.* 7.2.* 4.0.* 8.4.* - 10.20.0.135146 + 10.25.0.139117 1.2.0-beta.556 2.0.* 8.15.* 4.9.* - 18.4.* + 18.5.* 3.2.* 3.1.* @@ -68,7 +68,7 @@ --> 10.0.* - 8.14.* + 8.15.* 0.2.652701 3.1.23 1.15.*-* From f0dfcf289cea349b5f8f4ae27846dd6a70e73794 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 13 May 2026 17:03:43 +0200 Subject: [PATCH 61/81] Fix flaky certificate tests failing with InvalidOperationException (#1697) The tests used `using Task` on a poll task that was still running when the `WaitAsync` timeout expired, causing `Task.Dispose()` to throw `InvalidOperationException` instead of a meaningful timeout failure. Replace polling with `IOptionsMonitor.OnChange` and a `TaskCompletionSource`, registering the listener before triggering the file change to avoid race conditions. --- .../ConfigureCertificateOptionsTest.cs | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/Common/test/Certificates.Test/ConfigureCertificateOptionsTest.cs b/src/Common/test/Certificates.Test/ConfigureCertificateOptionsTest.cs index f14de9eed7..080e005b87 100644 --- a/src/Common/test/Certificates.Test/ConfigureCertificateOptionsTest.cs +++ b/src/Common/test/Certificates.Test/ConfigureCertificateOptionsTest.cs @@ -163,11 +163,11 @@ public async Task CertificateOptions_update_on_changed_contents(string certifica var optionsMonitor = serviceProvider.GetRequiredService>(); optionsMonitor.Get(certificateName).Certificate.Should().BeEquivalentTo(firstX509); - await File.WriteAllTextAsync(certificateFilePath, secondCertificateContent, TestContext.Current.CancellationToken); - await File.WriteAllTextAsync(privateKeyFilePath, secondPrivateKeyContent, TestContext.Current.CancellationToken); - - using Task pollTask = WaitUntilCertificateChangedToAsync(secondX509, optionsMonitor, certificateName, TestContext.Current.CancellationToken); - await pollTask.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); + await WaitUntilCertificateChangedToAsync(certificateName, secondX509, optionsMonitor, async () => + { + await File.WriteAllTextAsync(certificateFilePath, secondCertificateContent, TestContext.Current.CancellationToken); + await File.WriteAllTextAsync(privateKeyFilePath, secondPrivateKeyContent, TestContext.Current.CancellationToken); + }); optionsMonitor.Get(certificateName).Certificate.Should().Be(secondX509); } @@ -200,11 +200,11 @@ public async Task CertificateOptions_update_on_changed_path(string certificateNa var optionsMonitor = serviceProvider.GetRequiredService>(); optionsMonitor.Get(certificateName).Certificate.Should().BeEquivalentTo(firstX509); - appSettings = BuildAppSettingsJson(certificateName, "secondInstance.crt", "secondInstance.key"); - await File.WriteAllTextAsync(appSettingsPath, appSettings, TestContext.Current.CancellationToken); - - using Task pollTask = WaitUntilCertificateChangedToAsync(secondX509, optionsMonitor, certificateName, TestContext.Current.CancellationToken); - await pollTask.WaitAsync(TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken); + await WaitUntilCertificateChangedToAsync(certificateName, secondX509, optionsMonitor, async () => + { + appSettings = BuildAppSettingsJson(certificateName, "secondInstance.crt", "secondInstance.key"); + await File.WriteAllTextAsync(appSettingsPath, appSettings, TestContext.Current.CancellationToken); + }); optionsMonitor.Get(certificateName).Certificate.Should().Be(secondX509); } @@ -235,13 +235,21 @@ private static string BuildAppSettingsJson(string certificateName, string certif """; } - private static async Task WaitUntilCertificateChangedToAsync(X509Certificate2 expectedCertificate, IOptionsMonitor optionsMonitor, - string certificateName, CancellationToken cancellationToken) + private static async Task WaitUntilCertificateChangedToAsync(string certificateName, X509Certificate2 expectedCertificate, + IOptionsMonitor optionsMonitor, Func triggerAction) { - while (!Equals(optionsMonitor.Get(certificateName).Certificate, expectedCertificate)) + var completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + using IDisposable? changeListener = optionsMonitor.OnChange((options, name) => { - await Task.Delay(50, cancellationToken); - } + if (name == certificateName && Equals(options.Certificate, expectedCertificate)) + { + completionSource.TrySetResult(); + } + }); + + await triggerAction(); + await completionSource.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); } private static string GetConfigurationKey(string? optionName, string propertyName) From f4d59932200d320863eb08149917bc9527e24a8f Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 13 May 2026 17:43:17 +0200 Subject: [PATCH 62/81] Fix CA1873: Potentially expensive logging (#1695) * Fix CA1873: Potentially expensive logging * Refactor UriExtensions.ToMaskedString into MaskedUri struct with implicit conversion * Mask request URLs in HTTP exchanges actuator --- shared-package.props | 8 --- src/Common/src/Common/Extensions/MaskedUri.cs | 59 +++++++++++++++++++ .../src/Common/Extensions/UriExtensions.cs | 33 ----------- src/Common/src/Http/HttpClientExtensions.cs | 3 +- .../Common.Test/Extensions/MaskedUriTest.cs | 43 ++++++++++++++ .../Extensions/UriExtensionsTest.cs | 33 ----------- .../CompositeConfigurationProvider.cs | 16 +++-- .../ConfigServerConfigurationProvider.cs | 44 ++++++++------ .../ConfigServerHealthContributor.cs | 13 +++- .../DecryptionConfigurationProvider.cs | 2 +- src/Discovery/src/Eureka/EurekaClient.cs | 36 +++++------ .../LoadBalancers/RandomLoadBalancer.cs | 3 +- .../LoadBalancers/RoundRobinLoadBalancer.cs | 3 +- .../CloudFoundry/PermissionsProvider.cs | 23 +++++--- .../HttpExchanges/HttpExchangeRequest.cs | 13 +++- .../HttpExchanges/HttpExchangesRepository.cs | 3 +- .../Loggers/LoggersEndpointHandler.cs | 13 +++- .../Endpoint/Middleware/EndpointMiddleware.cs | 5 +- .../src/Endpoint/PublicAPI.Unshipped.txt | 1 + .../HttpExchangesActuatorTest.cs | 26 ++++---- .../CertificateAuthorizationHandler.cs | 13 +++- 21 files changed, 244 insertions(+), 149 deletions(-) create mode 100644 src/Common/src/Common/Extensions/MaskedUri.cs create mode 100644 src/Common/test/Common.Test/Extensions/MaskedUriTest.cs diff --git a/shared-package.props b/shared-package.props index 5457c82531..4b7ead5e6f 100644 --- a/shared-package.props +++ b/shared-package.props @@ -33,14 +33,6 @@ True - - - $(NoWarn);CA1873 - -