Skip to content

Commit

Permalink
Merge pull request #1119 from SteeltoeOSS/mongodb-connector
Browse files Browse the repository at this point in the history
MongoDB connector support, based on new CloudFoundry service bindings
  • Loading branch information
bart-vmware authored Apr 13, 2023
2 parents a60df30 + b962002 commit 9e2cb52
Show file tree
Hide file tree
Showing 33 changed files with 910 additions and 204 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ private static IConfigurationBuilder RegisterPostProcessors(IConfigurationBuilde
source.RegisterPostProcessor(new PostgreSqlPostProcessor());
source.RegisterPostProcessor(new MySqlPostProcessor());
source.RegisterPostProcessor(new SqlServerPostProcessor());
source.RegisterPostProcessor(new MongoDbPostProcessor());

builder.Add(source);
return builder;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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.ServiceBinding;

internal sealed class MongoDbPostProcessor : CloudFoundryConfigurationPostProcessor
{
internal const string BindingType = "mongodb";

public override void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary<string, string> configurationData)
{
if (!provider.IsBindingTypeEnabled(BindingType))
{
return;
}

foreach (string key in FilterKeys(configurationData, BindingType))
{
var mapper = ServiceBindingMapper.Create(configurationData, key, BindingType);

// See MongoDB connection string parameters at: https://www.mongodb.com/docs/manual/reference/connection-string/
mapper.MapFromTo("credentials:uri", "url");

if (mapper.BindingProvider == "csb-azure-mongodb")
{
// The MongoDB Azure Service Broker solely provides an url, which contains the authentication database.
// We extract this and expose it as the database used for application data as well.
string url = mapper.GetFromValue("credentials:uri");
var uri = new Uri(url);
string database = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped);

mapper.SetToValue("database", database);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -295,19 +295,17 @@ public void PostProcessConfiguration(PostProcessorConfigurationProvider provider
configurationData.Filter(ServiceBindingConfigurationProvider.InputKeyPrefix, ServiceBindingConfigurationProvider.TypeKey, BindingType).ForEach(
bindingNameKey =>
{
// Spring -> spring.data.mongodb....
// Steeltoe -> steeltoe:service-bindings:mongodb....
var mapper = new ServiceBindingMapper(configurationData, bindingNameKey, ServiceBindingConfigurationProvider.OutputKeyPrefix, BindingType,
ConfigurationPath.GetSectionKey(bindingNameKey));

mapper.MapFromTo("authentication-database", "authenticationDatabase");
mapper.MapFromTo("database", "database");
mapper.MapFromTo("grid-fs-database", "gridfsDatabase");
mapper.MapFromTo("host", "host");
// See MongoDB connection string parameters at: https://www.mongodb.com/docs/manual/reference/connection-string/
mapper.MapFromTo("uri", "url");
mapper.MapFromTo("username", "username");
mapper.MapFromTo("password", "password");
mapper.MapFromTo("host", "server");
mapper.MapFromTo("port", "port");
mapper.MapFromTo("uri", "uri");
mapper.MapFromTo("username", "username");
mapper.MapFromTo("authentication-database", "authenticationDatabase");
mapper.MapFromTo("database", "database");
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,43 @@ public void SqlServerTest_BindingTypeEnabled()
configurationData[$"{keyPrefix}:User ID"].Should().Be("test-username");
configurationData[$"{keyPrefix}:Password"].Should().Be("test-password");
}

[Fact]
public void MongoDbTest_BindingTypeDisabled()
{
var postProcessor = new MongoDbPostProcessor();

var secrets = new[]
{
Tuple.Create("credentials:uri", "test-uri")
};

Dictionary<string, string> configurationData = GetConfigurationData(MongoDbPostProcessor.BindingType, TestProviderName, TestBindingName, secrets);
PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor, MongoDbPostProcessor.BindingType, false);

postProcessor.PostProcessConfiguration(provider, configurationData);

string keyPrefix = GetOutputKeyPrefix(TestBindingName, MongoDbPostProcessor.BindingType);
configurationData.Should().NotContainKey($"{keyPrefix}:url");
}

[Fact]
public void MongoDbTest_BindingTypeEnabled()
{
var postProcessor = new MongoDbPostProcessor();

var secrets = new[]
{
Tuple.Create("credentials:uri", "mongodb://localhost:27017/auth-db?appname=sample")
};

Dictionary<string, string> configurationData = GetConfigurationData(MongoDbPostProcessor.BindingType, "csb-azure-mongodb", TestBindingName, secrets);
PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor, MongoDbPostProcessor.BindingType, true);

postProcessor.PostProcessConfiguration(provider, configurationData);

string keyPrefix = GetOutputKeyPrefix(TestBindingName, MongoDbPostProcessor.BindingType);
configurationData[$"{keyPrefix}:url"].Should().Be("mongodb://localhost:27017/auth-db?appname=sample");
configurationData[$"{keyPrefix}:database"].Should().Be("auth-db");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -479,13 +479,6 @@ public void MongoDbTest_BindingTypeDisabled()

var secrets = new[]
{
Tuple.Create("authentication-database", "test-authentication-database"),
Tuple.Create("database", "test-database"),
Tuple.Create("grid-fs-database", "test-grid-fs-database"),
Tuple.Create("host", "test-host"),
Tuple.Create("password", "test-password"),
Tuple.Create("port", "test-port"),
Tuple.Create("uri", "test-uri"),
Tuple.Create("username", "test-username")
};

Expand All @@ -495,7 +488,7 @@ public void MongoDbTest_BindingTypeDisabled()
postProcessor.PostProcessConfiguration(provider, configurationData);

string keyPrefix = GetOutputKeyPrefix(TestBindingName, MongoDbPostProcessor.BindingType);
configurationData.Should().NotContainKey($"{keyPrefix}:authenticationDatabase");
configurationData.Should().NotContainKey($"{keyPrefix}:username");
}

[Fact]
Expand All @@ -505,14 +498,13 @@ public void MongoDbTest_BindingTypeEnabled()

var secrets = new[]
{
Tuple.Create("authentication-database", "test-authentication-database"),
Tuple.Create("database", "test-database"),
Tuple.Create("grid-fs-database", "test-grid-fs-database"),
Tuple.Create("host", "test-host"),
Tuple.Create("uri", "test-uri"),
Tuple.Create("username", "test-username"),
Tuple.Create("password", "test-password"),
Tuple.Create("host", "test-host"),
Tuple.Create("port", "test-port"),
Tuple.Create("uri", "test-uri"),
Tuple.Create("username", "test-username")
Tuple.Create("authentication-database", "test-authentication-database"),
Tuple.Create("database", "test-database")
};

Dictionary<string, string> configurationData = GetConfigurationData(TestBindingName, MongoDbPostProcessor.BindingType, secrets);
Expand All @@ -522,14 +514,13 @@ public void MongoDbTest_BindingTypeEnabled()

string keyPrefix = GetOutputKeyPrefix(TestBindingName, MongoDbPostProcessor.BindingType);

configurationData[$"{keyPrefix}:authenticationDatabase"].Should().Be("test-authentication-database");
configurationData[$"{keyPrefix}:database"].Should().Be("test-database");
configurationData[$"{keyPrefix}:gridfsDatabase"].Should().Be("test-grid-fs-database");
configurationData[$"{keyPrefix}:host"].Should().Be("test-host");
configurationData[$"{keyPrefix}:url"].Should().Be("test-uri");
configurationData[$"{keyPrefix}:username"].Should().Be("test-username");
configurationData[$"{keyPrefix}:password"].Should().Be("test-password");
configurationData[$"{keyPrefix}:server"].Should().Be("test-host");
configurationData[$"{keyPrefix}:port"].Should().Be("test-port");
configurationData[$"{keyPrefix}:uri"].Should().Be("test-uri");
configurationData[$"{keyPrefix}:username"].Should().Be("test-username");
configurationData[$"{keyPrefix}:authenticationDatabase"].Should().Be("test-authentication-database");
configurationData[$"{keyPrefix}:database"].Should().Be("test-database");
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public static void RegisterConfigurationSource(IConfigurationBuilder configurati
configurationBuilder.Add(source);
}

public static void RegisterNamedOptions<TOptions>(WebApplicationBuilder builder, string bindingType, Type connectionType, string healthDisplayName,
string healthHostNameKey)
public static void RegisterNamedOptions<TOptions>(WebApplicationBuilder builder, string bindingType,
Func<IServiceProvider, string, IHealthContributor> createHealthContributor)
where TOptions : ConnectionStringOptions
{
string key = ConfigurationPath.Combine(ConnectionStringPostProcessor.ServiceBindingsConfigurationKey, bindingType);
Expand All @@ -40,13 +40,13 @@ public static void RegisterNamedOptions<TOptions>(WebApplicationBuilder builder,

if (registerDefaultHealthContributor)
{
RegisterHealthContributor<TOptions>(builder.Services, string.Empty, connectionType, healthDisplayName, healthHostNameKey);
RegisterHealthContributor(builder.Services, string.Empty, createHealthContributor);
}
}
else
{
builder.Services.Configure<TOptions>(bindingName, child);
RegisterHealthContributor<TOptions>(builder.Services, bindingName, connectionType, healthDisplayName, healthHostNameKey);
RegisterHealthContributor(builder.Services, bindingName, createHealthContributor);
}
}
}
Expand All @@ -57,19 +57,17 @@ private static bool ShouldRegisterDefaultHealthContributor(IConfigurationSection
return childSections.Length == 1 && childSections[0].Key == ConnectionStringPostProcessor.DefaultBindingName;
}

private static void RegisterHealthContributor<TOptions>(IServiceCollection services, string bindingName, Type connectionType, string healthDisplayName,
string healthHostNameKey)
where TOptions : ConnectionStringOptions
private static void RegisterHealthContributor(IServiceCollection services, string bindingName,
Func<IServiceProvider, string, IHealthContributor> createHealthContributor)
{
services.AddSingleton(typeof(IHealthContributor),
serviceProvider => CreateHealthContributor<TOptions>(serviceProvider, bindingName, connectionType, healthDisplayName, healthHostNameKey));
services.AddSingleton(typeof(IHealthContributor), serviceProvider => createHealthContributor(serviceProvider, bindingName));
}

private static IHealthContributor CreateHealthContributor<TOptions>(IServiceProvider serviceProvider, string bindingName, Type connectionType,
public static IHealthContributor CreateRelationalHealthContributor<TOptions>(IServiceProvider serviceProvider, string bindingName, Type connectionType,
string healthDisplayName, string healthHostNameKey)
where TOptions : ConnectionStringOptions
{
IDbConnection connection = ConnectionFactoryInvoker.CreateConnection<TOptions>(serviceProvider, bindingName, connectionType);
var connection = (IDbConnection)ConnectionFactoryInvoker.CreateConnection<TOptions>(serviceProvider, bindingName, connectionType);
string serviceName = $"{healthDisplayName}-{bindingName}";
string hostName = GetHostNameFromConnectionString(connection.ConnectionString, healthHostNameKey);
var logger = serviceProvider.GetRequiredService<ILogger<RelationalDbHealthContributor>>();
Expand Down
56 changes: 8 additions & 48 deletions src/Connectors/src/Connector/ConnectionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Steeltoe.Common;

namespace Steeltoe.Connector;
Expand Down Expand Up @@ -33,65 +31,27 @@ public ConnectionFactory(IServiceProvider serviceProvider, Func<string, object>
}

/// <summary>
/// Creates a new connection for the default service binding, in case only one binding exists.
/// Gets a connection provider for the default service binding. Only use this if a single binding exists.
/// </summary>
/// <returns>
/// A new connection. Throws when the connection string is unavailable.
/// The connection provider.
/// </returns>
public TConnection GetDefaultConnection()
public ConnectionProvider<TOptions, TConnection> GetDefault()
{
return GetConnection(string.Empty);
return new ConnectionProvider<TOptions, TConnection>(_serviceProvider, string.Empty, _createConnection);
}

/// <summary>
/// Creates a new connection for the specified service binding name.
/// Gets a connection provider for the specified service binding name.
/// </summary>
/// <param name="name">
/// The service binding name.
/// </param>
/// <returns>
/// A new connection. Throws when the connection string is unavailable.
/// The connection provider.
/// </returns>
public TConnection GetConnection(string name)
public ConnectionProvider<TOptions, TConnection> GetNamed(string name)
{
string connectionString = GetConnectionString(name);

if (connectionString == null)
{
throw name == string.Empty
? new InvalidOperationException("Default connection string not found.")
: new InvalidOperationException($"Connection string for '{name}' not found.");
}

object connection = _createConnection(connectionString);
return (TConnection)connection;
}

/// <summary>
/// Gets the connection string for the default service binding, in case only one binding exists.
/// </summary>
/// <returns>
/// The connection string, or <c>null</c> if not found.
/// </returns>
public string GetDefaultConnectionString()
{
return GetConnectionString(string.Empty);
}

/// <summary>
/// Gets the connection string for the specified service binding name.
/// </summary>
/// <param name="name">
/// The service binding name.
/// </param>
/// <returns>
/// The connection string, or <c>null</c> if not found.
/// </returns>
public string GetConnectionString(string name)
{
var optionsMonitor = _serviceProvider.GetRequiredService<IOptionsMonitor<TOptions>>();
TOptions options = optionsMonitor.Get(name);

return options.ConnectionString;
return new ConnectionProvider<TOptions, TConnection>(_serviceProvider, name, _createConnection);
}
}
Loading

0 comments on commit 9e2cb52

Please sign in to comment.