12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
14
15
- import { Server , Socket , createServer } from 'node:net' ;
15
+ import { Server , createServer } from 'node:net' ;
16
16
import tls from 'node:tls' ;
17
17
import { promisify } from 'node:util' ;
18
18
import { AuthClient , GoogleAuth } from 'google-auth-library' ;
@@ -22,6 +22,8 @@ import {IpAddressTypes} from './ip-addresses';
22
22
import { AuthTypes } from './auth-types' ;
23
23
import { SQLAdminFetcher } from './sqladmin-fetcher' ;
24
24
import { CloudSQLConnectorError } from './errors' ;
25
+ import { SocketWrapper , SocketWrapperOptions } from './socket-wrapper' ;
26
+ import stream from 'node:stream' ;
25
27
26
28
// These Socket types are subsets from nodejs definitely typed repo, ref:
27
29
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/ae0fe42ff0e6e820e8ae324acf4f8e944aa1b2b7/types/node/v18/net.d.ts#L437
@@ -42,19 +44,21 @@ export declare interface UnixSocketOptions {
42
44
export declare interface ConnectionOptions {
43
45
authType ?: AuthTypes ;
44
46
ipType ?: IpAddressTypes ;
45
- instanceConnectionName : string ;
47
+ instanceConnectionName ? : string ;
46
48
}
47
49
48
50
export declare interface SocketConnectionOptions extends ConnectionOptions {
49
51
listenOptions : UnixSocketOptions ;
50
52
}
51
53
52
54
interface StreamFunction {
53
- ( ) : tls . TLSSocket ;
55
+ //eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ ( ...opts : any | undefined ) : stream . Duplex ;
54
57
}
55
58
56
59
interface PromisedStreamFunction {
57
- ( ) : Promise < tls . TLSSocket > ;
60
+ //eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ ( ...opts : any | undefined ) : Promise < stream . Duplex > ;
58
62
}
59
63
60
64
// DriverOptions is the interface describing the object returned by
@@ -86,11 +90,17 @@ class CloudSQLInstanceMap extends Map {
86
90
authType : AuthTypes ;
87
91
instanceConnectionName : string ;
88
92
sqlAdminFetcher : SQLAdminFetcher ;
89
- } ) : Promise < void > {
93
+ } ) : Promise < CloudSQLInstance > {
90
94
// in case an instance to that connection name has already
91
95
// been setup there's no need to set it up again
92
96
if ( this . has ( instanceConnectionName ) ) {
93
97
const instance = this . get ( instanceConnectionName ) ;
98
+ if ( ! instance ) {
99
+ throw new CloudSQLConnectorError ( {
100
+ message : `Cannot find info for instance: ${ instanceConnectionName } ` ,
101
+ code : 'ENOINSTANCEINFO' ,
102
+ } ) ;
103
+ }
94
104
if ( instance . authType && instance . authType !== authType ) {
95
105
throw new CloudSQLConnectorError ( {
96
106
message :
@@ -100,42 +110,23 @@ class CloudSQLInstanceMap extends Map {
100
110
code : 'EMISMATCHAUTHTYPE' ,
101
111
} ) ;
102
112
}
103
- return ;
113
+ return instance ;
104
114
}
115
+
105
116
const connectionInstance = await CloudSQLInstance . getCloudSQLInstance ( {
106
117
ipType,
107
118
authType,
108
119
instanceConnectionName,
109
120
sqlAdminFetcher : sqlAdminFetcher ,
110
121
} ) ;
111
- this . set ( instanceConnectionName , connectionInstance ) ;
112
- }
113
-
114
- getInstance ( {
115
- instanceConnectionName,
116
- authType,
117
- } : {
118
- instanceConnectionName : string ;
119
- authType : AuthTypes ;
120
- } ) : CloudSQLInstance {
121
- const connectionInstance = this . get ( instanceConnectionName ) ;
122
122
if ( ! connectionInstance ) {
123
123
throw new CloudSQLConnectorError ( {
124
124
message : `Cannot find info for instance: ${ instanceConnectionName } ` ,
125
125
code : 'ENOINSTANCEINFO' ,
126
126
} ) ;
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
- } ) ;
138
127
}
128
+ this . set ( instanceConnectionName , connectionInstance ) ;
129
+
139
130
return connectionInstance ;
140
131
}
141
132
}
@@ -158,7 +149,7 @@ export class Connector {
158
149
private readonly instances : CloudSQLInstanceMap ;
159
150
private readonly sqlAdminFetcher : SQLAdminFetcher ;
160
151
private readonly localProxies : Set < Server > ;
161
- private readonly sockets : Set < Socket > ;
152
+ private readonly sockets : Set < stream . Duplex > ;
162
153
163
154
constructor ( opts : ConnectorOptions = { } ) {
164
155
this . instances = new CloudSQLInstanceMap ( ) ;
@@ -171,79 +162,132 @@ export class Connector {
171
162
this . localProxies = new Set ( ) ;
172
163
this . sockets = new Set ( ) ;
173
164
}
174
-
175
- // Connector.getOptions is a method that accepts a Cloud SQL instance
176
- // connection name along with the connection type and returns an object
177
- // that can be used to configure a driver to be used with Cloud SQL. e.g:
178
- //
179
- // const connector = new Connector()
180
- // const opts = await connector.getOptions({
181
- // ipType: 'PUBLIC',
182
- // instanceConnectionName: 'PROJECT:REGION:INSTANCE',
183
- // });
184
- // const pool = new Pool(opts)
185
- // const res = await pool.query('SELECT * FROM pg_catalog.pg_tables;')
186
- async getOptions ( {
165
+ async loadInstance ( {
187
166
authType = AuthTypes . PASSWORD ,
188
167
ipType = IpAddressTypes . PUBLIC ,
189
168
instanceConnectionName,
190
- } : ConnectionOptions ) : Promise < DriverOptions > {
191
- const { instances} = this ;
192
- await instances . loadInstance ( {
169
+ } : ConnectionOptions ) : Promise < CloudSQLInstance > {
170
+ if ( ! instanceConnectionName ) {
171
+ throw new CloudSQLConnectorError ( {
172
+ code : 'ENOTCONFIGURED' ,
173
+ message : 'Instance connection name missing.' ,
174
+ } ) ;
175
+ }
176
+
177
+ const inst = await this . instances . loadInstance ( {
193
178
ipType,
194
179
authType,
195
180
instanceConnectionName,
196
181
sqlAdminFetcher : this . sqlAdminFetcher ,
197
182
} ) ;
198
183
199
- return {
200
- stream ( ) {
201
- const cloudSqlInstance = instances . getInstance ( {
202
- instanceConnectionName,
203
- authType,
204
- } ) ;
205
- const {
206
- instanceInfo,
207
- ephemeralCert,
208
- host,
209
- port,
210
- privateKey,
211
- serverCaCert,
212
- serverCaMode,
213
- dnsName,
214
- } = cloudSqlInstance ;
184
+ return inst ;
185
+ }
215
186
216
- if (
217
- instanceInfo &&
218
- ephemeralCert &&
219
- host &&
220
- port &&
221
- privateKey &&
222
- serverCaCert
223
- ) {
224
- const tlsSocket = getSocket ( {
225
- instanceInfo,
226
- ephemeralCert,
227
- host,
228
- port,
229
- privateKey,
230
- serverCaCert,
231
- serverCaMode,
232
- dnsName : instanceInfo . domainName || dnsName , // use the configured domain name, or the instance dnsName.
233
- } ) ;
234
- tlsSocket . once ( 'error' , ( ) => {
235
- cloudSqlInstance . forceRefresh ( ) ;
236
- } ) ;
237
- tlsSocket . once ( 'secureConnect' , async ( ) => {
238
- cloudSqlInstance . setEstablishedConnection ( ) ;
239
- } ) ;
240
- return tlsSocket ;
187
+ async connect ( {
188
+ authType = AuthTypes . PASSWORD ,
189
+ ipType = IpAddressTypes . PUBLIC ,
190
+ instanceConnectionName,
191
+ } : ConnectionOptions ) : Promise < tls . TLSSocket > {
192
+ if ( ! instanceConnectionName ) {
193
+ throw new CloudSQLConnectorError ( {
194
+ code : 'ENOTCONFIGURED' ,
195
+ message : 'Instance connection name missing.' ,
196
+ } ) ;
197
+ }
198
+
199
+ const cloudSqlInstance = await this . loadInstance ( {
200
+ ipType,
201
+ authType,
202
+ instanceConnectionName,
203
+ } ) ;
204
+
205
+ const {
206
+ instanceInfo,
207
+ ephemeralCert,
208
+ host,
209
+ port,
210
+ privateKey,
211
+ serverCaCert,
212
+ serverCaMode,
213
+ dnsName,
214
+ } = cloudSqlInstance ;
215
+
216
+ if (
217
+ instanceInfo &&
218
+ ephemeralCert &&
219
+ host &&
220
+ port &&
221
+ privateKey &&
222
+ serverCaCert
223
+ ) {
224
+ const tlsSocket = getSocket ( {
225
+ instanceInfo,
226
+ ephemeralCert,
227
+ host,
228
+ port,
229
+ privateKey,
230
+ serverCaCert,
231
+ serverCaMode,
232
+ dnsName : instanceInfo . domainName || dnsName , // use the configured domain name, or the instance dnsName.
233
+ } ) ;
234
+ tlsSocket . once ( 'error' , ( ) => {
235
+ cloudSqlInstance . forceRefresh ( ) ;
236
+ } ) ;
237
+ tlsSocket . once ( 'secureConnect' , async ( ) => {
238
+ cloudSqlInstance . setEstablishedConnection ( ) ;
239
+ } ) ;
240
+ return tlsSocket ;
241
+ }
242
+ throw new CloudSQLConnectorError ( {
243
+ message : 'Invalid Cloud SQL Instance info' ,
244
+ code : 'EBADINSTANCEINFO' ,
245
+ } ) ;
246
+ }
247
+
248
+ getOptions ( {
249
+ authType = AuthTypes . PASSWORD ,
250
+ ipType = IpAddressTypes . PUBLIC ,
251
+ instanceConnectionName,
252
+ } : ConnectionOptions ) : DriverOptions {
253
+ // bring 'this' into a closure-scope variable.
254
+ //eslint-disable-next-line @typescript-eslint/no-this-alias
255
+ const connector = this ;
256
+ return {
257
+ stream ( opts ) {
258
+ let host ;
259
+ let startConnection = false ;
260
+ if ( opts ) {
261
+ if ( opts ?. config ?. host ) {
262
+ // Mysql driver passes the host in the options, and expects
263
+ // this to start the connection.
264
+ host = opts ?. config ?. host ;
265
+ startConnection = true ;
266
+ }
267
+ if ( opts ?. host ) {
268
+ // Sql Server (Tedious) driver passes host in the options
269
+ // this to start the connection.
270
+ host = opts ?. host ;
271
+ startConnection = true ;
272
+ }
273
+ } else {
274
+ // Postgres driver does not pass options.
275
+ // Postgres will call Socket.connect(port,host).
276
+ startConnection = false ;
241
277
}
242
278
243
- throw new CloudSQLConnectorError ( {
244
- message : 'Invalid Cloud SQL Instance info' ,
245
- code : 'EBADINSTANCEINFO' ,
246
- } ) ;
279
+ return new SocketWrapper (
280
+ new SocketWrapperOptions ( {
281
+ connector,
282
+ host,
283
+ startConnection,
284
+ connectionConfig : {
285
+ authType,
286
+ ipType,
287
+ instanceConnectionName,
288
+ } ,
289
+ } )
290
+ ) ;
247
291
} ,
248
292
} ;
249
293
}
@@ -265,8 +309,8 @@ export class Connector {
265
309
instanceConnectionName,
266
310
} ) ;
267
311
return {
268
- async connector ( ) {
269
- return driverOptions . stream ( ) ;
312
+ async connector ( opts ) {
313
+ return driverOptions . stream ( opts ) ;
270
314
} ,
271
315
// note: the connector handles a secured encrypted connection
272
316
// with that in mind, the driver encryption is disabled here
0 commit comments