Skip to content

Commit

Permalink
Merge pull request #19066 from sgahlot/mongodb-service-binding
Browse files Browse the repository at this point in the history
Introduce support for MongoDB service binding
  • Loading branch information
geoand authored Aug 4, 2021
2 parents 59c9ab8 + f5aa66b commit 4fa14ec
Show file tree
Hide file tree
Showing 33 changed files with 566 additions and 3 deletions.
2 changes: 1 addition & 1 deletion extensions/mongodb-client/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,4 @@
</plugins>
</build>

</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.SslNativeConfigBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem;
import io.quarkus.mongodb.MongoClientName;
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
import io.quarkus.mongodb.runtime.MongoClientBeanUtil;
import io.quarkus.mongodb.runtime.MongoClientRecorder;
import io.quarkus.mongodb.runtime.MongoClientSupport;
import io.quarkus.mongodb.runtime.MongoClients;
import io.quarkus.mongodb.runtime.MongoServiceBindingConverter;
import io.quarkus.mongodb.runtime.MongodbConfig;
import io.quarkus.runtime.metrics.MetricsFactory;
import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem;
Expand All @@ -69,6 +71,8 @@ public class MongoClientProcessor {
private static final DotName MONGO_CLIENT = DotName.createSimple(MongoClient.class.getName());
private static final DotName REACTIVE_MONGO_CLIENT = DotName.createSimple(ReactiveMongoClient.class.getName());

private static final String SERVICE_BINDING_INTERFACE_NAME = "io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter";

@BuildStep
AdditionalIndexedClassesBuildItem includeBsonTypesToIndex() {
return new AdditionalIndexedClassesBuildItem(
Expand Down Expand Up @@ -371,4 +375,13 @@ HealthBuildItem addHealthCheck(MongoClientBuildTimeConfig buildTimeConfig) {
return new HealthBuildItem("io.quarkus.mongodb.health.MongoHealthCheck",
buildTimeConfig.healthEnabled);
}
}

@BuildStep
void registerServiceBinding(Capabilities capabilities, BuildProducer<ServiceProviderBuildItem> buildProducer) {
if (capabilities.isPresent(Capability.KUBERNETES_SERVICE_BINDING)) {
buildProducer.produce(
new ServiceProviderBuildItem(SERVICE_BINDING_INTERFACE_NAME,
MongoServiceBindingConverter.class.getName()));
}
}
}
19 changes: 18 additions & 1 deletion extensions/mongodb-client/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
<groupId>org.mongodb</groupId>
<artifactId>mongodb-crypt</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes-service-binding</artifactId>
<optional>true</optional>
</dependency>

<!-- Add the health extension as optional as we will produce the health check only if it's included -->
<dependency>
Expand Down Expand Up @@ -100,6 +105,18 @@
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

<!--
We need this dependency to make MP context propagation work. As these tests do not
actually start Quarkus the MP CP extension does not run, so it does not exclude the
Expand Down Expand Up @@ -139,4 +156,4 @@
</build>


</project>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package io.quarkus.mongodb.runtime;

import static java.lang.String.format;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.jboss.logging.Logger;

import io.quarkus.kubernetes.service.binding.runtime.ServiceBinding;
import io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConfigSource;
import io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter;

/**
* ServiceBindingConverter for MongoDB to support SBO (Service Binding Operator) in Quarkus.
*
* This class supports both the <b>Standard</b> and <b>SRV</b> connection string format for
* MongoDB depending on whether port is provided or not.
*
* <br>
* Following individual properties are supported to make the connection string:
* <ul>
* <li>username</li>
* <li>password</li>
* <li>host</li>
* <li>port</li>
* <li>database</li>
* </ul>
* <i>Other than host all other properties are optinoal</i>
*
* <br>
* <br>
* Only following options are supported by default:
* <ul>
* <li>retryWrites=true</li>
* <li>w=majority</li>
* </ul>
*
*/
public class MongoServiceBindingConverter implements ServiceBindingConverter {
private static final Logger LOGGER = Logger.getLogger(MongoServiceBindingConverter.class);

private static final String BINDING_TYPE = "mongodb";
public static final String BINDING_CONFIG_SOURCE_NAME = BINDING_TYPE + "-k8s-service-binding-source";
public static final String MONGO_DB_CONNECTION_STRING = "quarkus.mongodb.connection-string";

// DB properties
public static final String DB_USER = "username";
public static final String DB_PASSWORD = "password";
public static final String DB_HOST = "host";
public static final String DB_PORT = "port";
public static final String DB_DATABASE = "database";
public static final String DB_PREFIX_STANDARD = "mongodb://";
public static final String DB_PREFIX_SRV = "mongodb+srv://";
public static final String DB_DEFAULT_OPTIONS = "?retryWrites=true&w=majority";

private static final String CONNECTION_STRING_WITH_USER = "%s%s:%s@%s/%s" + DB_DEFAULT_OPTIONS; // user:pass@hostPort/database
private static final String CONNECTION_STRING_WITH_USER_NO_DB = "%s%s:%s@%s" + DB_DEFAULT_OPTIONS; // user:pass@hostPort
private static final String CONNECTION_STRING_WITHOUT_USER = "%s%s/%s" + DB_DEFAULT_OPTIONS; // hostPort/database
private static final String CONNECTION_STRING_WITHOUT_USER_NO_DB = "%s%s" + DB_DEFAULT_OPTIONS; // hostPort

@Override
public Optional<ServiceBindingConfigSource> convert(List<ServiceBinding> serviceBindings) {
Optional<ServiceBinding> matchingByType = ServiceBinding.singleMatchingByType(BINDING_TYPE, serviceBindings);
if (matchingByType.isEmpty()) {
return Optional.empty();
}

Map<String, String> properties = new HashMap<>();

properties.put(MONGO_DB_CONNECTION_STRING, getConnectionString(matchingByType.get()));

return Optional.of(new ServiceBindingConfigSource(BINDING_CONFIG_SOURCE_NAME, properties));
}

private String getConnectionString(ServiceBinding binding) {
String user = getDbProperty(binding, DB_USER);
String password = getDbProperty(binding, DB_PASSWORD);
String hostPort = getHostPort(binding);
String database = getDbProperty(binding, DB_DATABASE);
String prefix = isPortProvided(hostPort) ? DB_PREFIX_STANDARD : DB_PREFIX_SRV;

String connectionString;
if (isBlank(user)) {
if (isBlank(database)) {
connectionString = format(CONNECTION_STRING_WITHOUT_USER_NO_DB, prefix, hostPort);
} else {
connectionString = format(CONNECTION_STRING_WITHOUT_USER, prefix, hostPort, database);
}
} else {
if (isBlank(database)) {
connectionString = format(CONNECTION_STRING_WITH_USER_NO_DB, prefix, user, password, hostPort);
} else {
connectionString = format(CONNECTION_STRING_WITH_USER, prefix, user, password, hostPort, database);
}
}

LOGGER.info(format("MongoDB connection string: [%s]", connectionString));
return connectionString;
}

private String getDbProperty(ServiceBinding binding, String dbPropertyKey) {
String dbPropertyValue = binding.getProperties().get(dbPropertyKey);
if (isBlank(dbPropertyValue)) {
LOGGER.debug(format("Property '%s' not found", dbPropertyKey));
}

return dbPropertyValue;
}

private String getHostPort(ServiceBinding binding) {
String host = getDbProperty(binding, DB_HOST);
String port = getDbProperty(binding, DB_PORT);

String hostPort = host;
if (isNotBlank(host)) {
if (isNotBlank(port)) {
hostPort = host + ":" + port;
}
} else {
LOGGER.warn("Unable to get the host property. Connection string won't be correct");
}

return hostPort;
}

private boolean isPortProvided(String hostPort) {
return hostPort != null && hostPort.contains(":");
}

private boolean isBlank(String value) {
return value == null || value.trim().length() == 0;
}

private boolean isNotBlank(String value) {
return !isBlank(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.quarkus.mongodb.runtime.MongoServiceBindingConverter
Loading

0 comments on commit 4fa14ec

Please sign in to comment.