Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for configuring Admin API URL #1617

Merged
merged 4 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions core/src/main/java/com/google/cloud/sql/ConnectionConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public class ConnectionConfig {
public static final String CLOUD_SQL_INSTANCE_PROPERTY = "cloudSqlInstance";
public static final String CLOUD_SQL_DELEGATES_PROPERTY = "cloudSqlDelegates";
public static final String CLOUD_SQL_TARGET_PRINCIPAL_PROPERTY = "cloudSqlTargetPrincipal";
public static final String CLOUD_SQL_ADMIN_ROOT_URL_PROPERTY = "cloudSqlAdminRootUrl";
public static final String CLOUD_SQL_ADMIN_SERVICE_PATH_PROPERTY = "cloudSqlAdminServicePath";
public static final String UNIX_SOCKET_PROPERTY = "unixSocketPath";
public static final String ENABLE_IAM_AUTH_PROPERTY = "enableIamAuth";
public static final String IP_TYPES_PROPERTY = "ipTypes";
Expand All @@ -45,6 +47,8 @@ public class ConnectionConfig {
private final String unixSocketPath;
private final AuthType authType;
private final List<IpType> ipTypes;
private final String adminRootUrl;
private final String adminServicePath;

/** Create a new ConnectionConfig from the well known JDBC Connection properties. */
public static ConnectionConfig fromConnectionProperties(Properties props) {
Expand All @@ -67,9 +71,20 @@ public static ConnectionConfig fromConnectionProperties(Properties props) {
listIpTypes(
props.getProperty(
ConnectionConfig.IP_TYPES_PROPERTY, ConnectionConfig.DEFAULT_IP_TYPES));
final String adminRootUrl =
props.getProperty(ConnectionConfig.CLOUD_SQL_ADMIN_ROOT_URL_PROPERTY);
final String adminServicePath =
props.getProperty(ConnectionConfig.CLOUD_SQL_ADMIN_SERVICE_PATH_PROPERTY);

return new ConnectionConfig(
csqlInstanceName, targetPrincipal, delegates, unixSocketPath, authType, ipTypes);
csqlInstanceName,
targetPrincipal,
delegates,
unixSocketPath,
authType,
ipTypes,
adminRootUrl,
adminServicePath);
}

/**
Expand Down Expand Up @@ -101,13 +116,17 @@ private ConnectionConfig(
List<String> delegates,
String unixSocketPath,
AuthType authType,
List<IpType> ipTypes) {
List<IpType> ipTypes,
String adminRootUrl,
String adminServicePath) {
this.cloudSqlInstance = cloudSqlInstance;
this.targetPrincipal = targetPrincipal;
this.delegates = delegates;
this.unixSocketPath = unixSocketPath;
this.authType = authType;
this.ipTypes = ipTypes;
this.adminRootUrl = adminRootUrl;
this.adminServicePath = adminServicePath;
}

public String getCloudSqlInstance() {
Expand All @@ -134,6 +153,14 @@ public List<IpType> getIpTypes() {
return ipTypes;
}

public String getAdminRootUrl() {
return adminRootUrl;
}

public String getAdminServicePath() {
return adminServicePath;
}

/** The builder for the ConnectionConfig. */
public static class Builder {

Expand All @@ -143,6 +170,8 @@ public static class Builder {
private String unixSocketPath;
private AuthType authType = DEFAULT_AUTH_TYPE;
private List<IpType> ipTypes = DEFAULT_IP_TYPE_LIST;
private String adminRootUrl;
private String adminServicePath;

public Builder withCloudSqlInstance(String cloudSqlInstance) {
this.cloudSqlInstance = cloudSqlInstance;
Expand Down Expand Up @@ -181,9 +210,27 @@ public Builder withIpTypes(List<IpType> ipTypes) {
return this;
}

public Builder withAdminRootUrl(String adminRootUrl) {
this.adminRootUrl = adminRootUrl;
return this;
}

public Builder withAdminServicePath(String adminServicePath) {
this.adminServicePath = adminServicePath;
return this;
}

/** Builds a new instance of {@code ConnectionConfig}. */
public ConnectionConfig build() {
return new ConnectionConfig(
cloudSqlInstance, targetPrincipal, delegates, unixSocketPath, authType, ipTypes);
cloudSqlInstance,
targetPrincipal,
delegates,
unixSocketPath,
authType,
ipTypes,
adminRootUrl,
adminServicePath);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
package com.google.cloud.sql.core;

import com.google.api.client.http.HttpRequestInitializer;
import com.google.cloud.sql.ConnectionConfig;

/** Factory interface for creating SQLAdmin clients to interact with Cloud SQL Admin API. */
public interface ApiFetcherFactory {

SqlAdminApiFetcher create(HttpRequestInitializer credentials);
SqlAdminApiFetcher create(HttpRequestInitializer credentials, ConnectionConfig config);
}
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ private CloudSqlInstance apiFetcher(ConnectionConfig config) {
}

HttpRequestInitializer credential = instanceCredentialFactory.create();
SqlAdminApiFetcher adminApi = apiFetcherFactory.create(credential);
SqlAdminApiFetcher adminApi = apiFetcherFactory.create(credential, config);

return new CloudSqlInstance(
config.getCloudSqlInstance(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,12 @@
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.sqladmin.SQLAdmin;
import com.google.cloud.sql.ConnectionConfig;
import java.io.IOException;
import java.security.GeneralSecurityException;

/** Factory for creating a SQLAdmin client that interacts with the real SQL Admin API. */
public class SqlAdminApiFetcherFactory implements ApiFetcherFactory {
// Test properties, not for end-user use. May be changed or removed without notice.
private static final String API_ROOT_URL_PROPERTY = "_CLOUD_SQL_API_ROOT_URL";
private static final String API_SERVICE_PATH_PROPERTY = "_CLOUD_SQL_API_SERVICE_PATH";

private final String rootUrl;
private final String servicePath;
private final String userAgents;

/**
Expand All @@ -42,12 +37,11 @@ public class SqlAdminApiFetcherFactory implements ApiFetcherFactory {
*/
public SqlAdminApiFetcherFactory(String userAgents) {
this.userAgents = userAgents;
this.rootUrl = System.getProperty(API_ROOT_URL_PROPERTY);
this.servicePath = System.getProperty(API_SERVICE_PATH_PROPERTY);
}

@Override
public SqlAdminApiFetcher create(HttpRequestInitializer requestInitializer) {
public SqlAdminApiFetcher create(
HttpRequestInitializer requestInitializer, ConnectionConfig config) {
HttpTransport httpTransport;
try {
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
Expand All @@ -59,11 +53,11 @@ public SqlAdminApiFetcher create(HttpRequestInitializer requestInitializer) {
SQLAdmin.Builder adminApiBuilder =
new SQLAdmin.Builder(httpTransport, jsonFactory, requestInitializer)
.setApplicationName(userAgents);
if (rootUrl != null) {
adminApiBuilder.setRootUrl(rootUrl);
if (config.getAdminRootUrl() != null) {
adminApiBuilder.setRootUrl(config.getAdminRootUrl());
}
if (servicePath != null) {
adminApiBuilder.setServicePath(servicePath);
if (config.getAdminServicePath() != null) {
adminApiBuilder.setServicePath(config.getAdminServicePath());
}
return new SqlAdminApiFetcher(adminApiBuilder.build());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public void testConfigFromProps() {
final List<IpType> wantIpTypes =
Arrays.asList(IpType.PSC, IpType.PRIVATE, IpType.PUBLIC); // PUBLIC is replaced with PRIMARY
final String ipTypes = "psc,Private,PUBLIC";
final String wantAdminRootUrl = "https://googleapis.example.com/";
final String wantAdminServicePath = "sqladmin/";

Properties props = new Properties();
props.setProperty(ConnectionConfig.CLOUD_SQL_INSTANCE_PROPERTY, wantCsqlInstance);
Expand All @@ -45,6 +47,8 @@ public void testConfigFromProps() {
props.setProperty(ConnectionConfig.ENABLE_IAM_AUTH_PROPERTY, iamAuthN);
props.setProperty(ConnectionConfig.UNIX_SOCKET_PROPERTY, wantUnixSocket);
props.setProperty(ConnectionConfig.IP_TYPES_PROPERTY, ipTypes);
props.setProperty(ConnectionConfig.CLOUD_SQL_ADMIN_ROOT_URL_PROPERTY, wantAdminRootUrl);
props.setProperty(ConnectionConfig.CLOUD_SQL_ADMIN_SERVICE_PATH_PROPERTY, wantAdminServicePath);

ConnectionConfig c = ConnectionConfig.fromConnectionProperties(props);

Expand All @@ -54,6 +58,8 @@ public void testConfigFromProps() {
assertThat(c.getAuthType()).isEqualTo(AuthType.IAM);
assertThat(c.getUnixSocketPath()).isEqualTo(wantUnixSocket);
assertThat(c.getIpTypes()).isEqualTo(wantIpTypes);
assertThat(c.getAdminRootUrl()).isEqualTo(wantAdminRootUrl);
assertThat(c.getAdminServicePath()).isEqualTo(wantAdminServicePath);
}

@Test
Expand All @@ -64,6 +70,8 @@ public void testConfigFromBuilder() {
final String wantUnixSocket = "/path/to/socket";
final List<IpType> wantIpTypes = Arrays.asList(IpType.PSC, IpType.PRIVATE, IpType.PUBLIC);
final AuthType wantAuthType = AuthType.PASSWORD;
final String wantAdminRootUrl = "https://googleapis.example.com/";
final String wantAdminServicePath = "sqladmin/";

ConnectionConfig c =
new ConnectionConfig.Builder()
Expand All @@ -73,6 +81,8 @@ public void testConfigFromBuilder() {
.withIpTypes(wantIpTypes)
.withUnixSocketPath(wantUnixSocket)
.withAuthType(wantAuthType)
.withAdminRootUrl(wantAdminRootUrl)
.withAdminServicePath(wantAdminServicePath)
.build();

assertThat(c.getCloudSqlInstance()).isEqualTo(wantCsqlInstance);
Expand All @@ -81,5 +91,7 @@ public void testConfigFromBuilder() {
assertThat(c.getAuthType()).isEqualTo(wantAuthType);
assertThat(c.getUnixSocketPath()).isEqualTo(wantUnixSocket);
assertThat(c.getIpTypes()).isEqualTo(wantIpTypes);
assertThat(c.getAdminRootUrl()).isEqualTo(wantAdminRootUrl);
assertThat(c.getAdminServicePath()).isEqualTo(wantAdminServicePath);
}
}
45 changes: 33 additions & 12 deletions core/src/test/java/com/google/cloud/sql/core/MockAdminApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ public class MockAdminApi {

private static final Pattern CONNECT_SETTINGS_PATTERN =
Pattern.compile(
".*/sql/v1beta4/projects/(?<project>.*)/instances/(?<instance>.*)/connectSettings");
"(?<baseUrl>.*)sql/v1beta4/projects/(?<project>.*)/instances/(?<instance>.*)/connectSettings");
private static final Pattern GENERATE_EPHEMERAL_CERT_PATTERN =
Pattern.compile(
".*/sql/v1beta4/projects/(?<project>.*)/instances/(?<instance>.*):generateEphemeralCert");
"(?<baseUrl>.*)sql/v1beta4/projects/(?<project>.*)/instances/(?<instance>.*):generateEphemeralCert");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. This is as good as an integration test, since it actually exercises the http client.

private final KeyPair clientKeyPair;
private final PrivateKey proxyServerPrivateKey;
private final List<ConnectSettingsRequest> connectSettingsRequests;
Expand Down Expand Up @@ -117,7 +117,8 @@ public void addConnectSettingsResponse(
String publicIp,
String privateIp,
String databaseVersion,
String pscHostname) {
String pscHostname,
String baseUrl) {
CloudSqlInstanceName cloudSqlInstanceName = new CloudSqlInstanceName(instanceConnectionName);

ArrayList<IpMapping> ipMappings = new ArrayList<>();
Expand All @@ -140,11 +141,12 @@ public void addConnectSettingsResponse(
.setRegion(cloudSqlInstanceName.getRegionId());
settings.setFactory(GsonFactory.getDefaultInstance());

connectSettingsRequests.add(new ConnectSettingsRequest(cloudSqlInstanceName, settings));
connectSettingsRequests.add(
new ConnectSettingsRequest(cloudSqlInstanceName, settings, baseUrl));
}

public void addGenerateEphemeralCertResponse(
String instanceConnectionName, Duration ephemeralCertExpiration)
String instanceConnectionName, Duration ephemeralCertExpiration, String baseUrl)
throws GeneralSecurityException, OperatorCreationException {
CloudSqlInstanceName cloudSqlInstanceName = new CloudSqlInstanceName(instanceConnectionName);

Expand All @@ -155,7 +157,8 @@ public void addGenerateEphemeralCertResponse(
generateEphemeralCertResponse.setFactory(GsonFactory.getDefaultInstance());

generateEphemeralCertRequests.add(
new GenerateEphemeralCertRequest(cloudSqlInstanceName, generateEphemeralCertResponse));
new GenerateEphemeralCertRequest(
cloudSqlInstanceName, generateEphemeralCertResponse, baseUrl));
}

public HttpTransport getHttpTransport() {
Expand All @@ -171,7 +174,9 @@ public LowLevelHttpResponse execute() throws IOException {
int i = allConnectSettingsRequestsIndex.getAndIncrement();
ConnectSettingsRequest connectSettingsRequest = connectSettingsRequests.get(i);
if (isRequestUnknown(
connectSettingsMatcher, connectSettingsRequest.getCloudSqlInstanceName())) {
connectSettingsMatcher,
connectSettingsRequest.getCloudSqlInstanceName(),
connectSettingsRequest.getBaseUrl())) {
throw new RuntimeException("Unrecognized request: GET " + url);
}
return new MockLowLevelHttpResponse()
Expand All @@ -188,7 +193,8 @@ public LowLevelHttpResponse execute() throws IOException {
generateEphemeralCertRequests.get(i);
if (isRequestUnknown(
generateEphemeralMatcher,
generateEphemeralCertRequest.getCloudSqlInstanceName())) {
generateEphemeralCertRequest.getCloudSqlInstanceName(),
generateEphemeralCertRequest.getBaseUrl())) {
throw new RuntimeException("Unrecognized request: GET " + url);
}
return new MockLowLevelHttpResponse()
Expand Down Expand Up @@ -237,20 +243,24 @@ private String createEphemeralCert(Duration timeUntilExpiration)
+ "-----END CERTIFICATE-----\n";
}

private boolean isRequestUnknown(Matcher urlMatcher, CloudSqlInstanceName cloudSqlInstanceName) {
private boolean isRequestUnknown(
Matcher urlMatcher, CloudSqlInstanceName cloudSqlInstanceName, String baseUrl) {
return !urlMatcher.group("project").equals(cloudSqlInstanceName.getProjectId())
|| !urlMatcher.group("instance").equals(cloudSqlInstanceName.getInstanceId());
|| !urlMatcher.group("instance").equals(cloudSqlInstanceName.getInstanceId())
|| !urlMatcher.group("baseUrl").equals(baseUrl);
}

private static class ConnectSettingsRequest {

private final CloudSqlInstanceName cloudSqlInstanceName;
private final ConnectSettings settings;
private final String baseUrl;

public ConnectSettingsRequest(
CloudSqlInstanceName cloudSqlInstanceName, ConnectSettings settings) {
CloudSqlInstanceName cloudSqlInstanceName, ConnectSettings settings, String baseUrl) {
this.cloudSqlInstanceName = cloudSqlInstanceName;
this.settings = settings;
this.baseUrl = baseUrl;
}

public ConnectSettings getSettings() {
Expand All @@ -260,18 +270,25 @@ public ConnectSettings getSettings() {
public CloudSqlInstanceName getCloudSqlInstanceName() {
return cloudSqlInstanceName;
}

public String getBaseUrl() {
return baseUrl;
}
}

private static class GenerateEphemeralCertRequest {

private final CloudSqlInstanceName cloudSqlInstanceName;
private final GenerateEphemeralCertResponse generateEphemeralCertResponse;
private final String baseUrl;

public GenerateEphemeralCertRequest(
CloudSqlInstanceName instanceConnectionName,
GenerateEphemeralCertResponse generateEphemeralCertResponse) {
GenerateEphemeralCertResponse generateEphemeralCertResponse,
String baseUrl) {
this.cloudSqlInstanceName = instanceConnectionName;
this.generateEphemeralCertResponse = generateEphemeralCertResponse;
this.baseUrl = baseUrl;
}

public CloudSqlInstanceName getCloudSqlInstanceName() {
Expand All @@ -281,6 +298,10 @@ public CloudSqlInstanceName getCloudSqlInstanceName() {
public GenerateEphemeralCertResponse getGenerateEphemeralCertResponse() {
return generateEphemeralCertResponse;
}

public String getBaseUrl() {
return baseUrl;
}
}

private static class MockRefreshHandler implements OAuth2RefreshHandler {
Expand Down
Loading