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 TPC #1901

Merged
merged 1 commit into from
Apr 15, 2024
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
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 @@ -305,6 +305,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
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