-
Notifications
You must be signed in to change notification settings - Fork 328
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce EnvironmentPostProcessor for R2DBC support in Cloud Sql (#772)
This PR adds two starters for R2DBC support in Cloud SQL (one for mySQL and another for postgreSQL). It also includes 2 samples, associated integrations tests and documentation. The starters bring in spring-boot-starter-data-r2dbc which takes care of the creation of the ConnectionFactory bean.
- Loading branch information
Showing
34 changed files
with
1,055 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
...onfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/PropertiesRetriever.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* | ||
* Copyright 2021-2022 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.cloud.spring.autoconfigure.sql; | ||
|
||
import com.google.cloud.spring.autoconfigure.core.GcpProperties; | ||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
import org.springframework.boot.context.properties.bind.Binder; | ||
import org.springframework.boot.context.properties.bind.PlaceholdersResolver; | ||
import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; | ||
import org.springframework.boot.context.properties.source.ConfigurationPropertySources; | ||
import org.springframework.core.env.Environment; | ||
|
||
/** | ||
* Helper class to derive Cloud SQL and GCP properties from the user configuration | ||
* (application.properties, for example). | ||
*/ | ||
class PropertiesRetriever { | ||
|
||
private Binder binder; | ||
|
||
PropertiesRetriever(Environment environment) { | ||
// Bind properties without resolving Secret Manager placeholders | ||
this.binder = | ||
new Binder( | ||
ConfigurationPropertySources.get(environment), | ||
new NonSecretsManagerPropertiesPlaceholdersResolver(environment), | ||
null, | ||
null, | ||
null); | ||
} | ||
|
||
GcpCloudSqlProperties getCloudSqlProperties() { | ||
String cloudSqlPropertiesPrefix = | ||
GcpCloudSqlProperties.class.getAnnotation(ConfigurationProperties.class).value(); | ||
return this.binder | ||
.bind(cloudSqlPropertiesPrefix, GcpCloudSqlProperties.class) | ||
.orElse(new GcpCloudSqlProperties()); | ||
} | ||
|
||
GcpProperties getGcpProperties() { | ||
String gcpPropertiesPrefix = | ||
GcpProperties.class.getAnnotation(ConfigurationProperties.class).value(); | ||
return this.binder.bind(gcpPropertiesPrefix, GcpProperties.class).orElse(new GcpProperties()); | ||
} | ||
|
||
private static class NonSecretsManagerPropertiesPlaceholdersResolver | ||
implements PlaceholdersResolver { | ||
private PlaceholdersResolver resolver; | ||
|
||
NonSecretsManagerPropertiesPlaceholdersResolver(Environment environment) { | ||
this.resolver = new PropertySourcesPlaceholdersResolver(environment); | ||
} | ||
|
||
@Override | ||
public Object resolvePlaceholders(Object value) { | ||
if (value.toString().contains("sm://")) { | ||
return value; | ||
} else { | ||
return resolver.resolvePlaceholders(value); | ||
} | ||
} | ||
} | ||
} |
115 changes: 115 additions & 0 deletions
115
...java/com/google/cloud/spring/autoconfigure/sql/R2dbcCloudSqlEnvironmentPostProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* | ||
* Copyright 2021-2022 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.cloud.spring.autoconfigure.sql; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import org.apache.commons.logging.Log; | ||
import org.apache.commons.logging.LogFactory; | ||
import org.springframework.boot.SpringApplication; | ||
import org.springframework.boot.env.EnvironmentPostProcessor; | ||
import org.springframework.core.env.ConfigurableEnvironment; | ||
import org.springframework.core.env.MapPropertySource; | ||
import org.springframework.util.Assert; | ||
import org.springframework.util.ClassUtils; | ||
|
||
/** | ||
* Builds connection string for Cloud SQL through Spring R2DBC by requiring only a database and | ||
* instance connection name. | ||
*/ | ||
public class R2dbcCloudSqlEnvironmentPostProcessor implements EnvironmentPostProcessor { | ||
private static final Log LOGGER = LogFactory.getLog(R2dbcCloudSqlEnvironmentPostProcessor.class); | ||
|
||
@Override | ||
public void postProcessEnvironment( | ||
ConfigurableEnvironment environment, SpringApplication application) { | ||
if (environment.getPropertySources().contains("bootstrap")) { | ||
// Do not run in the bootstrap phase as the user configuration is not available yet | ||
return; | ||
} | ||
|
||
DatabaseType databaseType = getEnabledDatabaseType(environment); | ||
if (databaseType != null) { | ||
PropertiesRetriever propertiesRetriever = new PropertiesRetriever(environment); | ||
String r2dbcUrl = createUrl(databaseType, propertiesRetriever.getCloudSqlProperties()); | ||
if (LOGGER.isInfoEnabled()) { | ||
LOGGER.info( | ||
"Default " + databaseType.name() + " R2dbcUrl provider. Connecting to " + r2dbcUrl); | ||
} | ||
|
||
// Add default username as fallback when not specified | ||
Map<String, Object> fallbackMap = new HashMap<>(); | ||
fallbackMap.put("spring.r2dbc.username", databaseType.getDefaultUsername()); | ||
environment | ||
.getPropertySources() | ||
.addLast(new MapPropertySource("CLOUD_SQL_R2DBC_USERNAME", fallbackMap)); | ||
|
||
Map<String, Object> primaryMap = new HashMap<>(); | ||
primaryMap.put("spring.r2dbc.url", r2dbcUrl); | ||
environment | ||
.getPropertySources() | ||
.addFirst(new MapPropertySource("CLOUD_SQL_R2DBC_URL", primaryMap)); | ||
} | ||
} | ||
|
||
String createUrl(DatabaseType databaseType, GcpCloudSqlProperties sqlProperties) { | ||
Assert.hasText(sqlProperties.getDatabaseName(), "A database name must be provided."); | ||
Assert.hasText( | ||
sqlProperties.getInstanceConnectionName(), | ||
"An instance connection name must be provided in the format" | ||
+ " <PROJECT_ID>:<REGION>:<INSTANCE_ID>."); | ||
|
||
return String.format( | ||
databaseType.getR2dbcUrlTemplate(), | ||
sqlProperties.getInstanceConnectionName(), | ||
sqlProperties.getDatabaseName()); | ||
} | ||
|
||
/** | ||
* Returns {@link DatabaseType} constant based on whether mySQL or postgreSQL R2DBC driver and | ||
* connector dependencies are present on the classpath. Returns null if Cloud SQL is not enabled | ||
* in Spring Cloud GCP, CredentialFactory is not present or ConnectionFactory (which is used to | ||
* enable Spring R2DBC auto-configuration) is not present. | ||
* | ||
* @param environment environment to post-process | ||
* @return database type | ||
*/ | ||
DatabaseType getEnabledDatabaseType(ConfigurableEnvironment environment) { | ||
if (isR2dbcEnabled(environment) | ||
&& isOnClasspath("com.google.cloud.sql.CredentialFactory") | ||
&& isOnClasspath("io.r2dbc.spi.ConnectionFactory")) { | ||
if (isOnClasspath("com.google.cloud.sql.core.GcpConnectionFactoryProviderMysql") | ||
&& isOnClasspath("dev.miku.r2dbc.mysql.MySqlConnectionFactoryProvider")) { | ||
return DatabaseType.MYSQL; | ||
} else if (isOnClasspath("com.google.cloud.sql.core.GcpConnectionFactoryProviderPostgres") | ||
&& isOnClasspath("io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider")) { | ||
return DatabaseType.POSTGRESQL; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
private boolean isOnClasspath(String className) { | ||
return ClassUtils.isPresent(className, null); | ||
} | ||
|
||
private boolean isR2dbcEnabled(ConfigurableEnvironment environment) { | ||
return Boolean.parseBoolean(environment.getProperty("spring.cloud.gcp.sql.enabled", "true")) | ||
&& Boolean.parseBoolean( | ||
environment.getProperty("spring.cloud.gcp.sql.r2dbc.enabled", "true")); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.