From f303bf448a4a88c9c42cb7f7d9a4159e2410541d Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:04:27 +0200 Subject: [PATCH 1/7] Add property to disable built-in post-processors, including sample code for binding third-party brokers --- .../ConnectorConfigureOptionsBuilder.cs | 79 ++++++++++++++++ .../src/Connectors/ConnectorConfigurer.cs | 7 +- .../src/Connectors/PublicAPI.Unshipped.txt | 2 + .../PostgreSql/PostgreSqlConnectorTest.cs | 92 +++++++++++++++++++ 4 files changed, 178 insertions(+), 2 deletions(-) diff --git a/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs b/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs index 85f4a87b56..46dd74a382 100644 --- a/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs +++ b/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs @@ -13,4 +13,83 @@ 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, 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..ba77bbf605 100644 --- a/src/Connectors/src/Connectors/ConnectorConfigurer.cs +++ b/src/Connectors/src/Connectors/ConnectorConfigurer.cs @@ -20,8 +20,11 @@ public static void Configure(IConfigurationBuilder builder, Acti var optionsBuilder = new ConnectorConfigureOptionsBuilder(); configureAction?.Invoke(optionsBuilder); - builder.AddCloudFoundryServiceBindings(); - builder.AddKubernetesServiceBindings(); + if (!optionsBuilder.SkipDefaultServiceBindings) + { + builder.AddCloudFoundryServiceBindings(); + builder.AddKubernetesServiceBindings(); + } RegisterPostProcessor(connectionStringPostProcessor, builder, optionsBuilder.DetectConfigurationChanges); } 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/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTest.cs b/src/Connectors/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTest.cs index 9a158bb430..9db3f840b4 100644 --- a/src/Connectors/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTest.cs +++ b/src/Connectors/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTest.cs @@ -9,6 +9,7 @@ using Npgsql; using Steeltoe.Common.HealthChecks; using Steeltoe.Common.TestResources; +using Steeltoe.Configuration.CloudFoundry; using Steeltoe.Configuration.CloudFoundry.ServiceBindings; using Steeltoe.Configuration.Kubernetes.ServiceBindings; using Steeltoe.Connectors.PostgreSql; @@ -394,6 +395,97 @@ public async Task Binds_options_with_Kubernetes_service_bindings() }, options => options.WithoutStrictOrdering()); } + [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" + } + }, + { + "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); + MapCustomServiceBindings("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"); + + 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()); + + PostgreSqlOptions ordersDbOptions = optionsMonitor.Get("orders-db"); + + 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 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 + }); + } + } + } + [Fact] public async Task Registers_ConnectorFactory() { From 66157c8f6ff833146fa52ab6417b54c9f7d6607d Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:12:22 +0200 Subject: [PATCH 2/7] Update links to supported service brokers --- .../PostProcessors/MongoDbCloudFoundryPostProcessor.cs | 2 +- .../PostProcessors/MySqlCloudFoundryPostProcessor.cs | 6 +++--- .../PostProcessors/PostgreSqlCloudFoundryPostProcessor.cs | 6 +++--- .../PostProcessors/RabbitMQCloudFoundryPostProcessor.cs | 2 +- .../PostProcessors/RedisCloudFoundryPostProcessor.cs | 7 +++---- .../PostProcessors/SqlServerCloudFoundryPostProcessor.cs | 4 ++-- 6 files changed, 13 insertions(+), 14 deletions(-) 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", ","); From 17eb384f9a3dd7b0a8503b1efdfff6a18dcf26f6 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:56:25 +0200 Subject: [PATCH 3/7] Review feedback: adapt documentation --- .../ConnectorConfigureOptionsBuilder.cs | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs b/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs index 46dd74a382..746fe5617e 100644 --- a/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs +++ b/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs @@ -19,39 +19,43 @@ public sealed class ConnectorConfigureOptionsBuilder /// 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, the following code can be used to map the PostgreSQL credentials to the format that + /// 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: /// Date: Thu, 23 Apr 2026 12:30:25 +0200 Subject: [PATCH 4/7] Bugfix: preserve custom query string parameters in local RabbitMQ URI; relax other parsers to allow unknown parameters to support third-party brokers --- .../MongoDb/MongoDbConnectionStringBuilder.cs | 57 +++++-------------- .../RabbitMQConnectionStringBuilder.cs | 34 ++++++++--- .../Redis/RedisConnectionStringBuilder.cs | 20 +------ .../MongoDbConnectionStringBuilderTest.cs | 6 +- .../MongoDb/MongoDbConnectorTest.cs | 2 +- .../RabbitMQConnectionStringBuilderTest.cs | 33 ++++++++--- .../RabbitMQ/RabbitMQConnectorTest.cs | 12 ++-- .../Redis/RedisConnectionStringBuilderTest.cs | 6 +- 8 files changed, 81 insertions(+), 89 deletions(-) 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/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/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/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..3d200dcd08 100644 --- a/src/Connectors/test/Connectors.Test/MongoDb/MongoDbConnectorTest.cs +++ b/src/Connectors/test/Connectors.Test/MongoDb/MongoDbConnectorTest.cs @@ -158,7 +158,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"); 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..5ec987837a 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,7 +214,7 @@ 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(); @@ -228,7 +228,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"); 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] From bf8abadd9361b29b2af1e895dab1fdd07b86b5f0 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:22:44 +0200 Subject: [PATCH 5/7] Refactor to allow multiple CloudFoundryServiceBindingConfigurationSources in configuration, each scoped to a subset of post-processors --- .../HostBuilderExtensionsTest.cs | 2 +- .../src/CloudFoundry/PublicAPI.Unshipped.txt | 13 + ...oundryServiceBindingConfigurationSource.cs | 11 +- .../CloudFoundryServiceBrokerTypes.cs | 25 ++ .../ConfigurationBuilderExtensions.cs | 113 ++++++-- .../ConfigurationBuilderExtensions.cs | 2 +- ...ServiceBindingConfigurationProviderTest.cs | 6 +- .../ConfigurationBuilderExtensionsTest.cs | 11 + .../ConnectorConfigureOptionsBuilder.cs | 4 + .../src/Connectors/ConnectorConfigurer.cs | 12 +- .../CosmosDbConfigurationBuilderExtensions.cs | 10 +- .../MongoDbConfigurationBuilderExtensions.cs | 18 +- ...MongoDbHostApplicationBuilderExtensions.cs | 9 +- .../MySqlConfigurationBuilderExtensions.cs | 18 +- .../MySqlHostApplicationBuilderExtensions.cs | 9 +- ...ostgreSqlConfigurationBuilderExtensions.cs | 22 +- ...tgreSqlHostApplicationBuilderExtensions.cs | 10 +- .../RabbitMQConfigurationBuilderExtensions.cs | 20 +- ...abbitMQHostApplicationBuilderExtensions.cs | 9 +- .../RedisConfigurationBuilderExtensions.cs | 18 +- .../RedisHostApplicationBuilderExtensions.cs | 9 +- ...SqlServerConfigurationBuilderExtensions.cs | 21 +- ...lServerHostApplicationBuilderExtensions.cs | 9 +- .../test/Connectors.Test/CustomBrokerTests.cs | 266 ++++++++++++++++++ .../MongoDb/MongoDbConnectorTest.cs | 6 +- .../MySqlConnector/MySqlConnectorTest.cs | 14 +- .../MySql/Oracle/MySqlConnectorTest.cs | 14 +- .../PostgreSql/PostgreSqlConnectorTest.cs | 112 +------- .../RabbitMQ/RabbitMQConnectorTest.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 +- 39 files changed, 648 insertions(+), 226 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..049734ab5b 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBindingConfigurationSource.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBindingConfigurationSource.cs @@ -2,19 +2,23 @@ // 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; + private readonly CloudFoundryServiceBrokerTypes _brokerTypes; - public CloudFoundryServiceBindingConfigurationSource(IServiceBindingsReader serviceBindingsReader) + public CloudFoundryServiceBindingConfigurationSource(IServiceBindingsReader serviceBindingsReader, CloudFoundryServiceBrokerTypes brokerTypes) { ArgumentNullException.ThrowIfNull(serviceBindingsReader); _serviceBindingsReader = serviceBindingsReader; + _brokerTypes = brokerTypes; } public IConfigurationProvider Build(IConfigurationBuilder builder) @@ -24,4 +28,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..3d0dbf764d --- /dev/null +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBrokerTypes.cs @@ -0,0 +1,25 @@ +// 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 +{ + None = 0x0, + + Eureka = 0x1, + Identity = 0x2, + MongoDb = 0x4, + MySql = 0x8, + PostgreSql = 0x10, + RabbitMQ = 0x20, + Redis = 0x40, + SqlServer = 0x80, + + 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..d5b880eeb1 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,41 @@ 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()) + if (brokerTypes != CloudFoundryServiceBrokerTypes.None) { - var source = new CloudFoundryServiceBindingConfigurationSource(serviceBindingsReader) + var source = new CloudFoundryServiceBindingConfigurationSource(serviceBindingsReader ?? DefaultReader, brokerTypes) { IgnoreKeyPredicate = ignoreKeyPredicate }; @@ -86,25 +130,56 @@ 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, brokerTypes, loggerFactory); builder.Add(source); } return builder; } - private static void RegisterPostProcessors(CloudFoundryServiceBindingConfigurationSource source, ILoggerFactory loggerFactory) + private static void RegisterPostProcessors(CloudFoundryServiceBindingConfigurationSource source, CloudFoundryServiceBrokerTypes brokerTypes, + ILoggerFactory loggerFactory) { - 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()); + 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/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..5188ad19ad 100644 --- a/src/Configuration/test/CloudFoundry.Test/ServiceBindings/ConfigurationBuilderExtensionsTest.cs +++ b/src/Configuration/test/CloudFoundry.Test/ServiceBindings/ConfigurationBuilderExtensionsTest.cs @@ -57,6 +57,17 @@ 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_EnvironmentVariableSet_LoadsServiceBindings() { diff --git a/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs b/src/Connectors/src/Connectors/ConnectorConfigureOptionsBuilder.cs index 746fe5617e..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 diff --git a/src/Connectors/src/Connectors/ConnectorConfigurer.cs b/src/Connectors/src/Connectors/ConnectorConfigurer.cs index ba77bbf605..5b5c489be1 100644 --- a/src/Connectors/src/Connectors/ConnectorConfigurer.cs +++ b/src/Connectors/src/Connectors/ConnectorConfigurer.cs @@ -3,26 +3,30 @@ // 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); if (!optionsBuilder.SkipDefaultServiceBindings) { - builder.AddCloudFoundryServiceBindings(); + builder.AddCloudFoundryServiceBindings(DefaultIgnoreKeyPredicate, serviceBindingsReader, optionsBuilder.CloudFoundryBrokerTypes, loggerFactory); builder.AddKubernetesServiceBindings(); } 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/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/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/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/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/MongoDbConnectorTest.cs b/src/Connectors/test/Connectors.Test/MongoDb/MongoDbConnectorTest.cs index 3d200dcd08..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>(); @@ -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 9db3f840b4..8ab17a04ee 100644 --- a/src/Connectors/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTest.cs +++ b/src/Connectors/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTest.cs @@ -9,7 +9,6 @@ using Npgsql; using Steeltoe.Common.HealthChecks; using Steeltoe.Common.TestResources; -using Steeltoe.Configuration.CloudFoundry; using Steeltoe.Configuration.CloudFoundry.ServiceBindings; using Steeltoe.Configuration.Kubernetes.ServiceBindings; using Steeltoe.Connectors.PostgreSql; @@ -233,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>(); @@ -395,97 +393,6 @@ public async Task Binds_options_with_Kubernetes_service_bindings() }, options => options.WithoutStrictOrdering()); } - [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" - } - }, - { - "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); - MapCustomServiceBindings("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"); - - 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()); - - PostgreSqlOptions ordersDbOptions = optionsMonitor.Get("orders-db"); - - 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 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 - }); - } - } - } - [Fact] public async Task Registers_ConnectorFactory() { @@ -553,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(); @@ -585,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>(); @@ -618,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>(); @@ -716,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>(); @@ -744,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>(); @@ -769,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/RabbitMQConnectorTest.cs b/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQConnectorTest.cs index 5ec987837a..6c944dc0b1 100644 --- a/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQConnectorTest.cs +++ b/src/Connectors/test/Connectors.Test/RabbitMQ/RabbitMQConnectorTest.cs @@ -218,9 +218,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.AddRabbitMQ(); + builder.AddRabbitMQ(null, null, new StringServiceBindingsReader(MultiVcapServicesJson)); await using WebApplication app = builder.Build(); var optionsMonitor = app.Services.GetRequiredService>(); @@ -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/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 2d615c97868eab671747b032d628e80784238dbf Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Thu, 23 Apr 2026 13:55:03 +0200 Subject: [PATCH 6/7] Fix broken build --- .../CloudFoundryServiceBrokerTypes.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBrokerTypes.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBrokerTypes.cs index 3d0dbf764d..187b30de3b 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBrokerTypes.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBrokerTypes.cs @@ -10,16 +10,53 @@ namespace Steeltoe.Configuration.CloudFoundry.ServiceBindings; [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 } From 4f5d923d09a14bb6391e0cfe8dc28d19d6943927 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Fri, 24 Apr 2026 11:11:21 +0200 Subject: [PATCH 7/7] Review feedback: filter already registered post-processors --- ...oundryServiceBindingConfigurationSource.cs | 7 +++--- .../ConfigurationBuilderExtensions.cs | 23 ++++++++++++++++--- .../ConfigurationBuilderExtensionsTest.cs | 18 +++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBindingConfigurationSource.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBindingConfigurationSource.cs index 049734ab5b..812960c4ce 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBindingConfigurationSource.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/CloudFoundryServiceBindingConfigurationSource.cs @@ -11,14 +11,15 @@ namespace Steeltoe.Configuration.CloudFoundry.ServiceBindings; internal sealed class CloudFoundryServiceBindingConfigurationSource : PostProcessorConfigurationSource, IConfigurationSource { private readonly IServiceBindingsReader _serviceBindingsReader; - private readonly CloudFoundryServiceBrokerTypes _brokerTypes; + + public CloudFoundryServiceBrokerTypes BrokerTypes { get; } public CloudFoundryServiceBindingConfigurationSource(IServiceBindingsReader serviceBindingsReader, CloudFoundryServiceBrokerTypes brokerTypes) { ArgumentNullException.ThrowIfNull(serviceBindingsReader); _serviceBindingsReader = serviceBindingsReader; - _brokerTypes = brokerTypes; + BrokerTypes = brokerTypes; } public IConfigurationProvider Build(IConfigurationBuilder builder) @@ -31,6 +32,6 @@ public IConfigurationProvider Build(IConfigurationBuilder builder) private string DebuggerToString() { - return $"{GetType().FullName} ({_brokerTypes})"; + return $"{GetType().FullName} ({BrokerTypes})"; } } diff --git a/src/Configuration/src/CloudFoundry/ServiceBindings/ConfigurationBuilderExtensions.cs b/src/Configuration/src/CloudFoundry/ServiceBindings/ConfigurationBuilderExtensions.cs index d5b880eeb1..8cdebbc329 100644 --- a/src/Configuration/src/CloudFoundry/ServiceBindings/ConfigurationBuilderExtensions.cs +++ b/src/Configuration/src/CloudFoundry/ServiceBindings/ConfigurationBuilderExtensions.cs @@ -119,9 +119,11 @@ public static IConfigurationBuilder AddCloudFoundryServiceBindings(this IConfigu ArgumentNullException.ThrowIfNull(ignoreKeyPredicate); ArgumentNullException.ThrowIfNull(loggerFactory); - if (brokerTypes != CloudFoundryServiceBrokerTypes.None) + CloudFoundryServiceBrokerTypes missingBrokerTypes = GetMissingBrokerTypes(builder, brokerTypes); + + if (missingBrokerTypes != CloudFoundryServiceBrokerTypes.None) { - var source = new CloudFoundryServiceBindingConfigurationSource(serviceBindingsReader ?? DefaultReader, brokerTypes) + var source = new CloudFoundryServiceBindingConfigurationSource(serviceBindingsReader ?? DefaultReader, missingBrokerTypes) { IgnoreKeyPredicate = ignoreKeyPredicate }; @@ -130,13 +132,28 @@ 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, brokerTypes, loggerFactory); + RegisterPostProcessors(source, missingBrokerTypes, loggerFactory); builder.Add(source); } return builder; } + private static CloudFoundryServiceBrokerTypes GetMissingBrokerTypes(IConfigurationBuilder builder, CloudFoundryServiceBrokerTypes brokerTypesRequested) + { + 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) { diff --git a/src/Configuration/test/CloudFoundry.Test/ServiceBindings/ConfigurationBuilderExtensionsTest.cs b/src/Configuration/test/CloudFoundry.Test/ServiceBindings/ConfigurationBuilderExtensionsTest.cs index 5188ad19ad..672b54db16 100644 --- a/src/Configuration/test/CloudFoundry.Test/ServiceBindings/ConfigurationBuilderExtensionsTest.cs +++ b/src/Configuration/test/CloudFoundry.Test/ServiceBindings/ConfigurationBuilderExtensionsTest.cs @@ -68,6 +68,24 @@ public void AddCloudFoundryServiceBindings_RegistersSubsetOfProcessors() 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() {