From d52706e761b05416a8c830bec8edfa22a413f283 Mon Sep 17 00:00:00 2001 From: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> Date: Wed, 1 Nov 2023 12:43:10 +0100 Subject: [PATCH] Add Connector support for VMware Postgres TAS tile --- .../ConfigurationDictionaryMapper.cs | 38 +++++++++++++++++++ .../CloudFoundryPostProcessor.cs | 2 +- .../PostgreSqlCloudFoundryPostProcessor.cs | 38 +++++++++++++++++-- .../PostProcessorsTest.cs | 31 +++++++++++++++ 4 files changed, 104 insertions(+), 5 deletions(-) diff --git a/src/Configuration/src/Abstractions/ConfigurationDictionaryMapper.cs b/src/Configuration/src/Abstractions/ConfigurationDictionaryMapper.cs index 7fc640c708..70bcf14d9e 100644 --- a/src/Configuration/src/Abstractions/ConfigurationDictionaryMapper.cs +++ b/src/Configuration/src/Abstractions/ConfigurationDictionaryMapper.cs @@ -4,6 +4,7 @@ #nullable enable +using System.Text; using Microsoft.Extensions.Configuration; namespace Steeltoe.Configuration; @@ -29,6 +30,43 @@ protected ConfigurationDictionaryMapper(IDictionary configurati return value; } + public string? MapArrayFromTo(string fromArrayKey, string toKey, string separator) + { + int arrayIndex = 0; + var toValueBuilder = new StringBuilder(); + + while (true) + { + string arrayElementKey = $"{BindingKey}{fromArrayKey}:{arrayIndex}"; + + if (!ConfigurationData.TryGetValue(arrayElementKey, out string? arrayElementValue)) + { + break; + } + + if (!string.IsNullOrEmpty(arrayElementValue)) + { + if (toValueBuilder.Length > 0) + { + toValueBuilder.Append(separator); + } + + toValueBuilder.Append(arrayElementValue); + } + + arrayIndex++; + } + + if (toValueBuilder.Length > 0) + { + string toValue = toValueBuilder.ToString(); + SetToValue(toKey, toValue); + return toValue; + } + + return null; + } + public string? MapFromAppendTo(string fromKey, string appendToKey, string separator) { string? valueToAppend = GetFromValue(fromKey); diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs index 9ac57c2aab..2fe69a1295 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs @@ -13,7 +13,7 @@ internal abstract class CloudFoundryPostProcessor : IConfigurationPostProcessor public abstract void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData); - protected IEnumerable FilterKeys(IDictionary configurationData, string tagValueToFind) + protected ICollection FilterKeys(IDictionary configurationData, string tagValueToFind) { List keys = new(); diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/PostgreSqlCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/PostgreSqlCloudFoundryPostProcessor.cs index e79160e980..a241a100bd 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/PostgreSqlCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/PostgreSqlCloudFoundryPostProcessor.cs @@ -6,24 +6,54 @@ namespace Steeltoe.Configuration.CloudFoundry.ServiceBinding.PostProcessors; internal sealed class PostgreSqlCloudFoundryPostProcessor : CloudFoundryPostProcessor { + private const string HostsSeparator = ","; + internal const string BindingType = "postgresql"; + internal const string AlternateInputBindingType = "postgres"; public override void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData) { - foreach (string key in FilterKeys(configurationData, BindingType)) + ICollection keys = FilterKeys(configurationData, BindingType); + ICollection alternateKeys = FilterKeys(configurationData, AlternateInputBindingType); + + foreach (string key in keys.Concat(alternateKeys)) { var mapper = ServiceBindingMapper.Create(configurationData, key, BindingType); // Mapping from CloudFoundry service binding credentials to driver-specific connection string parameters. // The available credentials are documented at: + // - VMware Broker: https://docs.vmware.com/en/VMware-Postgres-for-VMware-Tanzu-Application-Service/1.0/postgres/app-dev-guide.html // - Azure Service Broker: https://docs.vmware.com/en/Tanzu-Cloud-Service-Broker-for-Azure/1.4/csb-azure/GUID-reference-azure-postgresql.html#binding-credentials-5 // - GCP Service Broker: https://docs.vmware.com/en/Tanzu-Cloud-Service-Broker-for-GCP/1.2/csb-gcp/GUID-reference-gcp-postgresql.html#binding-credentials-6 // - AWS Service Broker: https://docs.vmware.com/en/Tanzu-Cloud-Service-Broker-for-AWS/1.5/csb-aws/GUID-reference-aws-postgres.html#binding-credentials-3 - mapper.MapFromTo("credentials:hostname", "host"); + string? hosts = mapper.MapArrayFromTo("credentials:hosts", "host", HostsSeparator); + + if (hosts != null) + { + if (hosts.Contains(HostsSeparator, StringComparison.Ordinal)) + { + // https://www.npgsql.org/doc/failover-and-load-balancing.html + mapper.SetToValue("Target Session Attributes", "primary"); + } + } + else + { + mapper.MapFromTo("credentials:hostname", "host"); + } + mapper.MapFromTo("credentials:port", "port"); - mapper.MapFromTo("credentials:name", "database"); - mapper.MapFromTo("credentials:username", "username"); + + if (mapper.MapFromTo("credentials:db", "database") == null) + { + mapper.MapFromTo("credentials:name", "database"); + } + + if (mapper.MapFromTo("credentials:user", "username") == null) + { + mapper.MapFromTo("credentials:username", "username"); + } + mapper.MapFromTo("credentials:password", "password"); mapper.MapFromToFile("credentials:sslcert", "SSL Certificate"); mapper.MapFromToFile("credentials:sslkey", "SSL Key"); diff --git a/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs b/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs index 32a877bf72..ef660bf8ea 100644 --- a/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs +++ b/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs @@ -74,6 +74,37 @@ public void Processes_PostgreSql_configuration() GetFileContentAtKey(configurationData, $"{keyPrefix}:Root Certificate").Should().Be("test-ssl-root-cert"); } + [Fact] + public void Processes_PostgreSql_High_Availability_configuration() + { + var postProcessor = new PostgreSqlCloudFoundryPostProcessor(); + + var secrets = new[] + { + Tuple.Create("credentials:hosts:0", "test-host1"), + Tuple.Create("credentials:hosts:1", "test-host2"), + Tuple.Create("credentials:port", "test-port"), + Tuple.Create("credentials:db", "test-database"), + Tuple.Create("credentials:user", "test-username"), + Tuple.Create("credentials:password", "test-password") + }; + + Dictionary configurationData = + GetConfigurationData(PostgreSqlCloudFoundryPostProcessor.AlternateInputBindingType, TestProviderName, TestBindingName, secrets); + + PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor); + + postProcessor.PostProcessConfiguration(provider, configurationData); + + string keyPrefix = GetOutputKeyPrefix(TestBindingName, PostgreSqlCloudFoundryPostProcessor.BindingType); + configurationData[$"{keyPrefix}:host"].Should().Be("test-host1,test-host2"); + configurationData[$"{keyPrefix}:Target Session Attributes"].Should().Be("primary"); + configurationData[$"{keyPrefix}:port"].Should().Be("test-port"); + configurationData[$"{keyPrefix}:database"].Should().Be("test-database"); + configurationData[$"{keyPrefix}:username"].Should().Be("test-username"); + configurationData[$"{keyPrefix}:password"].Should().Be("test-password"); + } + [Fact] public void Processes_RabbitMQ_configuration() {