Skip to content

Commit

Permalink
chore: Add domain name to the CloudSqlInstanceName value object
Browse files Browse the repository at this point in the history
  • Loading branch information
hessjcg committed Jan 17, 2025
1 parent 4c8722e commit 14f7483
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,81 @@ class CloudSqlInstanceName {
// Some legacy project ids are domain-scoped (e.g. "example.com:PROJECT:REGION:INSTANCE")
private static final Pattern CONNECTION_NAME =
Pattern.compile("([^:]+(:[^:]+)?):([^:]+):([^:]+)");

/**
* The domain name pattern in accordance with RFC 1035, RFC 1123 and RFC 2181.
*
* <p>Explanation of the Regex:
*
* <p>^: Matches the beginning of the string.<br>
* (?=.{1,255}$): Positive lookahead assertion to ensure the domain name is between 1 and 255
* characters long.<br>
* (?!-): Negative lookahead assertion to prevent hyphens at the beginning.<br>
* [A-Za-z0-9-]+: Matches one or more alphanumeric characters or hyphens.<br>
* (\\.[A-Za-z0-9-]+)*: Matches zero or more occurrences of a period followed by one or more
* alphanumeric characters or hyphens (for subdomains).<br>
* \\.: Matches a period before the TLD.<br>
* [A-Za-z]{2,}: Matches two or more letters for the TLD.<br>
* $: Matches the end of the string.<br>
*/
private static final Pattern DOMAIN_NAME =
Pattern.compile("^(?=.{1,255}$)(?!-)[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*\\.[A-Za-z]{2,}$");

private final String projectId;
private final String regionId;
private final String instanceId;
private final String connectionName;
private final String domainName;

/**
* Validates that a string is a well-formed domain name.
*
* @param domain the domain name to check
* @return true if domain is a well-formed domain name.
*/
public static boolean isValidDomain(String domain) {
Matcher matcher = DOMAIN_NAME.matcher(domain);
return matcher.matches();
}

/**
* Initializes a new CloudSqlInstanceName class.
*
* @param connectionName instance connection name in the format "PROJECT_ID:REGION_ID:INSTANCE_ID"
*/
CloudSqlInstanceName(String connectionName) {
this.connectionName = connectionName;
this(connectionName, null);
}

/**
* Initializes a new CloudSqlInstanceName class containing the domain name.
*
* @param connectionName instance connection name in the format "PROJECT_ID:REGION_ID:INSTANCE_ID"
* @param domainName the domain name used to look up the instance, or null.
*/
CloudSqlInstanceName(String connectionName, String domainName) {
Matcher matcher = CONNECTION_NAME.matcher(connectionName);
checkArgument(
matcher.matches(),
String.format(
"[%s] Cloud SQL connection name is invalid, expected string in the form of"
+ " \"<PROJECT_ID>:<REGION_ID>:<INSTANCE_ID>\".",
connectionName));
this.connectionName = connectionName;
this.projectId = matcher.group(1);
this.regionId = matcher.group(3);
this.instanceId = matcher.group(4);

// Only set this.domainName when it is not empty
if (domainName != null && !domainName.isEmpty()) {
Matcher domainMatcher = DOMAIN_NAME.matcher(domainName);
checkArgument(
domainMatcher.matches(),
String.format("[%s] Domain name is invalid, expected a valid domain name", domainName));
this.domainName = domainName;
} else {
this.domainName = null;
}
}

String getConnectionName() {
Expand All @@ -71,6 +124,10 @@ String getInstanceId() {
return instanceId;
}

String getDomainName() {
return domainName;
}

@Override
public String toString() {
return connectionName;
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/java/com/google/cloud/sql/core/Connector.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ ConnectionInfoCache getConnection(final ConnectionConfig config) {
return instance;
}

/**
* Updates the ConnectionConfig to ensure that the cloudSqlInstance field is set, resolving the
* domainName using the InstanceNameResolver.
*
* @param config the configuration to resolve.
* @return a ConnectionConfig guaranteed to have the CloudSqlInstance field set.
*/
private ConnectionConfig resolveConnectionName(ConnectionConfig config) {
// If domainName is not set, return the original configuration unmodified.
if (config.getDomainName() == null || config.getDomainName().isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@ public LazyRefreshConnectionInfoCache(
ConnectionInfoRepository connectionInfoRepository,
CredentialFactory tokenSourceFactory,
KeyPair keyPair) {

CloudSqlInstanceName instanceName =
new CloudSqlInstanceName(config.getCloudSqlInstance(), config.getDomainName());

this.config = config;
this.instanceName = new CloudSqlInstanceName(config.getCloudSqlInstance());
this.instanceName = instanceName;

AccessTokenSupplier accessTokenSupplier =
DefaultAccessTokenSupplier.newInstance(config.getAuthType(), tokenSourceFactory);
CloudSqlInstanceName instanceName = new CloudSqlInstanceName(config.getCloudSqlInstance());

this.refreshStrategy =
new LazyRefreshStrategy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,15 @@ public RefreshAheadConnectionInfoCache(
ListeningScheduledExecutorService executor,
ListenableFuture<KeyPair> keyPair,
long minRefreshDelayMs) {

CloudSqlInstanceName instanceName =
new CloudSqlInstanceName(config.getCloudSqlInstance(), config.getDomainName());

this.config = config;
this.instanceName = new CloudSqlInstanceName(config.getCloudSqlInstance());
this.instanceName = instanceName;

AccessTokenSupplier accessTokenSupplier =
DefaultAccessTokenSupplier.newInstance(config.getAuthType(), tokenSourceFactory);
CloudSqlInstanceName instanceName = new CloudSqlInstanceName(config.getCloudSqlInstance());

this.refreshStrategy =
new RefreshAheadStrategy(
Expand Down
40 changes: 40 additions & 0 deletions core/src/test/java/com/google/cloud/sql/core/ConnectorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,46 @@ public void create_successfulPublicConnectionWithDomainName()
assertThat(readLine(socket)).isEqualTo(SERVER_MESSAGE);
}

@Test
public void create_successfulPrivateConnection_UsesInstanceName_DomainNameIgnored()
throws IOException, InterruptedException {
FakeSslServer sslServer = new FakeSslServer();
ConnectionConfig config =
new ConnectionConfig.Builder()
.withDomainName("db.example.com")
.withCloudSqlInstance("myProject:myRegion:myInstance")
.withIpTypes("PRIVATE")
.build();

int port = sslServer.start(PRIVATE_IP);

Connector connector = newConnector(config.getConnectorConfig(), port);

Socket socket = connector.connect(config, TEST_MAX_REFRESH_MS);

assertThat(readLine(socket)).isEqualTo(SERVER_MESSAGE);
}

@Test
public void create_successfulPrivateConnection_UsesInstanceName_EmptyDomainNameIgnored()
throws IOException, InterruptedException {
FakeSslServer sslServer = new FakeSslServer();
ConnectionConfig config =
new ConnectionConfig.Builder()
.withDomainName("")
.withCloudSqlInstance("myProject:myRegion:myInstance")
.withIpTypes("PRIVATE")
.build();

int port = sslServer.start(PRIVATE_IP);

Connector connector = newConnector(config.getConnectorConfig(), port);

Socket socket = connector.connect(config, TEST_MAX_REFRESH_MS);

assertThat(readLine(socket)).isEqualTo(SERVER_MESSAGE);
}

@Test
public void create_throwsErrorForDomainNameWithNoResolver()
throws IOException, InterruptedException {
Expand Down

0 comments on commit 14f7483

Please sign in to comment.