From bc395bdb6d5a6ebaf14bce1e355d981b7c591964 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Tue, 17 Nov 2020 17:43:48 -0500 Subject: [PATCH 1/6] Replaces CloudSqlAutoConfiguration with CloudSqlEnvironmentPostProcessor Fixes: https://github.com/spring-cloud/spring-cloud-gcp/issues/272 --- .../sql/CloudSqlEnvironmentPostProcessor.java | 174 ++++++++++++ .../sql/DefaultCloudSqlJdbcInfoProvider.java | 26 +- .../sql/GcpCloudSqlAutoConfiguration.java | 266 ------------------ .../sql/GcpCloudSqlProperties.java | 74 ----- .../main/resources/META-INF/spring.factories | 58 ++-- ...loudSqlEnvironmentPostProcessorTests.java} | 93 +++--- .../sql/GcpCloudSqlTestConfiguration.java | 39 --- .../src/main/resources/application.properties | 10 +- 8 files changed, 269 insertions(+), 471 deletions(-) create mode 100644 spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java delete mode 100644 spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlAutoConfiguration.java delete mode 100644 spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlProperties.java rename spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/{GcpCloudSqlAutoConfigurationMockTests.java => CloudSqlEnvironmentPostProcessorTests.java} (71%) delete mode 100644 spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlTestConfiguration.java diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java new file mode 100644 index 0000000000..feb0700d72 --- /dev/null +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java @@ -0,0 +1,174 @@ +package com.google.cloud.spring.autoconfigure.sql; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import com.google.cloud.spring.autoconfigure.core.GcpProperties; +import com.google.cloud.spring.core.Credentials; +import com.google.cloud.sql.CredentialFactory; +import com.google.cloud.sql.core.CoreSocketFactory; +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.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Provides Google Cloud SQL instance connectivity through Spring JDBC by providing only a + * database and instance connection name. + * + * @author João André Martins + * @author Artem Bilan + * @author Mike Eltsufin + * @author Chengyuan Zhao + * @author Eddú Meléndez + */ +public class CloudSqlEnvironmentPostProcessor implements EnvironmentPostProcessor { + private final static String CLOUD_SQL_PROPERTIES_PREFIX = "spring.cloud.gcp.sql."; + + private static final Log LOGGER = + LogFactory.getLog(CloudSqlEnvironmentPostProcessor.class); + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + if (Boolean.parseBoolean(getSqlProperty(environment, "enabled", "true")) + && isOnClasspath("javax.sql.DataSource") + && isOnClasspath("org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType") + && isOnClasspath("com.google.cloud.sql.CredentialFactory")) { + + DatabaseType databaseType = null; + if (isOnClasspath("com.google.cloud.sql.mysql.SocketFactory") + && isOnClasspath("com.mysql.cj.jdbc.Driver")) { + databaseType = DatabaseType.MYSQL; + } + else if (isOnClasspath("com.google.cloud.sql.postgres.SocketFactory") + && isOnClasspath("org.postgresql.Driver")) { + databaseType = DatabaseType.POSTGRESQL; + } + + if (databaseType != null) { + LOGGER.info("post-processing Cloud SQL properties"); + + CloudSqlJdbcInfoProvider cloudSqlJdbcInfoProvider = new DefaultCloudSqlJdbcInfoProvider( + getSqlProperty(environment, "database-name", null), + getSqlProperty(environment, "instance-connection-name", null), + getSqlProperty(environment, "ip-types", null), + databaseType); + + // configure default JDBC driver and username as fallback values when not specified + Map fallbackMap = new HashMap<>(); + fallbackMap.put("spring.datasource.username", "root"); + fallbackMap.put("spring.datasource.driver-class-name", cloudSqlJdbcInfoProvider.getJdbcDriverClass()); + environment.getPropertySources() + .addLast(new MapPropertySource("CLOUD_SQL_DATA_SOURCE_FALLBACK", fallbackMap)); + + // always set the spring.datasource.url property in the environment + Map primaryMap = new HashMap<>(); + primaryMap.put("spring.datasource.url", cloudSqlJdbcInfoProvider.getJdbcUrl()); + environment.getPropertySources() + .addFirst(new MapPropertySource("CLOUD_SQL_DATA_SOURCE_URL", primaryMap)); + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Default " + databaseType.name() + + " JdbcUrl provider. Connecting to " + + cloudSqlJdbcInfoProvider.getJdbcUrl() + " with driver " + + cloudSqlJdbcInfoProvider.getJdbcDriverClass()); + } + + String encodedKey = getSqlProperty(environment,"credentials.encoded-key", null); + if (encodedKey != null) { + setCredentialsEncodedKeyProperty(encodedKey); + } else { + setCredentialsFileProperty(environment, application); + } + + CoreSocketFactory.setApplicationName("spring-cloud-gcp-sql/" + + this.getClass().getPackage().getImplementationVersion()); + } + } + } + + private String getSqlProperty(ConfigurableEnvironment environment, String shortName, String defaultValue) { + return environment.getProperty(CLOUD_SQL_PROPERTIES_PREFIX + shortName, defaultValue); + } + + private boolean isOnClasspath(String className) { + try { + ClassUtils.forName(className, (ClassLoader) null); + return true; + } catch (ClassNotFoundException ex) { + return false; + } + } + + private void setCredentialsEncodedKeyProperty(String encodedKey) { + System.setProperty(SqlCredentialFactory.CREDENTIAL_ENCODED_KEY_PROPERTY_NAME, + encodedKey); + + System.setProperty(CredentialFactory.CREDENTIAL_FACTORY_PROPERTY, + SqlCredentialFactory.class.getName()); + } + + /** + * Set credentials to be used by the Google Cloud SQL socket factory. + * + *

The only way to pass a {@link CredentialFactory} to the socket factory is by passing a + * class name through a system property. The socket factory creates an instance of + * {@link CredentialFactory} using reflection without any arguments. Because of that, the + * credential location needs to be stored somewhere where the class can read it without + * any context. It could be possible to pass in a Spring context to + * {@link SqlCredentialFactory}, but this is a tricky solution that needs some thinking + * about. + * + *

