1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
15- import { Server , Socket , createServer } from 'node:net' ;
15+ import { createServer , Server , Socket } from 'node:net' ;
1616import tls from 'node:tls' ;
1717import { promisify } from 'node:util' ;
1818import { AuthClient , GoogleAuth } from 'google-auth-library' ;
@@ -22,6 +22,7 @@ import {IpAddressTypes} from './ip-addresses';
2222import { AuthTypes } from './auth-types' ;
2323import { SQLAdminFetcher } from './sqladmin-fetcher' ;
2424import { CloudSQLConnectorError } from './errors' ;
25+ import { isSameInstance , resolveInstanceName } from './parse-instance-connection-name' ;
2526
2627// These Socket types are subsets from nodejs definitely typed repo, ref:
2728// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/ae0fe42ff0e6e820e8ae324acf4f8e944aa1b2b7/types/node/v18/net.d.ts#L437
@@ -43,6 +44,7 @@ export declare interface ConnectionOptions {
4344 authType ?: AuthTypes ;
4445 ipType ?: IpAddressTypes ;
4546 instanceConnectionName : string ;
47+ domainName ?: string ;
4648}
4749
4850export declare interface SocketConnectionOptions extends ConnectionOptions {
@@ -75,66 +77,65 @@ export declare interface TediousDriverOptions {
7577
7678// Internal mapping of the CloudSQLInstances that
7779// adds extra logic to async initialize items.
78- class CloudSQLInstanceMap extends Map {
79- async loadInstance ( {
80- ipType,
81- authType,
82- instanceConnectionName,
83- sqlAdminFetcher,
84- } : {
85- ipType : IpAddressTypes ;
86- authType : AuthTypes ;
87- instanceConnectionName : string ;
88- sqlAdminFetcher : SQLAdminFetcher ;
89- } ) : Promise < void > {
80+ class CloudSQLInstanceMap extends Map < string , CloudSQLInstance > {
81+ private readonly sqlAdminFetcher : SQLAdminFetcher
82+
83+ constructor ( sqlAdminFetcher : SQLAdminFetcher ) {
84+ super ( ) ;
85+ this . sqlAdminFetcher = sqlAdminFetcher ;
86+ }
87+
88+ private cacheKey ( opts : ConnectionOptions ) : string {
89+ //TODO: for now, the cache key function must be synchronous.
90+ // When we implement the async connection info from
91+ // https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector/pull/426
92+ // then the cache key should contain both the domain name
93+ // and the resolved instance name.
94+ return ( opts . instanceConnectionName || opts . domainName ) + "-" +
95+ opts . authType + "-" + opts . ipType ;
96+ }
97+
98+ async loadInstance ( opts : ConnectionOptions ) : Promise < void > {
9099 // in case an instance to that connection name has already
91100 // been setup there's no need to set it up again
92- if ( this . has ( instanceConnectionName ) ) {
93- const instance = this . get ( instanceConnectionName ) ;
94- if ( instance . authType && instance . authType !== authType ) {
95- throw new CloudSQLConnectorError ( {
96- message :
97- `getOptions called for instance ${ instanceConnectionName } with authType ${ authType } , ` +
98- `but was previously called with authType ${ instance . authType } . ` +
99- 'If you require both for your use case, please use a new connector object.' ,
100- code : 'EMISMATCHAUTHTYPE' ,
101- } ) ;
101+ if ( this . has ( this . cacheKey ( opts ) ) ) {
102+ const instance = this . get ( this . cacheKey ( opts ) ) ;
103+ let oldInfo = instance ?. instanceInfo
104+ if ( oldInfo && oldInfo . domainName ) {
105+ // configured with domain name
106+ let newInfo = await resolveInstanceName ( undefined , oldInfo . domainName ) ;
107+ if ( ! isSameInstance ( oldInfo , newInfo ) ) {
108+ // Domain name changed. Close and remove, then create a new map entry.
109+ instance ?. close ( ) ;
110+ this . delete ( this . cacheKey ( opts ) )
111+ } else {
112+ // Domain name resolves to the same instance, do nothing.
113+ return
114+ }
115+ } else {
116+ // Configured with instance name. Existing map entry is OK.
117+ return ;
102118 }
103- return ;
104119 }
120+
105121 const connectionInstance = await CloudSQLInstance . getCloudSQLInstance ( {
106- ipType,
107- authType,
108- instanceConnectionName,
109- sqlAdminFetcher : sqlAdminFetcher ,
122+ instanceConnectionName : opts . instanceConnectionName ,
123+ domainName : opts . domainName ,
124+ authType : opts . authType || AuthTypes . PASSWORD ,
125+ ipType : opts . ipType || IpAddressTypes . PUBLIC ,
126+ limitRateInterval : 30 * 1000 , // 30 sec
127+ sqlAdminFetcher : this . sqlAdminFetcher ,
110128 } ) ;
111- this . set ( instanceConnectionName , connectionInstance ) ;
129+ this . set ( this . cacheKey ( opts ) , connectionInstance ) ;
112130 }
113131
114- getInstance ( {
115- instanceConnectionName,
116- authType,
117- } : {
118- instanceConnectionName : string ;
119- authType : AuthTypes ;
120- } ) : CloudSQLInstance {
121- const connectionInstance = this . get ( instanceConnectionName ) ;
132+ getInstance ( opts : ConnectionOptions ) : CloudSQLInstance {
133+ const connectionInstance = this . get ( this . cacheKey ( opts ) ) ;
122134 if ( ! connectionInstance ) {
123135 throw new CloudSQLConnectorError ( {
124- message : `Cannot find info for instance: ${ instanceConnectionName } ` ,
136+ message : `Cannot find info for instance: ${ opts . instanceConnectionName } ` ,
125137 code : 'ENOINSTANCEINFO' ,
126138 } ) ;
127- } else if (
128- connectionInstance . authType &&
129- connectionInstance . authType !== authType
130- ) {
131- throw new CloudSQLConnectorError ( {
132- message :
133- `getOptions called for instance ${ instanceConnectionName } with authType ${ authType } , ` +
134- `but was previously called with authType ${ connectionInstance . authType } . ` +
135- 'If you require both for your use case, please use a new connector object.' ,
136- code : 'EMISMATCHAUTHTYPE' ,
137- } ) ;
138139 }
139140 return connectionInstance ;
140141 }
@@ -160,13 +161,13 @@ export class Connector {
160161 private readonly sockets : Set < Socket > ;
161162
162163 constructor ( opts : ConnectorOptions = { } ) {
163- this . instances = new CloudSQLInstanceMap ( ) ;
164164 this . sqlAdminFetcher = new SQLAdminFetcher ( {
165165 loginAuth : opts . auth ,
166166 sqlAdminAPIEndpoint : opts . sqlAdminAPIEndpoint ,
167167 universeDomain : opts . universeDomain ,
168168 userAgent : opts . userAgent ,
169169 } ) ;
170+ this . instances = new CloudSQLInstanceMap ( this . sqlAdminFetcher ) ;
170171 this . localProxies = new Set ( ) ;
171172 this . sockets = new Set ( ) ;
172173 }
@@ -183,22 +184,25 @@ export class Connector {
183184 // const pool = new Pool(opts)
184185 // const res = await pool.query('SELECT * FROM pg_catalog.pg_tables;')
185186 async getOptions ( {
186- authType = AuthTypes . PASSWORD ,
187- ipType = IpAddressTypes . PUBLIC ,
188- instanceConnectionName,
189- } : ConnectionOptions ) : Promise < DriverOptions > {
187+ authType = AuthTypes . PASSWORD ,
188+ ipType = IpAddressTypes . PUBLIC ,
189+ instanceConnectionName,
190+ domainName,
191+ } : ConnectionOptions ) : Promise < DriverOptions > {
190192 const { instances} = this ;
191193 await instances . loadInstance ( {
192194 ipType,
193195 authType,
194196 instanceConnectionName,
195- sqlAdminFetcher : this . sqlAdminFetcher ,
197+ domainName
196198 } ) ;
197199
198200 return {
199201 stream ( ) {
200202 const cloudSqlInstance = instances . getInstance ( {
203+ ipType,
201204 instanceConnectionName,
205+ domainName,
202206 authType,
203207 } ) ;
204208 const {
@@ -228,7 +232,7 @@ export class Connector {
228232 privateKey,
229233 serverCaCert,
230234 serverCaMode,
231- dnsName,
235+ dnsName : instanceInfo . domainName || dnsName , // use the configured domain name, or the instance dnsName.
232236 } ) ;
233237 tlsSocket . once ( 'error' , ( ) => {
234238 cloudSqlInstance . forceRefresh ( ) ;
@@ -248,10 +252,10 @@ export class Connector {
248252 }
249253
250254 async getTediousOptions ( {
251- authType,
252- ipType,
253- instanceConnectionName,
254- } : ConnectionOptions ) : Promise < TediousDriverOptions > {
255+ authType,
256+ ipType,
257+ instanceConnectionName,
258+ } : ConnectionOptions ) : Promise < TediousDriverOptions > {
255259 if ( authType === AuthTypes . IAM ) {
256260 throw new CloudSQLConnectorError ( {
257261 message : 'Tedious does not support Auto IAM DB Authentication' ,
@@ -288,11 +292,11 @@ export class Connector {
288292 // `postgresql://${user}@localhost /${database}?host=${process.cwd()}`;
289293 // const prisma = new PrismaClient({ datasourceUrl });
290294 async startLocalProxy ( {
291- authType,
292- ipType,
293- instanceConnectionName,
294- listenOptions,
295- } : SocketConnectionOptions ) : Promise < void > {
295+ authType,
296+ ipType,
297+ instanceConnectionName,
298+ listenOptions,
299+ } : SocketConnectionOptions ) : Promise < void > {
296300 const { stream} = await this . getOptions ( {
297301 authType,
298302 ipType,
0 commit comments