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 }
@@ -161,13 +162,13 @@ export class Connector {
161162 private readonly sockets : Set < Socket > ;
162163
163164 constructor ( opts : ConnectorOptions = { } ) {
164- this . instances = new CloudSQLInstanceMap ( ) ;
165165 this . sqlAdminFetcher = new SQLAdminFetcher ( {
166166 loginAuth : opts . auth ,
167167 sqlAdminAPIEndpoint : opts . sqlAdminAPIEndpoint ,
168168 universeDomain : opts . universeDomain ,
169169 userAgent : opts . userAgent ,
170170 } ) ;
171+ this . instances = new CloudSQLInstanceMap ( this . sqlAdminFetcher ) ;
171172 this . localProxies = new Set ( ) ;
172173 this . sockets = new Set ( ) ;
173174 }
@@ -184,22 +185,25 @@ export class Connector {
184185 // const pool = new Pool(opts)
185186 // const res = await pool.query('SELECT * FROM pg_catalog.pg_tables;')
186187 async getOptions ( {
187- authType = AuthTypes . PASSWORD ,
188- ipType = IpAddressTypes . PUBLIC ,
189- instanceConnectionName,
190- } : ConnectionOptions ) : Promise < DriverOptions > {
188+ authType = AuthTypes . PASSWORD ,
189+ ipType = IpAddressTypes . PUBLIC ,
190+ instanceConnectionName,
191+ domainName,
192+ } : ConnectionOptions ) : Promise < DriverOptions > {
191193 const { instances} = this ;
192194 await instances . loadInstance ( {
193195 ipType,
194196 authType,
195197 instanceConnectionName,
196- sqlAdminFetcher : this . sqlAdminFetcher ,
198+ domainName
197199 } ) ;
198200
199201 return {
200202 stream ( ) {
201203 const cloudSqlInstance = instances . getInstance ( {
204+ ipType,
202205 instanceConnectionName,
206+ domainName,
203207 authType,
204208 } ) ;
205209 const {
@@ -249,10 +253,10 @@ export class Connector {
249253 }
250254
251255 async getTediousOptions ( {
252- authType,
253- ipType,
254- instanceConnectionName,
255- } : ConnectionOptions ) : Promise < TediousDriverOptions > {
256+ authType,
257+ ipType,
258+ instanceConnectionName,
259+ } : ConnectionOptions ) : Promise < TediousDriverOptions > {
256260 if ( authType === AuthTypes . IAM ) {
257261 throw new CloudSQLConnectorError ( {
258262 message : 'Tedious does not support Auto IAM DB Authentication' ,
@@ -289,11 +293,11 @@ export class Connector {
289293 // `postgresql://${user}@localhost /${database}?host=${process.cwd()}`;
290294 // const prisma = new PrismaClient({ datasourceUrl });
291295 async startLocalProxy ( {
292- authType,
293- ipType,
294- instanceConnectionName,
295- listenOptions,
296- } : SocketConnectionOptions ) : Promise < void > {
296+ authType,
297+ ipType,
298+ instanceConnectionName,
299+ listenOptions,
300+ } : SocketConnectionOptions ) : Promise < void > {
297301 const { stream} = await this . getOptions ( {
298302 authType,
299303 ipType,
@@ -344,4 +348,8 @@ export class Connector {
344348 socket . destroy ( ) ;
345349 }
346350 }
351+
352+ isClosed ( ) :boolean {
353+ return this . closed ;
354+ }
347355}
0 commit comments