If user didn't specify credentials, the socket factory already does the right thing by + * using the application default credentials by default. So we don't need to do anything. + */ + private void setCredentialsFileProperty(ConfigurableEnvironment environment, SpringApplication application) { + File credentialsLocationFile; + + try { + String sqlCredentialsLocation = getSqlProperty(environment,"credentials.location", null); + String globalCredentialsLocation = environment.getProperty("spring.cloud.gcp.credentials.location", (String) null); + // First tries the SQL configuration credential. + if (sqlCredentialsLocation != null) { + credentialsLocationFile = application.getResourceLoader().getResource(sqlCredentialsLocation).getFile(); + setSystemProperties(credentialsLocationFile); + } + // Then, the global credential. + else if (globalCredentialsLocation != null) { + // A resource might not be in the filesystem, but the Cloud SQL credential must. + credentialsLocationFile = application.getResourceLoader().getResource(globalCredentialsLocation).getFile(); + setSystemProperties(credentialsLocationFile); + } + + // Else do nothing, let sockets factory use application default credentials. + + } + catch (IOException ioe) { + LOGGER.info("Error reading Cloud SQL credentials file.", ioe); + } + } + + private void setSystemProperties(File credentialsLocationFile) { + // This should happen if the Spring resource isn't in the filesystem, but a URL, + // classpath file, etc. + if (credentialsLocationFile == null) { + LOGGER.info("The private key of the Google Cloud SQL credential must " + + "be in a file on the filesystem."); + return; + } + + System.setProperty(SqlCredentialFactory.CREDENTIAL_LOCATION_PROPERTY_NAME, + credentialsLocationFile.getAbsolutePath()); + + // If there are specified credentials, tell sockets factory to use them. + System.setProperty(CredentialFactory.CREDENTIAL_FACTORY_PROPERTY, + SqlCredentialFactory.class.getName()); + } +} diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/DefaultCloudSqlJdbcInfoProvider.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/DefaultCloudSqlJdbcInfoProvider.java index 608f83e083..fcfc187275 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/DefaultCloudSqlJdbcInfoProvider.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/DefaultCloudSqlJdbcInfoProvider.java @@ -27,19 +27,21 @@ * @author Ray Tsang * @author João André Martins * @author Øystein Urdahl Hardeng + * @author Mike Eltsufin */ public class DefaultCloudSqlJdbcInfoProvider implements CloudSqlJdbcInfoProvider { - - private final GcpCloudSqlProperties properties; - + private final String databaseName; + private final String instanceConnectionName; + private final String ipTypes; private final DatabaseType databaseType; - public DefaultCloudSqlJdbcInfoProvider(GcpCloudSqlProperties properties, - DatabaseType databaseType) { - this.properties = properties; + public DefaultCloudSqlJdbcInfoProvider(String databaseName, String instanceConnectionName, String ipTypes, DatabaseType databaseType) { + this.databaseName = databaseName; + this.instanceConnectionName = instanceConnectionName; + this.ipTypes = ipTypes; this.databaseType = databaseType; - Assert.hasText(this.properties.getDatabaseName(), "A database name must be provided."); - Assert.hasText(properties.getInstanceConnectionName(), + Assert.hasText(this.databaseName, "A database name must be provided."); + Assert.hasText(this.instanceConnectionName, "An instance connection name must be provided in the format ::."); } @@ -51,10 +53,10 @@ public String getJdbcDriverClass() { @Override public String getJdbcUrl() { String jdbcUrl = String.format(this.databaseType.getJdbcUrlTemplate(), - this.properties.getDatabaseName(), - this.properties.getInstanceConnectionName()); - if (StringUtils.hasText(properties.getIpTypes())) { - jdbcUrl = String.format(jdbcUrl + "&ipTypes=%s", properties.getIpTypes()); + this.databaseName, + this.instanceConnectionName); + if (StringUtils.hasText(this.ipTypes)) { + jdbcUrl = String.format(jdbcUrl + "&ipTypes=%s", this.ipTypes); } return jdbcUrl; } diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlAutoConfiguration.java deleted file mode 100644 index 2a6c1ff05b..0000000000 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlAutoConfiguration.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright 2017-2019 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.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -import javax.sql.DataSource; - -import com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration; -import com.google.cloud.spring.autoconfigure.core.GcpProperties; -import com.google.cloud.spring.core.Credentials; -import com.google.cloud.sql.CredentialFactory; -import com.google.cloud.sql.core.CoreSocketFactory; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.Primary; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.MapPropertySource; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.util.StringUtils; - -/** - * Provides Google Cloud SQL instance connectivity through Spring JDBC by providing only a - * database and instance connection name. - * - * @author João André Martins - * @author Artem Bilan - * @author Mike Eltsufin - * @author Chengyuan Zhao - * @author Eddú Meléndez - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class, CredentialFactory.class }) -@ConditionalOnProperty( - name = "spring.cloud.gcp.sql.enabled", havingValue = "true", matchIfMissing = true) -@EnableConfigurationProperties({ GcpCloudSqlProperties.class, DataSourceProperties.class }) -@AutoConfigureBefore({ - DataSourceAutoConfiguration.class, - JndiDataSourceAutoConfiguration.class, - XADataSourceAutoConfiguration.class }) -@AutoConfigureAfter(GcpContextAutoConfiguration.class) -public class GcpCloudSqlAutoConfiguration { //NOSONAR squid:S1610 must be a class for Spring - - private static final Log LOGGER = LogFactory.getLog(GcpCloudSqlAutoConfiguration.class); - - - /** - * The MySql Configuration for the {@link DefaultCloudSqlJdbcInfoProvider} - * based on the {@link DatabaseType#MYSQL}. - */ - @ConditionalOnClass({ com.google.cloud.sql.mysql.SocketFactory.class, - com.mysql.cj.jdbc.Driver.class }) - @ConditionalOnMissingBean(CloudSqlJdbcInfoProvider.class) - static class MySqlJdbcInfoProviderConfiguration { - - @Bean - public CloudSqlJdbcInfoProvider defaultMySqlJdbcInfoProvider( - GcpCloudSqlProperties gcpCloudSqlProperties) { - CloudSqlJdbcInfoProvider defaultProvider = - new DefaultCloudSqlJdbcInfoProvider(gcpCloudSqlProperties, DatabaseType.MYSQL); - - if (LOGGER.isInfoEnabled()) { - LOGGER.info("Default " + DatabaseType.MYSQL.name() - + " JdbcUrl provider. Connecting to " - + defaultProvider.getJdbcUrl() + " with driver " - + defaultProvider.getJdbcDriverClass()); - } - - return defaultProvider; - } - - } - - /** - * The PostgreSql Configuration for the {@link DefaultCloudSqlJdbcInfoProvider} - * based on the {@link DatabaseType#POSTGRESQL}. - */ - @ConditionalOnClass({ com.google.cloud.sql.postgres.SocketFactory.class, - org.postgresql.Driver.class }) - @ConditionalOnMissingBean(CloudSqlJdbcInfoProvider.class) - static class PostgreSqlJdbcInfoProviderConfiguration { - - @Bean - public CloudSqlJdbcInfoProvider defaultPostgreSqlJdbcInfoProvider( - GcpCloudSqlProperties gcpCloudSqlProperties) { - CloudSqlJdbcInfoProvider defaultProvider = new DefaultCloudSqlJdbcInfoProvider( - gcpCloudSqlProperties, DatabaseType.POSTGRESQL); - - if (LOGGER.isInfoEnabled()) { - LOGGER.info("Default " + DatabaseType.POSTGRESQL.name() - + " JdbcUrl provider. Connecting to " - + defaultProvider.getJdbcUrl() + " with driver " - + defaultProvider.getJdbcDriverClass()); - } - - return defaultProvider; - } - - } - - /** - * The Configuration to populated {@link DataSourceProperties} bean - * based on the cloud-specific properties. - */ - @Configuration(proxyBeanMethods = false) - @Import({GcpCloudSqlAutoConfiguration.MySqlJdbcInfoProviderConfiguration.class, - GcpCloudSqlAutoConfiguration.PostgreSqlJdbcInfoProviderConfiguration.class }) - static class CloudSqlDataSourcePropertiesConfiguration { - - @Bean - @Primary - @ConditionalOnBean(CloudSqlJdbcInfoProvider.class) - public DataSourceProperties cloudSqlDataSourceProperties( - GcpCloudSqlProperties gcpCloudSqlProperties, - DataSourceProperties properties, - GcpProperties gcpProperties, - CloudSqlJdbcInfoProvider cloudSqlJdbcInfoProvider, - ConfigurableEnvironment configurableEnvironment) { - - if (StringUtils.isEmpty(properties.getUsername())) { - properties.setUsername("root"); - LOGGER.warn("spring.datasource.username is not specified. " - + "Setting default username."); - } - if (StringUtils.isEmpty(properties.getDriverClassName())) { - properties.setDriverClassName(cloudSqlJdbcInfoProvider.getJdbcDriverClass()); - } - else { - LOGGER.warn("spring.datasource.driver-class-name is specified. " + - "Not using generated Cloud SQL configuration"); - } - - if (StringUtils.hasText(properties.getUrl())) { - LOGGER.warn("Ignoring provided spring.datasource.url. Overwriting it based on the " + - "spring.cloud.gcp.sql.instance-connection-name."); - } - properties.setUrl(cloudSqlJdbcInfoProvider.getJdbcUrl()); - - // also set the spring.datasource.url property in the environment - Map myMap = new HashMap<>(); - myMap.put("spring.datasource.url", cloudSqlJdbcInfoProvider.getJdbcUrl()); - configurableEnvironment.getPropertySources() - .addFirst(new MapPropertySource("CLOUD_SQL_DATA_SOURCE_URL", myMap)); - - if (gcpCloudSqlProperties.getCredentials().getEncodedKey() != null) { - setCredentialsEncodedKeyProperty(gcpCloudSqlProperties); - } - else { - setCredentialsFileProperty(gcpCloudSqlProperties, gcpProperties); - } - - CoreSocketFactory.setApplicationName("spring-cloud-gcp-sql/" - + this.getClass().getPackage().getImplementationVersion()); - - return properties; - } - - /** - * Set credentials to be used by the Google Cloud SQL socket factory. - * - *

The only way to pass a {@link CredentialFactory} to the socket factory is by passing a - * class name through a system property. The socket factory creates an instance of - * {@link CredentialFactory} using reflection without any arguments. Because of that, the - * credential location needs to be stored somewhere where the class can read it without - * any context. It could be possible to pass in a Spring context to - * {@link SqlCredentialFactory}, but this is a tricky solution that needs some thinking - * about. - * - *

If user didn't specify credentials, the socket factory already does the right thing by - * using the application default credentials by default. So we don't need to do anything. - * @param gcpCloudSqlProperties the Cloud SQL properties to use - * @param gcpProperties the GCP properties to use - */ - private void setCredentialsFileProperty(GcpCloudSqlProperties gcpCloudSqlProperties, - GcpProperties gcpProperties) { - File credentialsLocationFile; - - try { - // First tries the SQL configuration credential. - if (gcpCloudSqlProperties.getCredentials().getLocation() != null) { - credentialsLocationFile = - gcpCloudSqlProperties.getCredentials().getLocation().getFile(); - setSystemProperties(credentialsLocationFile); - } - // Then, the global credential. - else if (gcpProperties != null - && gcpProperties.getCredentials().getLocation() != null) { - // A resource might not be in the filesystem, but the Cloud SQL credential must. - credentialsLocationFile = - gcpProperties.getCredentials().getLocation().getFile(); - setSystemProperties(credentialsLocationFile); - } - - // Else do nothing, let sockets factory use application default credentials. - - } - catch (IOException ioe) { - LOGGER.info("Error reading Cloud SQL credentials file.", ioe); - } - } - - private void setSystemProperties(File credentialsLocationFile) { - // This should happen if the Spring resource isn't in the filesystem, but a URL, - // classpath file, etc. - if (credentialsLocationFile == null) { - LOGGER.info("The private key of the Google Cloud SQL credential must " - + "be in a file on the filesystem."); - return; - } - - System.setProperty(SqlCredentialFactory.CREDENTIAL_LOCATION_PROPERTY_NAME, - credentialsLocationFile.getAbsolutePath()); - - // If there are specified credentials, tell sockets factory to use them. - System.setProperty(CredentialFactory.CREDENTIAL_FACTORY_PROPERTY, - SqlCredentialFactory.class.getName()); - } - - private void setCredentialsEncodedKeyProperty(GcpCloudSqlProperties gcpCloudSqlProperties) { - Credentials credentials = gcpCloudSqlProperties.getCredentials(); - - if (credentials.getEncodedKey() == null) { - LOGGER.info("SQL properties contain no encoded key. Can't set credentials."); - return; - } - - System.setProperty(SqlCredentialFactory.CREDENTIAL_ENCODED_KEY_PROPERTY_NAME, - credentials.getEncodedKey()); - - System.setProperty(CredentialFactory.CREDENTIAL_FACTORY_PROPERTY, - SqlCredentialFactory.class.getName()); - } - } -} diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlProperties.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlProperties.java deleted file mode 100644 index a4a445751f..0000000000 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlProperties.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2017-2020 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.core.Credentials; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Google Cloud SQL properties. - * - * @author João André Martins - * @author Øystein Urdahl Hardeng - */ -@ConfigurationProperties("spring.cloud.gcp.sql") -public class GcpCloudSqlProperties { - /** Name of the database in the Cloud SQL instance. */ - private String databaseName; - - /** Cloud SQL instance connection name. [GCP_PROJECT_ID]:[INSTANCE_REGION]:[INSTANCE_NAME]. */ - private String instanceConnectionName; - - /** A comma delimited list of preferred IP types for connecting to the Cloud SQL instance. */ - private String ipTypes; - - /** Overrides the GCP OAuth2 credentials specified in the Core module. */ - private Credentials credentials = new Credentials(); - - public String getDatabaseName() { - return this.databaseName; - } - - public void setDatabaseName(String databaseName) { - this.databaseName = databaseName; - } - - public String getInstanceConnectionName() { - return this.instanceConnectionName; - } - - public void setInstanceConnectionName(String instanceConnectionName) { - this.instanceConnectionName = instanceConnectionName; - } - - public String getIpTypes() { - return this.ipTypes; - } - - public void setIpTypes(String ipTypes) { - this.ipTypes = ipTypes; - } - - public Credentials getCredentials() { - return this.credentials; - } - - public void setCredentials(Credentials credentials) { - this.credentials = credentials; - } -} diff --git a/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring.factories index 0781aa1b9b..9ba00c9db2 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring.factories @@ -1,31 +1,33 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubEmulatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.logging.StackdriverLoggingAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubReactiveAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.spanner.GcpSpannerAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.spanner.GcpSpannerEmulatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.spanner.SpannerTransactionManagerAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.datastore.GcpDatastoreAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.firestore.GcpFirestoreAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.firestore.GcpFirestoreEmulatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.datastore.health.DatastoreHealthIndicatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.sql.GcpCloudSqlAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.storage.GcpStorageAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.trace.StackdriverTraceAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.datastore.DatastoreRepositoriesAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.spanner.SpannerRepositoriesAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.security.IapAuthenticationAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.security.FirebaseAuthenticationAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.vision.CloudVisionAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.datastore.GcpDatastoreEmulatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.bigquery.GcpBigQueryAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.datastore.DatastoreTransactionManagerAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.firestore.FirestoreRepositoriesAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.pubsub.health.PubSubHealthIndicatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.metrics.GcpStackdriverMetricsAutoConfiguration +org.springframework.cloud.gcp.autoconfigure.pubsub.GcpPubSubEmulatorAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.core.GcpContextAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.logging.StackdriverLoggingAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.pubsub.GcpPubSubAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.pubsub.GcpPubSubReactiveAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.spanner.GcpSpannerAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.spanner.GcpSpannerEmulatorAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.spanner.SpannerTransactionManagerAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.datastore.GcpDatastoreAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.firestore.GcpFirestoreAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.firestore.GcpFirestoreEmulatorAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.datastore.health.DatastoreHealthIndicatorAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.storage.GcpStorageAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.trace.StackdriverTraceAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.datastore.DatastoreRepositoriesAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.spanner.SpannerRepositoriesAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.security.IapAuthenticationAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.security.FirebaseAuthenticationAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.vision.CloudVisionAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.datastore.GcpDatastoreEmulatorAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.bigquery.GcpBigQueryAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.datastore.DatastoreTransactionManagerAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.firestore.FirestoreRepositoriesAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.pubsub.health.PubSubHealthIndicatorAutoConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.metrics.GcpStackdriverMetricsAutoConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ -com.google.cloud.spring.autoconfigure.config.GcpConfigBootstrapConfiguration,\ -com.google.cloud.spring.autoconfigure.secretmanager.GcpSecretManagerBootstrapConfiguration +org.springframework.cloud.gcp.autoconfigure.config.GcpConfigBootstrapConfiguration,\ +org.springframework.cloud.gcp.autoconfigure.secretmanager.GcpSecretManagerBootstrapConfiguration + +org.springframework.boot.env.EnvironmentPostProcessor=\ +org.springframework.cloud.gcp.autoconfigure.sql.CloudSqlEnvironmentPostProcessor \ No newline at end of file diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlAutoConfigurationMockTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java similarity index 71% rename from spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlAutoConfigurationMockTests.java rename to spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java index 42bde7da22..0a09e12cda 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlAutoConfigurationMockTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java @@ -22,71 +22,73 @@ import com.zaxxer.hikari.HikariDataSource; import org.junit.Test; +import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for Cloud SQL auto config. + * Tests for Cloud SQL {@link CloudSqlEnvironmentPostProcessor}. * * @author João André Martins * @author Artem Bilan * @author Øystein Urdahl Hardeng + * @author Mike Eltsufin */ -public class GcpCloudSqlAutoConfigurationMockTests { +public class CloudSqlEnvironmentPostProcessorTests { + private CloudSqlEnvironmentPostProcessor initializer = new CloudSqlEnvironmentPostProcessor(); private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.cloud.gcp.sql.databaseName=test-database") - .withConfiguration(AutoConfigurations.of(GcpCloudSqlAutoConfiguration.class, + .withInitializer(configurableApplicationContext -> initializer.postProcessEnvironment(configurableApplicationContext.getEnvironment(), new SpringApplication())) + .withConfiguration(AutoConfigurations.of( GcpContextAutoConfiguration.class, - DataSourceAutoConfiguration.class)) - .withUserConfiguration(GcpCloudSqlTestConfiguration.class); + DataSourceAutoConfiguration.class)); @Test public void testCloudSqlDataSource() { this.contextRunner.withPropertyValues( - "spring.cloud.gcp.sql.instanceConnectionName=tubular-bells:singapore:test-instance", + "spring.cloud.gcp.sql.instance-connection-name=tubular-bells:singapore:test-instance", "spring.datasource.password=") .run((context) -> { HikariDataSource dataSource = (HikariDataSource) context.getBean(DataSource.class); - CloudSqlJdbcInfoProvider urlProvider = - context.getBean(CloudSqlJdbcInfoProvider.class); assertThat(dataSource.getDriverClassName()).matches("com.mysql.cj.jdbc.Driver"); - assertThat(urlProvider.getJdbcUrl()).isEqualTo( + assertThat(dataSource.getJdbcUrl()).isEqualTo( "jdbc:mysql://google/test-database?" + "socketFactory=com.google.cloud.sql.mysql.SocketFactory" + "&cloudSqlInstance=tubular-bells:singapore:test-instance"); assertThat(dataSource.getUsername()).matches("root"); assertThat(dataSource.getPassword()).isNull(); - assertThat(urlProvider.getJdbcDriverClass()).matches("com.mysql.cj.jdbc.Driver"); }); } @Test public void testCloudSqlSpringDataSourceUrlPropertyOverride() { this.contextRunner.withPropertyValues( - "spring.cloud.gcp.sql.instanceConnectionName=tubular-bells:singapore:test-instance", + "spring.cloud.gcp.sql.instance-connection-name=tubular-bells:singapore:test-instance", "spring.datasource.password=", "spring.datasource.url=jdbc:h2:mem:none;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE") .run((context) -> { HikariDataSource dataSource = (HikariDataSource) context.getBean(DataSource.class); - CloudSqlJdbcInfoProvider urlProvider = - context.getBean(CloudSqlJdbcInfoProvider.class); assertThat(dataSource.getDriverClassName()).matches("com.mysql.cj.jdbc.Driver"); - assertThat(urlProvider.getJdbcUrl()).isEqualTo( + assertThat(dataSource.getJdbcUrl()).isEqualTo( "jdbc:mysql://google/test-database?" + "socketFactory=com.google.cloud.sql.mysql.SocketFactory" + "&cloudSqlInstance=tubular-bells:singapore:test-instance"); assertThat(dataSource.getUsername()).matches("root"); assertThat(dataSource.getPassword()).isNull(); - assertThat(urlProvider.getJdbcDriverClass()).matches("com.mysql.cj.jdbc.Driver"); + assertThat(getSpringDatasourceDriverClassName(context)).matches("com.mysql.cj.jdbc.Driver"); assertThat(context.getEnvironment().getProperty("spring.datasource.url")) .isEqualTo("jdbc:mysql://google/test-database?" + "socketFactory=com.google.cloud.sql.mysql.SocketFactory" @@ -97,22 +99,20 @@ public void testCloudSqlSpringDataSourceUrlPropertyOverride() { @Test public void testCloudSqlDataSourceWithIgnoredProvidedUrl() { this.contextRunner.withPropertyValues( - "spring.cloud.gcp.sql.instanceConnectionName=tubular-bells:singapore:test-instance", + "spring.cloud.gcp.sql.instance-connection-name=tubular-bells:singapore:test-instance", "spring.datasource.password=", "spring.datasource.url=test-url") .run((context) -> { HikariDataSource dataSource = (HikariDataSource) context.getBean(DataSource.class); - CloudSqlJdbcInfoProvider urlProvider = - context.getBean(CloudSqlJdbcInfoProvider.class); assertThat(dataSource.getDriverClassName()).matches("com.mysql.cj.jdbc.Driver"); - assertThat(urlProvider.getJdbcUrl()).isEqualTo( + assertThat(dataSource.getJdbcUrl()).isEqualTo( "jdbc:mysql://google/test-database?" + "socketFactory=com.google.cloud.sql.mysql.SocketFactory" + "&cloudSqlInstance=tubular-bells:singapore:test-instance"); assertThat(dataSource.getUsername()).matches("root"); assertThat(dataSource.getPassword()).isNull(); - assertThat(urlProvider.getJdbcDriverClass()).matches("com.mysql.cj.jdbc.Driver"); + assertThat(getSpringDatasourceDriverClassName(context)).matches("com.mysql.cj.jdbc.Driver"); }); } @@ -120,20 +120,18 @@ public void testCloudSqlDataSourceWithIgnoredProvidedUrl() { public void testCloudSqlAppEngineDataSourceTest() { this.contextRunner.withPropertyValues( "spring.cloud.gcp.project-id=im-not-used-for-anything", - "spring.cloud.gcp.sql.instanceConnectionName=tubular-bells:australia:test-instance", + "spring.cloud.gcp.sql.instance-connection-name=tubular-bells:australia:test-instance", "spring.datasource.password=") .withSystemProperties( "com.google.appengine.runtime.version=Google App Engine/Some Server") .run((context) -> { HikariDataSource dataSource = (HikariDataSource) context.getBean(DataSource.class); - CloudSqlJdbcInfoProvider urlProvider = - context.getBean(CloudSqlJdbcInfoProvider.class); - assertThat(urlProvider.getJdbcDriverClass()) + assertThat(getSpringDatasourceDriverClassName(context)) .matches("com.mysql.cj.jdbc.Driver"); assertThat(dataSource.getDriverClassName()) .matches("com.mysql.cj.jdbc.Driver"); - assertThat(urlProvider.getJdbcUrl()).isEqualTo( + assertThat(getSpringDatasourceUrl(context)).isEqualTo( "jdbc:mysql://google/test-database?" + "socketFactory=com.google.cloud.sql.mysql.SocketFactory" + "&cloudSqlInstance=tubular-bells:australia:test-instance"); @@ -146,38 +144,34 @@ public void testCloudSqlAppEngineDataSourceTest() { public void testUserAndPassword() { this.contextRunner.withPropertyValues("spring.datasource.username=watchmaker", "spring.datasource.password=pass", - "spring.cloud.gcp.sql.instanceConnectionName=proj:reg:test-instance") + "spring.cloud.gcp.sql.instance-connection-name=proj:reg:test-instance") .run((context) -> { HikariDataSource dataSource = (HikariDataSource) context.getBean(DataSource.class); - CloudSqlJdbcInfoProvider urlProvider = - context.getBean(CloudSqlJdbcInfoProvider.class); - assertThat(urlProvider.getJdbcUrl()).isEqualTo( + assertThat(getSpringDatasourceUrl(context)).isEqualTo( "jdbc:mysql://google/test-database?" + "socketFactory=com.google.cloud.sql.mysql.SocketFactory" + "&cloudSqlInstance=proj:reg:test-instance"); assertThat(dataSource.getUsername()).matches("watchmaker"); assertThat(dataSource.getPassword()).matches("pass"); - assertThat(urlProvider.getJdbcDriverClass()).matches("com.mysql.cj.jdbc.Driver"); + assertThat(getSpringDatasourceDriverClassName(context)).matches("com.mysql.cj.jdbc.Driver"); }); } @Test public void testDataSourceProperties() { this.contextRunner.withPropertyValues( - "spring.cloud.gcp.sql.instanceConnectionName=proj:reg:test-instance", + "spring.cloud.gcp.sql.instance-connection-name=proj:reg:test-instance", "spring.datasource.hikari.connectionTestQuery=select 1", "spring.datasource.hikari.maximum-pool-size=19") .run((context) -> { HikariDataSource dataSource = (HikariDataSource) context.getBean(DataSource.class); - CloudSqlJdbcInfoProvider urlProvider = - context.getBean(CloudSqlJdbcInfoProvider.class); - assertThat(urlProvider.getJdbcUrl()).isEqualTo( + assertThat(getSpringDatasourceUrl(context)).isEqualTo( "jdbc:mysql://google/test-database?" + "socketFactory=com.google.cloud.sql.mysql.SocketFactory" + "&cloudSqlInstance=proj:reg:test-instance"); - assertThat(urlProvider.getJdbcDriverClass()).matches("com.mysql.cj.jdbc.Driver"); + assertThat(getSpringDatasourceDriverClassName(context)).matches("com.mysql.cj.jdbc.Driver"); assertThat(dataSource.getMaximumPoolSize()).isEqualTo(19); assertThat(dataSource.getConnectionTestQuery()).matches("select 1"); }); @@ -186,45 +180,40 @@ public void testDataSourceProperties() { @Test public void testInstanceConnectionName() { this.contextRunner.withPropertyValues( - "spring.cloud.gcp.sql.instanceConnectionName=world:asia:japan") + "spring.cloud.gcp.sql.instance-connection-name=world:asia:japan") .run((context) -> { - CloudSqlJdbcInfoProvider urlProvider = - context.getBean(CloudSqlJdbcInfoProvider.class); - assertThat(urlProvider.getJdbcUrl()).isEqualTo( + assertThat(getSpringDatasourceUrl(context)).isEqualTo( "jdbc:mysql://google/test-database?" + "socketFactory=com.google.cloud.sql.mysql.SocketFactory" + "&cloudSqlInstance=world:asia:japan"); - assertThat(urlProvider.getJdbcDriverClass()).matches("com.mysql.cj.jdbc.Driver"); + assertThat(getSpringDatasourceDriverClassName(context)).matches("com.mysql.cj.jdbc.Driver"); }); } @Test public void testPostgre() { this.contextRunner.withPropertyValues( - "spring.cloud.gcp.sql.instanceConnectionName=tubular-bells:singapore:test-instance") + "spring.cloud.gcp.sql.instance-connection-name=tubular-bells:singapore:test-instance") .withClassLoader( new FilteredClassLoader("com.google.cloud.sql.mysql")) .run((context) -> { - CloudSqlJdbcInfoProvider urlProvider = - context.getBean(CloudSqlJdbcInfoProvider.class); - assertThat(urlProvider.getJdbcUrl()).isEqualTo( + assertThat(getSpringDatasourceUrl(context)).isEqualTo( "jdbc:postgresql://google/test-database?" + "socketFactory=com.google.cloud.sql.postgres.SocketFactory" + "&cloudSqlInstance=tubular-bells:singapore:test-instance"); - assertThat(urlProvider.getJdbcDriverClass()).matches("org.postgresql.Driver"); + assertThat(getSpringDatasourceDriverClassName(context)).matches("org.postgresql.Driver"); }); } @Test public void testNoJdbc() { this.contextRunner.withPropertyValues( - "spring.cloud.gcp.sql.instanceConnectionName=tubular-bells:singapore:test-instance") + "spring.cloud.gcp.sql.instance-connection-name=tubular-bells:singapore:test-instance") .withClassLoader( new FilteredClassLoader(EmbeddedDatabaseType.class, DataSource.class)) .run((context) -> { assertThat(context.getBeanNamesForType(DataSource.class)).isEmpty(); assertThat(context.getBeanNamesForType(DataSourceProperties.class)).isEmpty(); - assertThat(context.getBeanNamesForType(GcpCloudSqlProperties.class)).isEmpty(); assertThat(context.getBeanNamesForType(CloudSqlJdbcInfoProvider.class)).isEmpty(); }); } @@ -232,7 +221,7 @@ public void testNoJdbc() { @Test public void testIpTypes() { this.contextRunner.withPropertyValues( - "spring.cloud.gcp.sql.instanceConnectionName=world:asia:japan", + "spring.cloud.gcp.sql.instance-connection-name=world:asia:japan", "spring.cloud.gcp.sql.ip-types=PRIVATE") .run((context) -> { DataSourceProperties dataSourceProperties = @@ -241,4 +230,12 @@ public void testIpTypes() { "&ipTypes=PRIVATE"); }); } + + private String getSpringDatasourceUrl(ApplicationContext context) { + return context.getEnvironment().getProperty("spring.datasource.url"); + } + + private String getSpringDatasourceDriverClassName(ApplicationContext context) { + return context.getEnvironment().getProperty("spring.datasource.driver-class-name"); + } } diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlTestConfiguration.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlTestConfiguration.java deleted file mode 100644 index 30437d5e3f..0000000000 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlTestConfiguration.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2017-2018 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.api.gax.core.CredentialsProvider; -import com.google.auth.Credentials; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.mockito.Mockito.mock; - -/** - * Provides test mocks. - * - * @author João André Martins - */ -@Configuration -public class GcpCloudSqlTestConfiguration { - - @Bean - public CredentialsProvider googleCredentials() { - return () -> mock(Credentials.class); - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample/src/main/resources/application.properties b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample/src/main/resources/application.properties index 44b0f19b47..1eecddbaca 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample/src/main/resources/application.properties +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample/src/main/resources/application.properties @@ -1,5 +1,7 @@ -spring.cloud.gcp.sql.database-name=[database-name] -spring.cloud.gcp.sql.instance-connection-name=[instance-connection-name] +spring.datasource.url=jdbc:h2:mem:none;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE + +spring.cloud.gcp.sql.database-name=users +spring.cloud.gcp.sql.instance-connection-name=eltsufin-sandbox:us-central1:mikesql # So app starts despite "table already exists" errors. spring.datasource.continue-on-error=true @@ -7,10 +9,10 @@ spring.datasource.continue-on-error=true spring.datasource.initialization-mode=always # Leave empty for root, uncomment and fill out if you specified a user -#spring.datasource.username= +spring.datasource.username=root # Uncomment if root password is specified -#spring.datasource.password= +spring.datasource.password=admin #spring.cloud.gcp.project-id= #spring.cloud.gcp.credentials.location=file:/path/to/service-account.json From e2c7b6e00813a54165664186a86ce23f9a5050a8 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Tue, 17 Nov 2020 21:47:55 -0500 Subject: [PATCH 2/6] cleanup --- .../sql/CloudSqlEnvironmentPostProcessor.java | 31 +++++++--- .../main/resources/META-INF/spring.factories | 56 +++++++++---------- ...retManagerBootstrapConfigurationTests.java | 4 +- ...CloudSqlEnvironmentPostProcessorTests.java | 3 - .../src/main/resources/application.properties | 10 ++-- 5 files changed, 58 insertions(+), 46 deletions(-) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java index feb0700d72..c89879b633 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017-2020 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.io.File; @@ -5,8 +21,6 @@ import java.util.HashMap; import java.util.Map; -import com.google.cloud.spring.autoconfigure.core.GcpProperties; -import com.google.cloud.spring.core.Credentials; import com.google.cloud.sql.CredentialFactory; import com.google.cloud.sql.core.CoreSocketFactory; import org.apache.commons.logging.Log; @@ -17,7 +31,6 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** * Provides Google Cloud SQL instance connectivity through Spring JDBC by providing only a @@ -81,10 +94,11 @@ && isOnClasspath("org.postgresql.Driver")) { + cloudSqlJdbcInfoProvider.getJdbcDriverClass()); } - String encodedKey = getSqlProperty(environment,"credentials.encoded-key", null); + String encodedKey = getSqlProperty(environment, "credentials.encoded-key", null); if (encodedKey != null) { setCredentialsEncodedKeyProperty(encodedKey); - } else { + } + else { setCredentialsFileProperty(environment, application); } @@ -100,9 +114,10 @@ private String getSqlProperty(ConfigurableEnvironment environment, String shortN private boolean isOnClasspath(String className) { try { - ClassUtils.forName(className, (ClassLoader) null); + ClassUtils.forName(className, null); return true; - } catch (ClassNotFoundException ex) { + } + catch (ClassNotFoundException ex) { return false; } } @@ -133,7 +148,7 @@ private void setCredentialsFileProperty(ConfigurableEnvironment environment, Spr File credentialsLocationFile; try { - String sqlCredentialsLocation = getSqlProperty(environment,"credentials.location", null); + String sqlCredentialsLocation = getSqlProperty(environment, "credentials.location", null); String globalCredentialsLocation = environment.getProperty("spring.cloud.gcp.credentials.location", (String) null); // First tries the SQL configuration credential. if (sqlCredentialsLocation != null) { diff --git a/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring.factories index 9ba00c9db2..cbb110108b 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring.factories @@ -1,33 +1,33 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.springframework.cloud.gcp.autoconfigure.pubsub.GcpPubSubEmulatorAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.core.GcpContextAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.logging.StackdriverLoggingAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.pubsub.GcpPubSubAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.pubsub.GcpPubSubReactiveAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.spanner.GcpSpannerAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.spanner.GcpSpannerEmulatorAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.spanner.SpannerTransactionManagerAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.datastore.GcpDatastoreAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.firestore.GcpFirestoreAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.firestore.GcpFirestoreEmulatorAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.datastore.health.DatastoreHealthIndicatorAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.storage.GcpStorageAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.trace.StackdriverTraceAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.datastore.DatastoreRepositoriesAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.spanner.SpannerRepositoriesAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.security.IapAuthenticationAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.security.FirebaseAuthenticationAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.vision.CloudVisionAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.datastore.GcpDatastoreEmulatorAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.bigquery.GcpBigQueryAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.datastore.DatastoreTransactionManagerAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.firestore.FirestoreRepositoriesAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.pubsub.health.PubSubHealthIndicatorAutoConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.metrics.GcpStackdriverMetricsAutoConfiguration +com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubEmulatorAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.logging.StackdriverLoggingAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubReactiveAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.spanner.GcpSpannerAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.spanner.GcpSpannerEmulatorAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.spanner.SpannerTransactionManagerAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.datastore.GcpDatastoreAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.firestore.GcpFirestoreAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.firestore.GcpFirestoreEmulatorAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.datastore.health.DatastoreHealthIndicatorAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.storage.GcpStorageAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.trace.StackdriverTraceAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.datastore.DatastoreRepositoriesAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.spanner.SpannerRepositoriesAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.security.IapAuthenticationAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.security.FirebaseAuthenticationAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.vision.CloudVisionAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.datastore.GcpDatastoreEmulatorAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.bigquery.GcpBigQueryAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.datastore.DatastoreTransactionManagerAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.firestore.FirestoreRepositoriesAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.pubsub.health.PubSubHealthIndicatorAutoConfiguration,\ +com.google.cloud.spring.autoconfigure.metrics.GcpStackdriverMetricsAutoConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ -org.springframework.cloud.gcp.autoconfigure.config.GcpConfigBootstrapConfiguration,\ -org.springframework.cloud.gcp.autoconfigure.secretmanager.GcpSecretManagerBootstrapConfiguration +com.google.cloud.spring.autoconfigure.config.GcpConfigBootstrapConfiguration,\ +com.google.cloud.spring.autoconfigure.secretmanager.GcpSecretManagerBootstrapConfiguration org.springframework.boot.env.EnvironmentPostProcessor=\ -org.springframework.cloud.gcp.autoconfigure.sql.CloudSqlEnvironmentPostProcessor \ No newline at end of file +com.google.cloud.spring.autoconfigure.sql.CloudSqlEnvironmentPostProcessor \ No newline at end of file diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/secretmanager/SecretManagerBootstrapConfigurationTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/secretmanager/SecretManagerBootstrapConfigurationTests.java index 3697136135..308491c7a2 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/secretmanager/SecretManagerBootstrapConfigurationTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/secretmanager/SecretManagerBootstrapConfigurationTests.java @@ -52,7 +52,8 @@ public class SecretManagerBootstrapConfigurationTests { TestBootstrapConfiguration.class, GcpSecretManagerBootstrapConfiguration.class) .properties( "spring.cloud.gcp.secretmanager.project-id=" + PROJECT_NAME, - "spring.config.use-legacy-processing=true") + "spring.config.use-legacy-processing=true", + "spring.cloud.gcp.sql.enabled=false") .web(WebApplicationType.NONE); @Test @@ -99,6 +100,7 @@ public void configurationDisabled() { TestBootstrapConfiguration.class, GcpSecretManagerBootstrapConfiguration.class) .properties("spring.cloud.gcp.secretmanager.project-id=" + PROJECT_NAME) .properties("spring.cloud.gcp.secretmanager.enabled=false") + .properties("spring.cloud.gcp.sql.enabled=false") .web(WebApplicationType.NONE); try (ConfigurableApplicationContext c = disabledConfigurationApp.run()) { diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java index 0a09e12cda..8799d269fb 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java @@ -29,9 +29,6 @@ import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample/src/main/resources/application.properties b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample/src/main/resources/application.properties index 1eecddbaca..44b0f19b47 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample/src/main/resources/application.properties +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample/src/main/resources/application.properties @@ -1,7 +1,5 @@ -spring.datasource.url=jdbc:h2:mem:none;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE - -spring.cloud.gcp.sql.database-name=users -spring.cloud.gcp.sql.instance-connection-name=eltsufin-sandbox:us-central1:mikesql +spring.cloud.gcp.sql.database-name=[database-name] +spring.cloud.gcp.sql.instance-connection-name=[instance-connection-name] # So app starts despite "table already exists" errors. spring.datasource.continue-on-error=true @@ -9,10 +7,10 @@ spring.datasource.continue-on-error=true spring.datasource.initialization-mode=always # Leave empty for root, uncomment and fill out if you specified a user -spring.datasource.username=root +#spring.datasource.username= # Uncomment if root password is specified -spring.datasource.password=admin +#spring.datasource.password= #spring.cloud.gcp.project-id= #spring.cloud.gcp.credentials.location=file:/path/to/service-account.json From 21fa7395b92f16cfca547bf226cc595161a12b25 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Wed, 18 Nov 2020 09:34:56 -0500 Subject: [PATCH 3/6] Fix config it --- .../autoconfigure/config/it/GcpConfigIntegrationTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/config/it/GcpConfigIntegrationTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/config/it/GcpConfigIntegrationTests.java index 446986cf0a..2bc08775b4 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/config/it/GcpConfigIntegrationTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/config/it/GcpConfigIntegrationTests.java @@ -59,7 +59,8 @@ public void testConfiguration() { .properties("spring.cloud.gcp.config.enabled=true", "spring.application.name=myapp", "spring.profiles.active=dontexist,prod", - "spring.config.use-legacy-processing=true") + "spring.config.use-legacy-processing=true", + "spring.cloud.gcp.sql.enabled=false") .run(); assertThat(this.context.getEnvironment().getProperty("myapp.queue-size")) From 3a1c21c52ecd7d1130233772618d9712388785e7 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Thu, 19 Nov 2020 15:18:35 -0500 Subject: [PATCH 4/6] some more cleanup and refactoring --- .../sql/CloudSqlEnvironmentPostProcessor.java | 112 ++++++++++-------- ...CloudSqlEnvironmentPostProcessorTests.java | 20 +++- 2 files changed, 81 insertions(+), 51 deletions(-) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java index c89879b633..c1265de136 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java @@ -50,66 +50,51 @@ public class CloudSqlEnvironmentPostProcessor implements EnvironmentPostProcesso @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + DatabaseType databaseType = getEnabledDatabaseType(environment); + + if (databaseType != null) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("post-processing Cloud SQL properties for + " + databaseType.name()); + } + + CloudSqlJdbcInfoProvider cloudSqlJdbcInfoProvider = buildCloudSqlJdbcInfoProvider(environment, databaseType); + + // configure default JDBC driver and username as fallback values when not specified + Map fallbackMap = new HashMap<>(); + fallbackMap.put("spring.datasource.username", "root"); + fallbackMap.put("spring.datasource.driver-class-name", cloudSqlJdbcInfoProvider.getJdbcDriverClass()); + environment.getPropertySources() + .addLast(new MapPropertySource("CLOUD_SQL_DATA_SOURCE_FALLBACK", fallbackMap)); + + // always set the spring.datasource.url property in the environment + Map primaryMap = new HashMap<>(); + primaryMap.put("spring.datasource.url", cloudSqlJdbcInfoProvider.getJdbcUrl()); + environment.getPropertySources() + .addFirst(new MapPropertySource("CLOUD_SQL_DATA_SOURCE_URL", primaryMap)); + + setCredentials(environment, application); + + // support usage metrics + CoreSocketFactory.setApplicationName("spring-cloud-gcp-sql/" + + this.getClass().getPackage().getImplementationVersion()); + } + } + + private DatabaseType getEnabledDatabaseType(ConfigurableEnvironment environment) { if (Boolean.parseBoolean(getSqlProperty(environment, "enabled", "true")) && isOnClasspath("javax.sql.DataSource") && isOnClasspath("org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType") && isOnClasspath("com.google.cloud.sql.CredentialFactory")) { - - DatabaseType databaseType = null; if (isOnClasspath("com.google.cloud.sql.mysql.SocketFactory") && isOnClasspath("com.mysql.cj.jdbc.Driver")) { - databaseType = DatabaseType.MYSQL; + return DatabaseType.MYSQL; } else if (isOnClasspath("com.google.cloud.sql.postgres.SocketFactory") && isOnClasspath("org.postgresql.Driver")) { - databaseType = DatabaseType.POSTGRESQL; - } - - if (databaseType != null) { - LOGGER.info("post-processing Cloud SQL properties"); - - CloudSqlJdbcInfoProvider cloudSqlJdbcInfoProvider = new DefaultCloudSqlJdbcInfoProvider( - getSqlProperty(environment, "database-name", null), - getSqlProperty(environment, "instance-connection-name", null), - getSqlProperty(environment, "ip-types", null), - databaseType); - - // configure default JDBC driver and username as fallback values when not specified - Map fallbackMap = new HashMap<>(); - fallbackMap.put("spring.datasource.username", "root"); - fallbackMap.put("spring.datasource.driver-class-name", cloudSqlJdbcInfoProvider.getJdbcDriverClass()); - environment.getPropertySources() - .addLast(new MapPropertySource("CLOUD_SQL_DATA_SOURCE_FALLBACK", fallbackMap)); - - // always set the spring.datasource.url property in the environment - Map primaryMap = new HashMap<>(); - primaryMap.put("spring.datasource.url", cloudSqlJdbcInfoProvider.getJdbcUrl()); - environment.getPropertySources() - .addFirst(new MapPropertySource("CLOUD_SQL_DATA_SOURCE_URL", primaryMap)); - - if (LOGGER.isInfoEnabled()) { - LOGGER.info("Default " + databaseType.name() - + " JdbcUrl provider. Connecting to " - + cloudSqlJdbcInfoProvider.getJdbcUrl() + " with driver " - + cloudSqlJdbcInfoProvider.getJdbcDriverClass()); - } - - String encodedKey = getSqlProperty(environment, "credentials.encoded-key", null); - if (encodedKey != null) { - setCredentialsEncodedKeyProperty(encodedKey); - } - else { - setCredentialsFileProperty(environment, application); - } - - CoreSocketFactory.setApplicationName("spring-cloud-gcp-sql/" - + this.getClass().getPackage().getImplementationVersion()); + return DatabaseType.POSTGRESQL; } } - } - - private String getSqlProperty(ConfigurableEnvironment environment, String shortName, String defaultValue) { - return environment.getProperty(CLOUD_SQL_PROPERTIES_PREFIX + shortName, defaultValue); + return null; } private boolean isOnClasspath(String className) { @@ -122,6 +107,35 @@ private boolean isOnClasspath(String className) { } } + private CloudSqlJdbcInfoProvider buildCloudSqlJdbcInfoProvider(ConfigurableEnvironment environment, DatabaseType databaseType) { + CloudSqlJdbcInfoProvider cloudSqlJdbcInfoProvider = new DefaultCloudSqlJdbcInfoProvider( + getSqlProperty(environment, "database-name", null), + getSqlProperty(environment, "instance-connection-name", null), + getSqlProperty(environment, "ip-types", null), + databaseType); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Default " + databaseType.name() + + " JdbcUrl provider. Connecting to " + + cloudSqlJdbcInfoProvider.getJdbcUrl() + " with driver " + + cloudSqlJdbcInfoProvider.getJdbcDriverClass()); + } + return cloudSqlJdbcInfoProvider; + } + + private String getSqlProperty(ConfigurableEnvironment environment, String shortName, String defaultValue) { + return environment.getProperty(CLOUD_SQL_PROPERTIES_PREFIX + shortName, defaultValue); + } + + private void setCredentials(ConfigurableEnvironment environment, SpringApplication application) { + String encodedKey = getSqlProperty(environment, "credentials.encoded-key", null); + if (encodedKey != null) { + setCredentialsEncodedKeyProperty(encodedKey); + } + else { + setCredentialsFileProperty(environment, application); + } + } + private void setCredentialsEncodedKeyProperty(String encodedKey) { System.setProperty(SqlCredentialFactory.CREDENTIAL_ENCODED_KEY_PROPERTY_NAME, encodedKey); diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java index 8799d269fb..1a99a61bf8 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java @@ -114,7 +114,7 @@ public void testCloudSqlDataSourceWithIgnoredProvidedUrl() { } @Test - public void testCloudSqlAppEngineDataSourceTest() { + public void testCloudSqlAppEngineDataSourceDefaultUserNameRootTest() { this.contextRunner.withPropertyValues( "spring.cloud.gcp.project-id=im-not-used-for-anything", "spring.cloud.gcp.sql.instance-connection-name=tubular-bells:australia:test-instance", @@ -155,6 +155,22 @@ public void testUserAndPassword() { }); } + @Test + public void testUserSpecifiedDriverOverride() { + this.contextRunner.withPropertyValues( + "spring.cloud.gcp.sql.instance-connection-name=proj:reg:test-instance", + "spring.datasource.driver-class-name=org.postgresql.Driver") + .run((context) -> { + HikariDataSource dataSource = + (HikariDataSource) context.getBean(DataSource.class); + assertThat(getSpringDatasourceUrl(context)).isEqualTo( + "jdbc:mysql://google/test-database?" + + "socketFactory=com.google.cloud.sql.mysql.SocketFactory" + + "&cloudSqlInstance=proj:reg:test-instance"); + assertThat(dataSource.getDriverClassName()).matches("org.postgresql.Driver"); + }); + } + @Test public void testDataSourceProperties() { this.contextRunner.withPropertyValues( @@ -188,7 +204,7 @@ public void testInstanceConnectionName() { } @Test - public void testPostgre() { + public void testPostgres() { this.contextRunner.withPropertyValues( "spring.cloud.gcp.sql.instance-connection-name=tubular-bells:singapore:test-instance") .withClassLoader( From e38c40a02c1124451e2b9944f60b0d6293ca547c Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Thu, 19 Nov 2020 17:12:28 -0500 Subject: [PATCH 5/6] * Set Postgres default username: `postgres` * Improve tests and documentation --- docs/src/main/asciidoc/sql.adoc | 30 +++++++++---------- .../sql/CloudSqlEnvironmentPostProcessor.java | 2 +- .../autoconfigure/sql/DatabaseType.java | 16 +++++++--- ...CloudSqlEnvironmentPostProcessorTests.java | 5 +++- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/docs/src/main/asciidoc/sql.adoc b/docs/src/main/asciidoc/sql.adoc index 9cf72dd58a..34d0d63ce2 100644 --- a/docs/src/main/asciidoc/sql.adoc +++ b/docs/src/main/asciidoc/sql.adoc @@ -59,32 +59,32 @@ In other words, properties like the SQL username, `spring.datasource.username`, There is also some configuration specific to Google Cloud SQL: |=== -| Property name | Description | Default value -| `spring.cloud.gcp.sql.enabled` | Enables or disables Cloud SQL auto configuration | `true` -| `spring.cloud.gcp.sql.database-name` | Name of the database to connect to. | -| `spring.cloud.gcp.sql.instance-connection-name` | A string containing a Google Cloud SQL instance's project ID, region and name, each separated by a colon. -For example, `my-project-id:my-region:my-instance-name`. | -| `spring.cloud.gcp.sql.ip-types` | Allows you to specify a comma delimited list of preferred IP types for connecting to a Cloud SQL instance. Left unconfigured Cloud SQL Socket Factory will default it to `PUBLIC,PRIVATE`. See https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory#specifying-ip-types[Cloud SQL Socket Factory - Specifying IP Types] | `PUBLIC,PRIVATE` +| Property name | Description | Required | Default value +| `spring.cloud.gcp.sql.enabled` | Enables or disables Cloud SQL auto configuration | No | `true` +| `spring.cloud.gcp.sql.database-name` | Name of the database to connect to. | Yes | +| `spring.cloud.gcp.sql.instance-connection-name` | A string containing a Google Cloud SQL instance's project ID, region and name, each separated by a colon. | Yes | +For example, `my-project-id:my-region:my-instance-name`. +| `spring.cloud.gcp.sql.ip-types` | Allows you to specify a comma delimited list of preferred IP types for connecting to a Cloud SQL instance. Left unconfigured Cloud SQL Socket Factory will default it to `PUBLIC,PRIVATE`. See https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory#specifying-ip-types[Cloud SQL Socket Factory - Specifying IP Types] | No | `PUBLIC,PRIVATE` | `spring.cloud.gcp.sql.credentials.location` | File system path to the Google OAuth2 credentials private key file. -Used to authenticate and authorize new connections to a Google Cloud SQL instance. +Used to authenticate and authorize new connections to a Google Cloud SQL instance. | No | Default credentials provided by the Spring GCP Boot starter | `spring.cloud.gcp.sql.credentials.encoded-key` | Base64-encoded contents of OAuth2 account private key in JSON format. -Used to authenticate and authorize new connections to a Google Cloud SQL instance. +Used to authenticate and authorize new connections to a Google Cloud SQL instance. | No | Default credentials provided by the Spring GCP Boot starter +| `spring.datasource.username` | Database username | No | MySQL: `root`; PostgreSQL: `postgres` +| `spring.datasource.password` | Database password | No | `null` +| `spring.datasource.driver-class-name` | JDBC driver to use. | No | MySQL: `com.mysql.cj.jdbc.Driver`; PostgreSQL: `org.postgresql.Driver` |=== NOTE: If you provide your own `spring.datasource.url`, it will be ignored, unless you disable Cloud SQL auto configuration with `spring.cloud.gcp.sql.enabled=false`. ==== `DataSource` creation flow -Based on the previous properties, the Spring Boot starter for Google Cloud SQL creates a `CloudSqlJdbcInfoProvider` object which is used to obtain an instance's JDBC URL and driver class name. -If you provide your own `CloudSqlJdbcInfoProvider` bean, it is used instead and the properties related to building the JDBC URL or driver class are ignored. +Spring Boot starter for Google Cloud SQL registers a `CloudSqlEnvironmentPostProcessor` that provides a correctly formatted `spring.datasource.url` property to the environment based on the properties defined above. +It also provides defaults for `spring.datasource.username` and `spring.datasource.driver-class-name`, which can be overridden. +The starter also configures credentials for the JDBC connection based on the properties above. -The `DataSourceProperties` object provided by Spring Boot Autoconfigure is mutated in order to use the JDBC URL and driver class names provided by `CloudSqlJdbcInfoProvider`, unless those values were provided in the properties. -It is in the `DataSourceProperties` mutation step that the credentials factory is registered in a system property to be `SqlCredentialFactory`. - -`DataSource` creation is delegated to -https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html[Spring Boot]. +The user properties and the properties provided by the `CloudSqlEnvironmentPostProcessor` are then used by https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html[Spring Boot] to create the `DataSource`. You can select the type of connection pool (e.g., Tomcat, HikariCP, etc.) by https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-connect-to-production-database[adding their dependency to the classpath]. Using the created `DataSource` in conjunction with Spring JDBC provides you with a fully configured and operational `JdbcTemplate` object that you can use to interact with your SQL database. diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java index c1265de136..d277a3a7fc 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java @@ -61,7 +61,7 @@ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringAp // configure default JDBC driver and username as fallback values when not specified Map fallbackMap = new HashMap<>(); - fallbackMap.put("spring.datasource.username", "root"); + fallbackMap.put("spring.datasource.username", databaseType.getDefaultUsername()); fallbackMap.put("spring.datasource.driver-class-name", cloudSqlJdbcInfoProvider.getJdbcDriverClass()); environment.getPropertySources() .addLast(new MapPropertySource("CLOUD_SQL_DATA_SOURCE_FALLBACK", fallbackMap)); diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/DatabaseType.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/DatabaseType.java index 8c2ed9dea4..019f313199 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/DatabaseType.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/DatabaseType.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 the original author or authors. + * Copyright 2017-2020 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. @@ -21,6 +21,7 @@ * * @author João André Martins * @author Chengyuan Zhao + * @author Mike Eltsufin */ public enum DatabaseType { /** @@ -28,22 +29,25 @@ public enum DatabaseType { */ MYSQL("com.mysql.cj.jdbc.Driver", "jdbc:mysql://google/%s?" + "socketFactory=com.google.cloud.sql.mysql.SocketFactory" - + "&cloudSqlInstance=%s"), + + "&cloudSqlInstance=%s", "root"), /** * Postgresql constants. */ POSTGRESQL("org.postgresql.Driver", "jdbc:postgresql://google/%s?" + "socketFactory=com.google.cloud.sql.postgres.SocketFactory" - + "&cloudSqlInstance=%s"); + + "&cloudSqlInstance=%s", "postgres"); private final String jdbcDriverName; private final String jdbcUrlTemplate; - DatabaseType(String jdbcDriverName, String jdbcUrlTemplate) { + private final String defaultUsername; + + DatabaseType(String jdbcDriverName, String jdbcUrlTemplate, String defaultUsername) { this.jdbcDriverName = jdbcDriverName; this.jdbcUrlTemplate = jdbcUrlTemplate; + this.defaultUsername = defaultUsername; } public String getJdbcDriverName() { @@ -53,4 +57,8 @@ public String getJdbcDriverName() { public String getJdbcUrlTemplate() { return this.jdbcUrlTemplate; } + + public String getDefaultUsername() { + return defaultUsername; + } } diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java index 1a99a61bf8..3bf264510f 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessorTests.java @@ -114,7 +114,7 @@ public void testCloudSqlDataSourceWithIgnoredProvidedUrl() { } @Test - public void testCloudSqlAppEngineDataSourceDefaultUserNameRootTest() { + public void testCloudSqlAppEngineDataSourceDefaultUserNameMySqlTest() { this.contextRunner.withPropertyValues( "spring.cloud.gcp.project-id=im-not-used-for-anything", "spring.cloud.gcp.sql.instance-connection-name=tubular-bells:australia:test-instance", @@ -210,11 +210,14 @@ public void testPostgres() { .withClassLoader( new FilteredClassLoader("com.google.cloud.sql.mysql")) .run((context) -> { + HikariDataSource dataSource = + (HikariDataSource) context.getBean(DataSource.class); assertThat(getSpringDatasourceUrl(context)).isEqualTo( "jdbc:postgresql://google/test-database?" + "socketFactory=com.google.cloud.sql.postgres.SocketFactory" + "&cloudSqlInstance=tubular-bells:singapore:test-instance"); assertThat(getSpringDatasourceDriverClassName(context)).matches("org.postgresql.Driver"); + assertThat(dataSource.getUsername()).matches("postgres"); }); } From 500ff58783fbfe559143ec38b60d5277b01fad43 Mon Sep 17 00:00:00 2001 From: Mike Eltsufin Date: Fri, 20 Nov 2020 14:32:41 -0500 Subject: [PATCH 6/6] Restore GcpCloudSqlProperties and use Binder --- .../sql/CloudSqlEnvironmentPostProcessor.java | 150 ++++++++---------- .../sql/DefaultCloudSqlJdbcInfoProvider.java | 26 ++- .../sql/GcpCloudSqlProperties.java | 74 +++++++++ 3 files changed, 156 insertions(+), 94 deletions(-) create mode 100644 spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlProperties.java diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java index d277a3a7fc..debdcaf542 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/CloudSqlEnvironmentPostProcessor.java @@ -21,15 +21,20 @@ import java.util.HashMap; import java.util.Map; +import com.google.cloud.spring.autoconfigure.core.GcpProperties; +import com.google.cloud.spring.core.Credentials; import com.google.cloud.sql.CredentialFactory; import com.google.cloud.sql.core.CoreSocketFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; +import org.springframework.core.io.Resource; import org.springframework.util.ClassUtils; /** @@ -43,8 +48,6 @@ * @author Eddú Meléndez */ public class CloudSqlEnvironmentPostProcessor implements EnvironmentPostProcessor { - private final static String CLOUD_SQL_PROPERTIES_PREFIX = "spring.cloud.gcp.sql."; - private static final Log LOGGER = LogFactory.getLog(CloudSqlEnvironmentPostProcessor.class); @@ -57,7 +60,24 @@ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringAp LOGGER.info("post-processing Cloud SQL properties for + " + databaseType.name()); } - CloudSqlJdbcInfoProvider cloudSqlJdbcInfoProvider = buildCloudSqlJdbcInfoProvider(environment, databaseType); + // Bind properties + Binder binder = Binder.get(environment); + String cloudSqlPropertiesPrefix = GcpCloudSqlProperties.class.getAnnotation(ConfigurationProperties.class).value(); + String gcpPropertiesPrefix = GcpProperties.class.getAnnotation(ConfigurationProperties.class).value(); + GcpCloudSqlProperties sqlProperties = binder + .bind(cloudSqlPropertiesPrefix, GcpCloudSqlProperties.class) + .orElse(new GcpCloudSqlProperties()); + GcpProperties gcpProperties = binder + .bind(cloudSqlPropertiesPrefix, GcpProperties.class) + .orElse(new GcpProperties()); + + CloudSqlJdbcInfoProvider cloudSqlJdbcInfoProvider = new DefaultCloudSqlJdbcInfoProvider(sqlProperties, databaseType); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Default " + databaseType.name() + + " JdbcUrl provider. Connecting to " + + cloudSqlJdbcInfoProvider.getJdbcUrl() + " with driver " + + cloudSqlJdbcInfoProvider.getJdbcDriverClass()); + } // configure default JDBC driver and username as fallback values when not specified Map fallbackMap = new HashMap<>(); @@ -72,7 +92,7 @@ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringAp environment.getPropertySources() .addFirst(new MapPropertySource("CLOUD_SQL_DATA_SOURCE_URL", primaryMap)); - setCredentials(environment, application); + setCredentials(sqlProperties, gcpProperties); // support usage metrics CoreSocketFactory.setApplicationName("spring-cloud-gcp-sql/" @@ -81,7 +101,7 @@ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringAp } private DatabaseType getEnabledDatabaseType(ConfigurableEnvironment environment) { - if (Boolean.parseBoolean(getSqlProperty(environment, "enabled", "true")) + if (Boolean.parseBoolean(environment.getProperty("spring.cloud.gcp.sql.enabled", "true")) && isOnClasspath("javax.sql.DataSource") && isOnClasspath("org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType") && isOnClasspath("com.google.cloud.sql.CredentialFactory")) { @@ -98,50 +118,7 @@ && isOnClasspath("org.postgresql.Driver")) { } private boolean isOnClasspath(String className) { - try { - ClassUtils.forName(className, null); - return true; - } - catch (ClassNotFoundException ex) { - return false; - } - } - - private CloudSqlJdbcInfoProvider buildCloudSqlJdbcInfoProvider(ConfigurableEnvironment environment, DatabaseType databaseType) { - CloudSqlJdbcInfoProvider cloudSqlJdbcInfoProvider = new DefaultCloudSqlJdbcInfoProvider( - getSqlProperty(environment, "database-name", null), - getSqlProperty(environment, "instance-connection-name", null), - getSqlProperty(environment, "ip-types", null), - databaseType); - if (LOGGER.isInfoEnabled()) { - LOGGER.info("Default " + databaseType.name() - + " JdbcUrl provider. Connecting to " - + cloudSqlJdbcInfoProvider.getJdbcUrl() + " with driver " - + cloudSqlJdbcInfoProvider.getJdbcDriverClass()); - } - return cloudSqlJdbcInfoProvider; - } - - private String getSqlProperty(ConfigurableEnvironment environment, String shortName, String defaultValue) { - return environment.getProperty(CLOUD_SQL_PROPERTIES_PREFIX + shortName, defaultValue); - } - - private void setCredentials(ConfigurableEnvironment environment, SpringApplication application) { - String encodedKey = getSqlProperty(environment, "credentials.encoded-key", null); - if (encodedKey != null) { - setCredentialsEncodedKeyProperty(encodedKey); - } - else { - setCredentialsFileProperty(environment, application); - } - } - - private void setCredentialsEncodedKeyProperty(String encodedKey) { - System.setProperty(SqlCredentialFactory.CREDENTIAL_ENCODED_KEY_PROPERTY_NAME, - encodedKey); - - System.setProperty(CredentialFactory.CREDENTIAL_FACTORY_PROPERTY, - SqlCredentialFactory.class.getName()); + return ClassUtils.isPresent(className, null); } /** @@ -158,46 +135,59 @@ private void setCredentialsEncodedKeyProperty(String encodedKey) { *

If user didn't specify credentials, the socket factory already does the right thing by * using the application default credentials by default. So we don't need to do anything. */ - private void setCredentialsFileProperty(ConfigurableEnvironment environment, SpringApplication application) { - File credentialsLocationFile; + private void setCredentials(GcpCloudSqlProperties sqlProperties, GcpProperties gcpProperties) { + Credentials credentials = null; + // First tries the SQL configuration credential. + if (sqlProperties.getCredentials().hasKey()) { + credentials = sqlProperties.getCredentials(); + } + // Then, the global credential. + else { + credentials = gcpProperties.getCredentials(); + } + + if (credentials.getEncodedKey() != null) { + setCredentialsEncodedKeyProperty(credentials.getEncodedKey()); + } + else if (credentials.getLocation() != null) { + setCredentialsFileProperty(credentials.getLocation()); + } + // Else do nothing, let sockets factory use application default credentials. + } + + private void setCredentialsEncodedKeyProperty(String encodedKey) { + System.setProperty(SqlCredentialFactory.CREDENTIAL_ENCODED_KEY_PROPERTY_NAME, + encodedKey); + + System.setProperty(CredentialFactory.CREDENTIAL_FACTORY_PROPERTY, + SqlCredentialFactory.class.getName()); + } + + + private void setCredentialsFileProperty(Resource credentialsLocation) { try { - String sqlCredentialsLocation = getSqlProperty(environment, "credentials.location", null); - String globalCredentialsLocation = environment.getProperty("spring.cloud.gcp.credentials.location", (String) null); - // First tries the SQL configuration credential. - if (sqlCredentialsLocation != null) { - credentialsLocationFile = application.getResourceLoader().getResource(sqlCredentialsLocation).getFile(); - setSystemProperties(credentialsLocationFile); - } - // Then, the global credential. - else if (globalCredentialsLocation != null) { - // A resource might not be in the filesystem, but the Cloud SQL credential must. - credentialsLocationFile = application.getResourceLoader().getResource(globalCredentialsLocation).getFile(); - setSystemProperties(credentialsLocationFile); + // A resource might not be in the filesystem, but the Cloud SQL credential must. + File credentialsLocationFile = credentialsLocation.getFile(); + + // This should happen if the Spring resource isn't in the filesystem, but a URL, + // classpath file, etc. + if (credentialsLocationFile == null) { + LOGGER.info("The private key of the Google Cloud SQL credential must " + + "be in a file on the filesystem."); + return; } - // Else do nothing, let sockets factory use application default credentials. + System.setProperty(SqlCredentialFactory.CREDENTIAL_LOCATION_PROPERTY_NAME, + credentialsLocationFile.getAbsolutePath()); + // If there are specified credentials, tell sockets factory to use them. + System.setProperty(CredentialFactory.CREDENTIAL_FACTORY_PROPERTY, + SqlCredentialFactory.class.getName()); } catch (IOException ioe) { LOGGER.info("Error reading Cloud SQL credentials file.", ioe); } } - private void setSystemProperties(File credentialsLocationFile) { - // This should happen if the Spring resource isn't in the filesystem, but a URL, - // classpath file, etc. - if (credentialsLocationFile == null) { - LOGGER.info("The private key of the Google Cloud SQL credential must " - + "be in a file on the filesystem."); - return; - } - - System.setProperty(SqlCredentialFactory.CREDENTIAL_LOCATION_PROPERTY_NAME, - credentialsLocationFile.getAbsolutePath()); - - // If there are specified credentials, tell sockets factory to use them. - System.setProperty(CredentialFactory.CREDENTIAL_FACTORY_PROPERTY, - SqlCredentialFactory.class.getName()); - } } diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/DefaultCloudSqlJdbcInfoProvider.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/DefaultCloudSqlJdbcInfoProvider.java index fcfc187275..608f83e083 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/DefaultCloudSqlJdbcInfoProvider.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/DefaultCloudSqlJdbcInfoProvider.java @@ -27,21 +27,19 @@ * @author Ray Tsang * @author João André Martins * @author Øystein Urdahl Hardeng - * @author Mike Eltsufin */ public class DefaultCloudSqlJdbcInfoProvider implements CloudSqlJdbcInfoProvider { - private final String databaseName; - private final String instanceConnectionName; - private final String ipTypes; + + private final GcpCloudSqlProperties properties; + private final DatabaseType databaseType; - public DefaultCloudSqlJdbcInfoProvider(String databaseName, String instanceConnectionName, String ipTypes, DatabaseType databaseType) { - this.databaseName = databaseName; - this.instanceConnectionName = instanceConnectionName; - this.ipTypes = ipTypes; + public DefaultCloudSqlJdbcInfoProvider(GcpCloudSqlProperties properties, + DatabaseType databaseType) { + this.properties = properties; this.databaseType = databaseType; - Assert.hasText(this.databaseName, "A database name must be provided."); - Assert.hasText(this.instanceConnectionName, + Assert.hasText(this.properties.getDatabaseName(), "A database name must be provided."); + Assert.hasText(properties.getInstanceConnectionName(), "An instance connection name must be provided in the format ::."); } @@ -53,10 +51,10 @@ public String getJdbcDriverClass() { @Override public String getJdbcUrl() { String jdbcUrl = String.format(this.databaseType.getJdbcUrlTemplate(), - this.databaseName, - this.instanceConnectionName); - if (StringUtils.hasText(this.ipTypes)) { - jdbcUrl = String.format(jdbcUrl + "&ipTypes=%s", this.ipTypes); + this.properties.getDatabaseName(), + this.properties.getInstanceConnectionName()); + if (StringUtils.hasText(properties.getIpTypes())) { + jdbcUrl = String.format(jdbcUrl + "&ipTypes=%s", properties.getIpTypes()); } return jdbcUrl; } diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlProperties.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlProperties.java new file mode 100644 index 0000000000..a4a445751f --- /dev/null +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/GcpCloudSqlProperties.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017-2020 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.core.Credentials; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Google Cloud SQL properties. + * + * @author João André Martins + * @author Øystein Urdahl Hardeng + */ +@ConfigurationProperties("spring.cloud.gcp.sql") +public class GcpCloudSqlProperties { + /** Name of the database in the Cloud SQL instance. */ + private String databaseName; + + /** Cloud SQL instance connection name. [GCP_PROJECT_ID]:[INSTANCE_REGION]:[INSTANCE_NAME]. */ + private String instanceConnectionName; + + /** A comma delimited list of preferred IP types for connecting to the Cloud SQL instance. */ + private String ipTypes; + + /** Overrides the GCP OAuth2 credentials specified in the Core module. */ + private Credentials credentials = new Credentials(); + + public String getDatabaseName() { + return this.databaseName; + } + + public void setDatabaseName(String databaseName) { + this.databaseName = databaseName; + } + + public String getInstanceConnectionName() { + return this.instanceConnectionName; + } + + public void setInstanceConnectionName(String instanceConnectionName) { + this.instanceConnectionName = instanceConnectionName; + } + + public String getIpTypes() { + return this.ipTypes; + } + + public void setIpTypes(String ipTypes) { + this.ipTypes = ipTypes; + } + + public Credentials getCredentials() { + return this.credentials; + } + + public void setCredentials(Credentials credentials) { + this.credentials = credentials; + } +}