Skip to content

Commit 82837a2

Browse files
committed
chore: Add domain name to the CloudSqlInstanceName value object
1 parent 773e99a commit 82837a2

File tree

5 files changed

+92
-4
lines changed

5 files changed

+92
-4
lines changed

core/src/main/java/com/google/cloud/sql/core/CloudSqlInstanceName.java

+55
Original file line numberDiff line numberDiff line change
@@ -31,25 +31,76 @@ class CloudSqlInstanceName {
3131
// Some legacy project ids are domain-scoped (e.g. "example.com:PROJECT:REGION:INSTANCE")
3232
private static final Pattern CONNECTION_NAME =
3333
Pattern.compile("([^:]+(:[^:]+)?):([^:]+):([^:]+)");
34+
35+
/**
36+
* The domain name pattern in accordance with RFC 1035, RFC 1123 and RFC 2181.
37+
*
38+
* <p>Explanation of the Regex:
39+
*
40+
* <p>^: Matches the beginning of the string.<br>
41+
* (?=.{1,255}$): Positive lookahead assertion to ensure the domain name is between 1 and 255
42+
* characters long.<br>
43+
* (?!-): Negative lookahead assertion to prevent hyphens at the beginning.<br>
44+
* [A-Za-z0-9-]+: Matches one or more alphanumeric characters or hyphens.<br>
45+
* (\\.[A-Za-z0-9-]+)*: Matches zero or more occurrences of a period followed by one or more
46+
* alphanumeric characters or hyphens (for subdomains).<br>
47+
* \\.: Matches a period before the TLD.<br>
48+
* [A-Za-z]{2,}: Matches two or more letters for the TLD.<br>
49+
* $: Matches the end of the string.<br>
50+
*/
51+
private static final Pattern DOMAIN_NAME =
52+
Pattern.compile("^(?=.{1,255}$)(?!-)[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*\\.[A-Za-z]{2,}$");
53+
3454
private final String projectId;
3555
private final String regionId;
3656
private final String instanceId;
3757
private final String connectionName;
58+
private final String domainName;
59+
60+
/**
61+
* Validates that a string is a well-formed domain name.
62+
*
63+
* @param domain the domain name to check
64+
* @return true if domain is a well-formed domain name.
65+
*/
66+
public static boolean isValidDomain(String domain) {
67+
Matcher matcher = DOMAIN_NAME.matcher(domain);
68+
return matcher.matches();
69+
}
3870

3971
/**
4072
* Initializes a new CloudSqlInstanceName class.
4173
*
4274
* @param connectionName instance connection name in the format "PROJECT_ID:REGION_ID:INSTANCE_ID"
4375
*/
4476
CloudSqlInstanceName(String connectionName) {
77+
this(connectionName, null);
78+
}
79+
80+
/**
81+
* Initializes a new CloudSqlInstanceName class containing the domain name.
82+
*
83+
* @param connectionName instance connection name in the format "PROJECT_ID:REGION_ID:INSTANCE_ID"
84+
* @param domainName the domain name used to look up the instance, or null.
85+
*/
86+
CloudSqlInstanceName(String connectionName, String domainName) {
4587
this.connectionName = connectionName;
88+
this.domainName = domainName;
4689
Matcher matcher = CONNECTION_NAME.matcher(connectionName);
4790
checkArgument(
4891
matcher.matches(),
4992
String.format(
5093
"[%s] Cloud SQL connection name is invalid, expected string in the form of"
5194
+ " \"<PROJECT_ID>:<REGION_ID>:<INSTANCE_ID>\".",
5295
connectionName));
96+
97+
if (domainName != null) {
98+
Matcher domainMatcher = DOMAIN_NAME.matcher(domainName);
99+
checkArgument(
100+
domainMatcher.matches(),
101+
String.format("[%s] Domain name is invalid, expected a valid domain name", domainName));
102+
}
103+
53104
this.projectId = matcher.group(1);
54105
this.regionId = matcher.group(3);
55106
this.instanceId = matcher.group(4);
@@ -71,6 +122,10 @@ String getInstanceId() {
71122
return instanceId;
72123
}
73124

125+
String getDomainName() {
126+
return domainName;
127+
}
128+
74129
@Override
75130
public String toString() {
76131
return connectionName;

core/src/main/java/com/google/cloud/sql/core/Connector.java

+7
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ ConnectionInfoCache getConnection(final ConnectionConfig config) {
157157
return instance;
158158
}
159159

160+
/**
161+
* Updates the ConnectionConfig to ensure that the cloudSqlInstance field is set, resolving the
162+
* domainName using the InstanceNameResolver.
163+
*
164+
* @param config the configuration to resolve.
165+
* @return a ConnectionConfig guaranteed to have the CloudSqlInstance field set.
166+
*/
160167
private ConnectionConfig resolveConnectionName(ConnectionConfig config) {
161168
// If domainName is not set, return the original configuration unmodified.
162169
if (config.getDomainName() == null || config.getDomainName().isEmpty()) {

core/src/main/java/com/google/cloud/sql/core/LazyRefreshConnectionInfoCache.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,15 @@ public LazyRefreshConnectionInfoCache(
4444
ConnectionInfoRepository connectionInfoRepository,
4545
CredentialFactory tokenSourceFactory,
4646
KeyPair keyPair) {
47+
48+
CloudSqlInstanceName instanceName =
49+
new CloudSqlInstanceName(config.getCloudSqlInstance(), config.getDomainName());
50+
4751
this.config = config;
48-
this.instanceName = new CloudSqlInstanceName(config.getCloudSqlInstance());
52+
this.instanceName = instanceName;
4953

5054
AccessTokenSupplier accessTokenSupplier =
5155
DefaultAccessTokenSupplier.newInstance(config.getAuthType(), tokenSourceFactory);
52-
CloudSqlInstanceName instanceName = new CloudSqlInstanceName(config.getCloudSqlInstance());
5356

5457
this.refreshStrategy =
5558
new LazyRefreshStrategy(

core/src/main/java/com/google/cloud/sql/core/RefreshAheadConnectionInfoCache.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,15 @@ public RefreshAheadConnectionInfoCache(
4848
ListeningScheduledExecutorService executor,
4949
ListenableFuture<KeyPair> keyPair,
5050
long minRefreshDelayMs) {
51+
52+
CloudSqlInstanceName instanceName =
53+
new CloudSqlInstanceName(config.getCloudSqlInstance(), config.getDomainName());
54+
5155
this.config = config;
52-
this.instanceName = new CloudSqlInstanceName(config.getCloudSqlInstance());
56+
this.instanceName = instanceName;
5357

5458
AccessTokenSupplier accessTokenSupplier =
5559
DefaultAccessTokenSupplier.newInstance(config.getAuthType(), tokenSourceFactory);
56-
CloudSqlInstanceName instanceName = new CloudSqlInstanceName(config.getCloudSqlInstance());
5760

5861
this.refreshStrategy =
5962
new RefreshAheadStrategy(

core/src/test/java/com/google/cloud/sql/core/ConnectorTest.java

+20
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,26 @@ public void create_successfulPublicConnectionWithDomainName()
154154
assertThat(readLine(socket)).isEqualTo(SERVER_MESSAGE);
155155
}
156156

157+
@Test
158+
public void create_successfulPrivateConnection_UsesInstanceName_DomainNameIgnored()
159+
throws IOException, InterruptedException {
160+
FakeSslServer sslServer = new FakeSslServer();
161+
ConnectionConfig config =
162+
new ConnectionConfig.Builder()
163+
.withDomainName("db.example.com")
164+
.withCloudSqlInstance("myProject:myRegion:myInstance")
165+
.withIpTypes("PRIVATE")
166+
.build();
167+
168+
int port = sslServer.start(PRIVATE_IP);
169+
170+
Connector connector = newConnector(config.getConnectorConfig(), port);
171+
172+
Socket socket = connector.connect(config, TEST_MAX_REFRESH_MS);
173+
174+
assertThat(readLine(socket)).isEqualTo(SERVER_MESSAGE);
175+
}
176+
157177
@Test
158178
public void create_throwsErrorForDomainNameWithNoResolver()
159179
throws IOException, InterruptedException {

0 commit comments

Comments
 (0)