Skip to content

Commit

Permalink
feat: add support for TPC
Browse files Browse the repository at this point in the history
  • Loading branch information
ttosta-google committed Mar 20, 2024
1 parent 8cea3f8 commit b814e2a
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 7 deletions.
29 changes: 25 additions & 4 deletions core/src/main/java/com/google/cloud/sql/ConnectorConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class ConnectorConfig {
private final GoogleCredentials googleCredentials;
private final String googleCredentialsPath;
private final String adminQuotaProject;
private final String universeDomain;

private ConnectorConfig(
String targetPrincipal,
Expand All @@ -45,7 +46,8 @@ private ConnectorConfig(
Supplier<GoogleCredentials> googleCredentialsSupplier,
GoogleCredentials googleCredentials,
String googleCredentialsPath,
String adminQuotaProject) {
String adminQuotaProject,
String universeDomain) {
this.targetPrincipal = targetPrincipal;
this.delegates = delegates;
this.adminRootUrl = adminRootUrl;
Expand All @@ -54,6 +56,7 @@ private ConnectorConfig(
this.googleCredentials = googleCredentials;
this.googleCredentialsPath = googleCredentialsPath;
this.adminQuotaProject = adminQuotaProject;
this.universeDomain = universeDomain;
}

@Override
Expand All @@ -72,7 +75,8 @@ public boolean equals(Object o) {
&& Objects.equal(googleCredentialsSupplier, that.googleCredentialsSupplier)
&& Objects.equal(googleCredentials, that.googleCredentials)
&& Objects.equal(googleCredentialsPath, that.googleCredentialsPath)
&& Objects.equal(adminQuotaProject, that.adminQuotaProject);
&& Objects.equal(adminQuotaProject, that.adminQuotaProject)
&& Objects.equal(universeDomain, that.universeDomain);
}

@Override
Expand All @@ -85,7 +89,8 @@ public int hashCode() {
googleCredentialsSupplier,
googleCredentials,
googleCredentialsPath,
adminQuotaProject);
adminQuotaProject,
universeDomain);
}

public String getTargetPrincipal() {
Expand Down Expand Up @@ -120,6 +125,10 @@ public String getAdminQuotaProject() {
return adminQuotaProject;
}

public String getUniverseDomain() {
return universeDomain;
}

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

Expand All @@ -131,6 +140,7 @@ public static class Builder {
private GoogleCredentials googleCredentials;
private String googleCredentialsPath;
private String adminQuotaProject;
private String universeDomain;

public Builder withTargetPrincipal(String targetPrincipal) {
this.targetPrincipal = targetPrincipal;
Expand Down Expand Up @@ -173,6 +183,11 @@ public Builder withAdminQuotaProject(String adminQuotaProject) {
return this;
}

public Builder withUniverseDomain(String universeDomain) {
this.universeDomain = universeDomain;
return this;
}

/** Builds a new instance of {@code ConnectionConfig}. */
public ConnectorConfig build() {
// validate only one GoogleCredentials configuration field set
Expand All @@ -191,6 +206,11 @@ public ConnectorConfig build() {
"Invalid configuration, more than one GoogleCredentials field has a value "
+ "(googleCredentials, googleCredentialsPath, googleCredentialsSupplier)");
}
if (adminRootUrl != null && universeDomain != null) {
throw new IllegalStateException(
"Can not set Admin API Endpoint and Universe Domain together, "
+ "set only Admin API Endpoint (it already contains the universe domain)");
}

return new ConnectorConfig(
targetPrincipal,
Expand All @@ -200,7 +220,8 @@ public ConnectorConfig build() {
googleCredentialsSupplier,
googleCredentials,
googleCredentialsPath,
adminQuotaProject);
adminQuotaProject,
universeDomain);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class ConnectionConfig {
public static final String ENABLE_IAM_AUTH_PROPERTY = "enableIamAuth";
public static final String IP_TYPES_PROPERTY = "ipTypes";
public static final String CLOUD_SQL_ADMIN_QUOTA_PROJECT_PROPERTY = "cloudSqlAdminQuotaProject";
public static final String CLOUD_SQL_UNIVERSE_DOMAIN = "cloudSqlUniverseDomain";
public static final AuthType DEFAULT_AUTH_TYPE = AuthType.PASSWORD;
public static final String DEFAULT_IP_TYPES = "PUBLIC,PRIVATE";
public static final List<IpType> DEFAULT_IP_TYPE_LIST =
Expand Down Expand Up @@ -95,6 +96,8 @@ public static ConnectionConfig fromConnectionProperties(Properties props) {
props.getProperty(ConnectionConfig.CLOUD_SQL_GOOGLE_CREDENTIALS_PATH);
final String adminQuotaProject =
props.getProperty(ConnectionConfig.CLOUD_SQL_ADMIN_QUOTA_PROJECT_PROPERTY);
final String universeDomain = props.getProperty(ConnectionConfig.CLOUD_SQL_UNIVERSE_DOMAIN);

return new ConnectionConfig(
csqlInstanceName,
namedConnection,
Expand All @@ -109,6 +112,7 @@ public static ConnectionConfig fromConnectionProperties(Properties props) {
.withAdminServicePath(adminServicePath)
.withGoogleCredentialsPath(googleCredentialsPath)
.withAdminQuotaProject(adminQuotaProject)
.withUniverseDomain(universeDomain)
.build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ public DefaultConnectionInfoRepository create(
.build();
adminApiBuilder.setGoogleClientRequestInitializer(clientRequestInitializer);
}
if (config.getUniverseDomain() != null) {
adminApiBuilder.setUniverseDomain(config.getUniverseDomain());
}
return new DefaultConnectionInfoRepository(adminApiBuilder.build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,25 @@ private Connector createConnector(ConnectorConfig config) {
CredentialFactory instanceCredentialFactory =
credentialFactoryProvider.getInstanceCredentialFactory(config);

String universeDomain = config.getUniverseDomain();
String credentialsUniverse;
try {
credentialsUniverse = instanceCredentialFactory.getCredentials().getUniverseDomain();
} catch (IOException e) {
throw new IllegalStateException("Fail to fetch the credential universe domain");
}

// Verify that the universe domain provided matches the credential universe domain.
if (credentialsUniverse != null
&& universeDomain != null
&& !credentialsUniverse.equals(universeDomain)) {
throw new IllegalStateException(
String.format(
"The configured universe domain (%s) does not match "
+ "the credential universe domain (%s)",
universeDomain, credentialsUniverse));
}

return new Connector(
config,
connectionInfoRepositoryFactory,
Expand Down
46 changes: 45 additions & 1 deletion core/src/test/java/com/google/cloud/sql/ConnectorConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,28 @@ public void testBuild_withGoogleCredentialsSupplier() {
assertThat(cc.getGoogleCredentialsSupplier()).isSameInstanceAs(wantGoogleCredentialSupplier);
}

@Test
public void testBuild_withUniverseDomain() {
final String wantUniverseDomain = "test-universe.test";
ConnectorConfig cc =
new ConnectorConfig.Builder().withUniverseDomain(wantUniverseDomain).build();
assertThat(cc.getUniverseDomain()).isEqualTo(wantUniverseDomain);
}

@Test
public void testBuild_failsWhenAdminAPIAndUniverseDomainAreSet() {
final String wantAdminRootUrl = "https://googleapis.example.com/";
final String wantUniverseDomain = "test-universe.test";

assertThrows(
IllegalStateException.class,
() ->
new ConnectorConfig.Builder()
.withAdminRootUrl(wantAdminRootUrl)
.withUniverseDomain(wantUniverseDomain)
.build());
}

@Test
public void testBuild_failsWhenManyGoogleCredentialFieldsSet() {
final Supplier<GoogleCredentials> wantGoogleCredentialSupplier =
Expand Down Expand Up @@ -309,6 +331,27 @@ public void testEqual_withAdminQuotaProjectEqual() {
assertThat(k1.hashCode()).isEqualTo(k2.hashCode());
}

@Test
public void testNotEqual_withUniverseDomainNotEqual() {
ConnectorConfig k1 =
new ConnectorConfig.Builder().withUniverseDomain("test-universe.test").build();
ConnectorConfig k2 = new ConnectorConfig.Builder().withUniverseDomain("googleapis.com").build();

assertThat(k1).isNotEqualTo(k2);
assertThat(k1.hashCode()).isNotEqualTo(k2.hashCode());
}

@Test
public void testNotEqual_withUniverseDomainEqual() {
ConnectorConfig k1 =
new ConnectorConfig.Builder().withUniverseDomain("test-universe.test").build();
ConnectorConfig k2 =
new ConnectorConfig.Builder().withUniverseDomain("test-universe.test").build();

assertThat(k1).isEqualTo(k2);
assertThat(k1.hashCode()).isEqualTo(k2.hashCode());
}

@Test
public void testHashCode() {
final String wantTargetPrincipal = "test@example.com";
Expand Down Expand Up @@ -337,6 +380,7 @@ public void testHashCode() {
null, // googleCredentialsSupplier
null, // googleCredentials
wantGoogleCredentialsPath,
wantAdminQuotaProject));
wantAdminQuotaProject,
null)); // universeDomain
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,38 @@ public void registerConnectionFailsWithDuplicateNameAndDifferentConfig()
assertThrows(IllegalArgumentException.class, () -> registry.register("my-connection", config2));
}

@Test
public void registerConnectionFails_withUniverseDomainDoesNotMatchCredentialsDomain()
throws InterruptedException {
final String googleCredentialsPath =
InternalConnectorRegistryTest.class.getResource("/sample-credentials.json").getFile();
final String universeDomain = "test-universe.test";

InternalConnectorRegistry registry = createRegistry(PUBLIC_IP, stubCredentialFactoryProvider);
ConnectorConfig config =
new ConnectorConfig.Builder()
.withGoogleCredentialsPath(googleCredentialsPath)
.withUniverseDomain(universeDomain)
.build();
assertThrows(IllegalStateException.class, () -> registry.register("my-connection", config));
}

@Test
public void registerConnection_withUniverseDomainMatchingCredentialsDomain()
throws InterruptedException {
final String googleCredentialsPath =
InternalConnectorRegistryTest.class.getResource("/sample-credentials.json").getFile();
final String universeDomain = "googleapis.com";

InternalConnectorRegistry registry = createRegistry(PUBLIC_IP, stubCredentialFactoryProvider);
ConnectorConfig config =
new ConnectorConfig.Builder()
.withGoogleCredentialsPath(googleCredentialsPath)
.withUniverseDomain(universeDomain)
.build();
registry.register("my-connection", config);
}

@Test
public void closeNamedConnectionFailsWhenNotFound() throws InterruptedException {
InternalConnectorRegistry registry = createRegistry(PUBLIC_IP, stubCredentialFactoryProvider);
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/resources/sample-credentials.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"quota_project_id": "sample",
"refresh_token": "sample",
"access_token": "sample",
"type": "authorized_user"
"type": "authorized_user",
"universe_domain": "googleapis.com"
}
4 changes: 3 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,11 +280,13 @@ configuration using the Cloud SQL Admin API.
| cloudSqlGoogleCredentialsPath | GOOGLE_CREDENTIALS_PATH | A file path to a JSON file containing a GoogleCredentials oauth token. | `/home/alice/secrets/my-credentials.json` |
| cloudSqlAdminRootUrl | ADMIN_ROOT_URL | An alternate root url for the Cloud SQL admin API. Must end in '/' See [rootUrl](java-api-root-url) | `https://googleapis.example.com/` |
| cloudSqlAdminServicePath | ADMIN_SERVICE_PATH | An alternate path to the SQL Admin API endpoint. Must not begin with '/'. Must end with '/'. See [servicePath](java-api-service-path) | `sqladmin/v1beta1/` |
| cloudSqlAdminQuotaProject | ADMIN_QUOTA_PROJECT | A project ID for quota and billing. See [Quota Project](quota-project) | `my-project` |
| cloudSqlAdminQuotaProject | ADMIN_QUOTA_PROJECT | A project ID for quota and billing. See [Quota Project][quota-project] | `my-project` |
| cloudSqlUniverseDomain | UNIVERSE_DOMAIN | A universe domain for the TPC environment (default is googleapis.com). See [TPC][tpc] | test-universe.test

[java-api-root-url]: https://github.com/googleapis/google-api-java-client/blob/main/google-api-client/src/main/java/com/google/api/client/googleapis/services/AbstractGoogleClient.java#L49
[java-api-service-path]: https://github.com/googleapis/google-api-java-client/blob/main/google-api-client/src/main/java/com/google/api/client/googleapis/services/AbstractGoogleClient.java#L52
[quota-project]: jdbc.md#quota-project
[tpc]: jdbc.md#trusted-partner-cloud-tpc-support

### Connection Configuration Properties

Expand Down
13 changes: 13 additions & 0 deletions docs/jdbc.md
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,19 @@ Properties connProps = new Properties();
connProps.setProperty("cloudSqlAdminQuotaProject", "PROJECT_NAME");
```

### Trusted Partner Cloud (TPC) support

The Java Connector supports setting the universe domain for the TPC environment
with the `cloudSqlUniverseDomain` property. If not specified, defaults to the
Google Default Universe (GDU): googleapis.com.

#### Example

```java
Properties connProps = new Properties();
connProps.setProperty("cloudSqlUniverseDomain", "test-universe.test");
```

## Configuration Reference

- See [Configuration Reference](configuration.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public abstract class GcpConnectionFactoryProvider implements ConnectionFactoryP
public static final Option<String> ADMIN_ROOT_URL = Option.valueOf("ADMIN_ROOT_URL");
public static final Option<String> ADMIN_SERVICE_PATH = Option.valueOf("ADMIN_SERVICE_PATH");
public static final Option<String> ADMIN_QUOTA_PROJECT = Option.valueOf("ADMIN_QUOTA_PROJECT");
public static final Option<String> UNIVERSE_DOMAIN = Option.valueOf("UNIVERSE_DOMAIN");
public static final Option<String> GOOGLE_CREDENTIALS_PATH =
Option.valueOf("GOOGLE_CREDENTIALS_PATH");

Expand Down Expand Up @@ -114,6 +115,7 @@ public ConnectionFactory create(ConnectionFactoryOptions connectionFactoryOption
(String) connectionFactoryOptions.getValue(ADMIN_QUOTA_PROJECT);
final String googleCredentialsPath =
(String) connectionFactoryOptions.getValue(GOOGLE_CREDENTIALS_PATH);
final String universeDomain = (String) connectionFactoryOptions.getValue(UNIVERSE_DOMAIN);

Builder optionBuilder = createBuilder(connectionFactoryOptions);
String cloudSqlInstance = (String) connectionFactoryOptions.getRequiredValue(HOST);
Expand All @@ -131,6 +133,7 @@ public ConnectionFactory create(ConnectionFactoryOptions connectionFactoryOption
.withAdminServicePath(adminServicePath)
.withAdminQuotaProject(adminQuotaProject)
.withGoogleCredentialsPath(googleCredentialsPath)
.withUniverseDomain(universeDomain)
.build())
.build();
// Precompute SSL Data to trigger the initial refresh to happen immediately,
Expand Down

0 comments on commit b814e2a

Please sign in to comment.