cloud-sql-python-connector 1.6.0__tar.gz → 1.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/PKG-INFO +13 -14
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/README.md +11 -13
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/cloud_sql_python_connector.egg-info/PKG-INFO +13 -14
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/cloud_sql_python_connector.egg-info/SOURCES.txt +1 -0
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/cloud_sql_python_connector.egg-info/requires.txt +3 -3
- cloud-sql-python-connector-1.8.0/google/cloud/sql/connector/client.py +221 -0
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/connector.py +110 -52
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/exceptions.py +0 -8
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/instance.py +30 -91
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/rate_limiter.py +3 -1
- cloud-sql-python-connector-1.8.0/google/cloud/sql/connector/refresh_utils.py +107 -0
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/utils.py +1 -24
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/version.py +1 -1
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/setup.py +4 -3
- cloud-sql-python-connector-1.6.0/google/cloud/sql/connector/refresh_utils.py +0 -293
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/LICENSE +0 -0
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/cloud_sql_python_connector.egg-info/dependency_links.txt +0 -0
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/cloud_sql_python_connector.egg-info/not-zip-safe +0 -0
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/cloud_sql_python_connector.egg-info/top_level.txt +0 -0
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/__init__.py +0 -0
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/asyncpg.py +0 -0
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/pg8000.py +0 -0
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/py.typed +0 -0
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/pymysql.py +0 -0
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/pytds.py +0 -0
- {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cloud-sql-python-connector
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: The Cloud SQL Python Connector is a library that can be used alongside a database driver to allow users with sufficient permissions to connect to a Cloud SQL database without having to manually allowlist IPs or manage SSL certificates.
|
|
5
5
|
Home-page: https://github.com/GoogleCloudPlatform/cloud-sql-python-connector
|
|
6
6
|
Author: Google LLC
|
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.9
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
19
|
Requires-Python: >=3.8
|
|
19
20
|
Description-Content-Type: text/markdown
|
|
20
21
|
Provides-Extra: pymysql
|
|
@@ -136,7 +137,7 @@ format "project:region:instance".
|
|
|
136
137
|
### How to use this Connector
|
|
137
138
|
|
|
138
139
|
To connect to Cloud SQL using the connector, inititalize a `Connector`
|
|
139
|
-
object and call
|
|
140
|
+
object and call its `connect` method with the proper input parameters.
|
|
140
141
|
|
|
141
142
|
The `Connector` itself creates connection objects by calling its `connect` method but does not manage database connection pooling. For this reason, it is recommended to use the connector alongside a library that can create connection pools, such as [SQLAlchemy](https://www.sqlalchemy.org/). This will allow for connections to remain open and be reused, reducing connection overhead and the number of connections needed.
|
|
142
143
|
|
|
@@ -192,7 +193,7 @@ with pool.connect() as db_conn:
|
|
|
192
193
|
print(row)
|
|
193
194
|
```
|
|
194
195
|
|
|
195
|
-
To close the `Connector` object's background resources, call
|
|
196
|
+
To close the `Connector` object's background resources, call its `close()` method as follows:
|
|
196
197
|
|
|
197
198
|
```python
|
|
198
199
|
connector.close()
|
|
@@ -213,7 +214,7 @@ from google.cloud.sql.connector import Connector, IPTypes
|
|
|
213
214
|
|
|
214
215
|
# Note: all parameters below are optional
|
|
215
216
|
connector = Connector(
|
|
216
|
-
ip_type=
|
|
217
|
+
ip_type="public", # can also be "private" or "psc"
|
|
217
218
|
enable_iam_auth=False,
|
|
218
219
|
timeout=30,
|
|
219
220
|
credentials=custom_creds # google.auth.credentials.Credentials
|
|
@@ -286,18 +287,16 @@ using both public and private IP addresses, as well as
|
|
|
286
287
|
with, set the `ip_type` keyword argument when initializing a `Connector()` or when
|
|
287
288
|
calling `connector.connect()`.
|
|
288
289
|
|
|
289
|
-
Possible values for `ip_type` are `
|
|
290
|
-
`
|
|
290
|
+
Possible values for `ip_type` are `"public"` (default value),
|
|
291
|
+
`"private"`, and `"psc"`.
|
|
291
292
|
|
|
292
293
|
Example:
|
|
293
294
|
|
|
294
295
|
```python
|
|
295
|
-
from google.cloud.sql.connector import IPTypes
|
|
296
|
-
|
|
297
296
|
conn = connector.connect(
|
|
298
297
|
"project:region:instance",
|
|
299
298
|
"pymysql",
|
|
300
|
-
ip_type=
|
|
299
|
+
ip_type="private" # use private IP
|
|
301
300
|
... insert other kwargs ...
|
|
302
301
|
)
|
|
303
302
|
```
|
|
@@ -358,7 +357,7 @@ conn = connector.connect(
|
|
|
358
357
|
db="my-db-name",
|
|
359
358
|
active_directory_auth=True,
|
|
360
359
|
server_name="private.[instance].[location].[project].cloudsql.[domain]",
|
|
361
|
-
ip_type=
|
|
360
|
+
ip_type="private"
|
|
362
361
|
)
|
|
363
362
|
```
|
|
364
363
|
|
|
@@ -383,7 +382,7 @@ your web application through the following:
|
|
|
383
382
|
```python
|
|
384
383
|
from flask import Flask
|
|
385
384
|
from flask_sqlalchemy import SQLAlchemy
|
|
386
|
-
from google.cloud.sql.connector import Connector
|
|
385
|
+
from google.cloud.sql.connector import Connector
|
|
387
386
|
|
|
388
387
|
|
|
389
388
|
# initialize Python Connector object
|
|
@@ -397,7 +396,7 @@ def getconn():
|
|
|
397
396
|
user="my-user",
|
|
398
397
|
password="my-password",
|
|
399
398
|
db="my-database",
|
|
400
|
-
ip_type=
|
|
399
|
+
ip_type="public" # "private" for private IP
|
|
401
400
|
)
|
|
402
401
|
return conn
|
|
403
402
|
|
|
@@ -432,7 +431,7 @@ from sqlalchemy import create_engine
|
|
|
432
431
|
from sqlalchemy.engine import Engine
|
|
433
432
|
from sqlalchemy.ext.declarative import declarative_base
|
|
434
433
|
from sqlalchemy.orm import sessionmaker
|
|
435
|
-
from google.cloud.sql.connector import Connector
|
|
434
|
+
from google.cloud.sql.connector import Connector
|
|
436
435
|
|
|
437
436
|
# helper function to return SQLAlchemy connection pool
|
|
438
437
|
def init_connection_pool(connector: Connector) -> Engine:
|
|
@@ -444,7 +443,7 @@ def init_connection_pool(connector: Connector) -> Engine:
|
|
|
444
443
|
user="my-user",
|
|
445
444
|
password="my-password",
|
|
446
445
|
db="my-database",
|
|
447
|
-
ip_type=
|
|
446
|
+
ip_type="public" # "private" for private IP
|
|
448
447
|
)
|
|
449
448
|
return conn
|
|
450
449
|
|
|
@@ -111,7 +111,7 @@ format "project:region:instance".
|
|
|
111
111
|
### How to use this Connector
|
|
112
112
|
|
|
113
113
|
To connect to Cloud SQL using the connector, inititalize a `Connector`
|
|
114
|
-
object and call
|
|
114
|
+
object and call its `connect` method with the proper input parameters.
|
|
115
115
|
|
|
116
116
|
The `Connector` itself creates connection objects by calling its `connect` method but does not manage database connection pooling. For this reason, it is recommended to use the connector alongside a library that can create connection pools, such as [SQLAlchemy](https://www.sqlalchemy.org/). This will allow for connections to remain open and be reused, reducing connection overhead and the number of connections needed.
|
|
117
117
|
|
|
@@ -167,7 +167,7 @@ with pool.connect() as db_conn:
|
|
|
167
167
|
print(row)
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
-
To close the `Connector` object's background resources, call
|
|
170
|
+
To close the `Connector` object's background resources, call its `close()` method as follows:
|
|
171
171
|
|
|
172
172
|
```python
|
|
173
173
|
connector.close()
|
|
@@ -188,7 +188,7 @@ from google.cloud.sql.connector import Connector, IPTypes
|
|
|
188
188
|
|
|
189
189
|
# Note: all parameters below are optional
|
|
190
190
|
connector = Connector(
|
|
191
|
-
ip_type=
|
|
191
|
+
ip_type="public", # can also be "private" or "psc"
|
|
192
192
|
enable_iam_auth=False,
|
|
193
193
|
timeout=30,
|
|
194
194
|
credentials=custom_creds # google.auth.credentials.Credentials
|
|
@@ -261,18 +261,16 @@ using both public and private IP addresses, as well as
|
|
|
261
261
|
with, set the `ip_type` keyword argument when initializing a `Connector()` or when
|
|
262
262
|
calling `connector.connect()`.
|
|
263
263
|
|
|
264
|
-
Possible values for `ip_type` are `
|
|
265
|
-
`
|
|
264
|
+
Possible values for `ip_type` are `"public"` (default value),
|
|
265
|
+
`"private"`, and `"psc"`.
|
|
266
266
|
|
|
267
267
|
Example:
|
|
268
268
|
|
|
269
269
|
```python
|
|
270
|
-
from google.cloud.sql.connector import IPTypes
|
|
271
|
-
|
|
272
270
|
conn = connector.connect(
|
|
273
271
|
"project:region:instance",
|
|
274
272
|
"pymysql",
|
|
275
|
-
ip_type=
|
|
273
|
+
ip_type="private" # use private IP
|
|
276
274
|
... insert other kwargs ...
|
|
277
275
|
)
|
|
278
276
|
```
|
|
@@ -333,7 +331,7 @@ conn = connector.connect(
|
|
|
333
331
|
db="my-db-name",
|
|
334
332
|
active_directory_auth=True,
|
|
335
333
|
server_name="private.[instance].[location].[project].cloudsql.[domain]",
|
|
336
|
-
ip_type=
|
|
334
|
+
ip_type="private"
|
|
337
335
|
)
|
|
338
336
|
```
|
|
339
337
|
|
|
@@ -358,7 +356,7 @@ your web application through the following:
|
|
|
358
356
|
```python
|
|
359
357
|
from flask import Flask
|
|
360
358
|
from flask_sqlalchemy import SQLAlchemy
|
|
361
|
-
from google.cloud.sql.connector import Connector
|
|
359
|
+
from google.cloud.sql.connector import Connector
|
|
362
360
|
|
|
363
361
|
|
|
364
362
|
# initialize Python Connector object
|
|
@@ -372,7 +370,7 @@ def getconn():
|
|
|
372
370
|
user="my-user",
|
|
373
371
|
password="my-password",
|
|
374
372
|
db="my-database",
|
|
375
|
-
ip_type=
|
|
373
|
+
ip_type="public" # "private" for private IP
|
|
376
374
|
)
|
|
377
375
|
return conn
|
|
378
376
|
|
|
@@ -407,7 +405,7 @@ from sqlalchemy import create_engine
|
|
|
407
405
|
from sqlalchemy.engine import Engine
|
|
408
406
|
from sqlalchemy.ext.declarative import declarative_base
|
|
409
407
|
from sqlalchemy.orm import sessionmaker
|
|
410
|
-
from google.cloud.sql.connector import Connector
|
|
408
|
+
from google.cloud.sql.connector import Connector
|
|
411
409
|
|
|
412
410
|
# helper function to return SQLAlchemy connection pool
|
|
413
411
|
def init_connection_pool(connector: Connector) -> Engine:
|
|
@@ -419,7 +417,7 @@ def init_connection_pool(connector: Connector) -> Engine:
|
|
|
419
417
|
user="my-user",
|
|
420
418
|
password="my-password",
|
|
421
419
|
db="my-database",
|
|
422
|
-
ip_type=
|
|
420
|
+
ip_type="public" # "private" for private IP
|
|
423
421
|
)
|
|
424
422
|
return conn
|
|
425
423
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cloud-sql-python-connector
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: The Cloud SQL Python Connector is a library that can be used alongside a database driver to allow users with sufficient permissions to connect to a Cloud SQL database without having to manually allowlist IPs or manage SSL certificates.
|
|
5
5
|
Home-page: https://github.com/GoogleCloudPlatform/cloud-sql-python-connector
|
|
6
6
|
Author: Google LLC
|
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.9
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
19
|
Requires-Python: >=3.8
|
|
19
20
|
Description-Content-Type: text/markdown
|
|
20
21
|
Provides-Extra: pymysql
|
|
@@ -136,7 +137,7 @@ format "project:region:instance".
|
|
|
136
137
|
### How to use this Connector
|
|
137
138
|
|
|
138
139
|
To connect to Cloud SQL using the connector, inititalize a `Connector`
|
|
139
|
-
object and call
|
|
140
|
+
object and call its `connect` method with the proper input parameters.
|
|
140
141
|
|
|
141
142
|
The `Connector` itself creates connection objects by calling its `connect` method but does not manage database connection pooling. For this reason, it is recommended to use the connector alongside a library that can create connection pools, such as [SQLAlchemy](https://www.sqlalchemy.org/). This will allow for connections to remain open and be reused, reducing connection overhead and the number of connections needed.
|
|
142
143
|
|
|
@@ -192,7 +193,7 @@ with pool.connect() as db_conn:
|
|
|
192
193
|
print(row)
|
|
193
194
|
```
|
|
194
195
|
|
|
195
|
-
To close the `Connector` object's background resources, call
|
|
196
|
+
To close the `Connector` object's background resources, call its `close()` method as follows:
|
|
196
197
|
|
|
197
198
|
```python
|
|
198
199
|
connector.close()
|
|
@@ -213,7 +214,7 @@ from google.cloud.sql.connector import Connector, IPTypes
|
|
|
213
214
|
|
|
214
215
|
# Note: all parameters below are optional
|
|
215
216
|
connector = Connector(
|
|
216
|
-
ip_type=
|
|
217
|
+
ip_type="public", # can also be "private" or "psc"
|
|
217
218
|
enable_iam_auth=False,
|
|
218
219
|
timeout=30,
|
|
219
220
|
credentials=custom_creds # google.auth.credentials.Credentials
|
|
@@ -286,18 +287,16 @@ using both public and private IP addresses, as well as
|
|
|
286
287
|
with, set the `ip_type` keyword argument when initializing a `Connector()` or when
|
|
287
288
|
calling `connector.connect()`.
|
|
288
289
|
|
|
289
|
-
Possible values for `ip_type` are `
|
|
290
|
-
`
|
|
290
|
+
Possible values for `ip_type` are `"public"` (default value),
|
|
291
|
+
`"private"`, and `"psc"`.
|
|
291
292
|
|
|
292
293
|
Example:
|
|
293
294
|
|
|
294
295
|
```python
|
|
295
|
-
from google.cloud.sql.connector import IPTypes
|
|
296
|
-
|
|
297
296
|
conn = connector.connect(
|
|
298
297
|
"project:region:instance",
|
|
299
298
|
"pymysql",
|
|
300
|
-
ip_type=
|
|
299
|
+
ip_type="private" # use private IP
|
|
301
300
|
... insert other kwargs ...
|
|
302
301
|
)
|
|
303
302
|
```
|
|
@@ -358,7 +357,7 @@ conn = connector.connect(
|
|
|
358
357
|
db="my-db-name",
|
|
359
358
|
active_directory_auth=True,
|
|
360
359
|
server_name="private.[instance].[location].[project].cloudsql.[domain]",
|
|
361
|
-
ip_type=
|
|
360
|
+
ip_type="private"
|
|
362
361
|
)
|
|
363
362
|
```
|
|
364
363
|
|
|
@@ -383,7 +382,7 @@ your web application through the following:
|
|
|
383
382
|
```python
|
|
384
383
|
from flask import Flask
|
|
385
384
|
from flask_sqlalchemy import SQLAlchemy
|
|
386
|
-
from google.cloud.sql.connector import Connector
|
|
385
|
+
from google.cloud.sql.connector import Connector
|
|
387
386
|
|
|
388
387
|
|
|
389
388
|
# initialize Python Connector object
|
|
@@ -397,7 +396,7 @@ def getconn():
|
|
|
397
396
|
user="my-user",
|
|
398
397
|
password="my-password",
|
|
399
398
|
db="my-database",
|
|
400
|
-
ip_type=
|
|
399
|
+
ip_type="public" # "private" for private IP
|
|
401
400
|
)
|
|
402
401
|
return conn
|
|
403
402
|
|
|
@@ -432,7 +431,7 @@ from sqlalchemy import create_engine
|
|
|
432
431
|
from sqlalchemy.engine import Engine
|
|
433
432
|
from sqlalchemy.ext.declarative import declarative_base
|
|
434
433
|
from sqlalchemy.orm import sessionmaker
|
|
435
|
-
from google.cloud.sql.connector import Connector
|
|
434
|
+
from google.cloud.sql.connector import Connector
|
|
436
435
|
|
|
437
436
|
# helper function to return SQLAlchemy connection pool
|
|
438
437
|
def init_connection_pool(connector: Connector) -> Engine:
|
|
@@ -444,7 +443,7 @@ def init_connection_pool(connector: Connector) -> Engine:
|
|
|
444
443
|
user="my-user",
|
|
445
444
|
password="my-password",
|
|
446
445
|
db="my-database",
|
|
447
|
-
ip_type=
|
|
446
|
+
ip_type="public" # "private" for private IP
|
|
448
447
|
)
|
|
449
448
|
return conn
|
|
450
449
|
|
|
@@ -10,6 +10,7 @@ cloud_sql_python_connector.egg-info/requires.txt
|
|
|
10
10
|
cloud_sql_python_connector.egg-info/top_level.txt
|
|
11
11
|
google/cloud/sql/connector/__init__.py
|
|
12
12
|
google/cloud/sql/connector/asyncpg.py
|
|
13
|
+
google/cloud/sql/connector/client.py
|
|
13
14
|
google/cloud/sql/connector/connector.py
|
|
14
15
|
google/cloud/sql/connector/exceptions.py
|
|
15
16
|
google/cloud/sql/connector/instance.py
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
aiohttp
|
|
2
|
-
cryptography>=
|
|
2
|
+
cryptography>=42.0.0
|
|
3
3
|
Requests
|
|
4
4
|
google-auth
|
|
5
5
|
|
|
@@ -7,10 +7,10 @@ google-auth
|
|
|
7
7
|
asyncpg>=0.29.0
|
|
8
8
|
|
|
9
9
|
[pg8000]
|
|
10
|
-
pg8000>=1.30.
|
|
10
|
+
pg8000>=1.30.5
|
|
11
11
|
|
|
12
12
|
[pymysql]
|
|
13
13
|
PyMySQL>=1.1.0
|
|
14
14
|
|
|
15
15
|
[pytds]
|
|
16
|
-
python-tds>=1.
|
|
16
|
+
python-tds>=1.15.0
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Copyright 2024 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import datetime
|
|
18
|
+
import logging
|
|
19
|
+
from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
import aiohttp
|
|
22
|
+
from cryptography.hazmat.backends import default_backend
|
|
23
|
+
from cryptography.x509 import load_pem_x509_certificate
|
|
24
|
+
import google.auth.transport.requests
|
|
25
|
+
|
|
26
|
+
from google.cloud.sql.connector.refresh_utils import _downscope_credentials
|
|
27
|
+
from google.cloud.sql.connector.version import __version__ as version
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from google.auth.credentials import Credentials
|
|
31
|
+
|
|
32
|
+
USER_AGENT: str = f"cloud-sql-python-connector/{version}"
|
|
33
|
+
API_VERSION: str = "v1beta4"
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(name=__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _format_user_agent(driver: Optional[str], custom: Optional[str]) -> str:
|
|
39
|
+
agent = f"{USER_AGENT}+{driver}" if driver else USER_AGENT
|
|
40
|
+
if custom:
|
|
41
|
+
agent = f"{agent} {custom}"
|
|
42
|
+
return agent
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CloudSQLClient:
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
sqladmin_api_endpoint: str,
|
|
49
|
+
quota_project: Optional[str],
|
|
50
|
+
credentials: Credentials,
|
|
51
|
+
client: Optional[aiohttp.ClientSession] = None,
|
|
52
|
+
driver: Optional[str] = None,
|
|
53
|
+
user_agent: Optional[str] = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""
|
|
56
|
+
Establish the client to be used for Cloud SQL Admin API requests.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
sqladmin_api_endpoint (str): Base URL to use when calling
|
|
60
|
+
the Cloud SQL Admin API endpoints.
|
|
61
|
+
quota_project (str): The Project ID for an existing Google Cloud
|
|
62
|
+
project. The project specified is used for quota and
|
|
63
|
+
billing purposes.
|
|
64
|
+
credentials (google.auth.credentials.Credentials):
|
|
65
|
+
A credentials object created from the google-auth Python library.
|
|
66
|
+
Must have the Cloud SQL Admin scopes. For more info check out
|
|
67
|
+
https://google-auth.readthedocs.io/en/latest/.
|
|
68
|
+
client (aiohttp.ClientSession): Async client used to make requests to
|
|
69
|
+
Cloud SQL Admin APIs.
|
|
70
|
+
Optional, defaults to None and creates new client.
|
|
71
|
+
driver (str): Database driver to be used by the client.
|
|
72
|
+
"""
|
|
73
|
+
user_agent = _format_user_agent(driver, user_agent)
|
|
74
|
+
headers = {
|
|
75
|
+
"x-goog-api-client": user_agent,
|
|
76
|
+
"User-Agent": user_agent,
|
|
77
|
+
"Content-Type": "application/json",
|
|
78
|
+
}
|
|
79
|
+
if quota_project:
|
|
80
|
+
headers["x-goog-user-project"] = quota_project
|
|
81
|
+
|
|
82
|
+
self._client = client if client else aiohttp.ClientSession(headers=headers)
|
|
83
|
+
self._credentials = credentials
|
|
84
|
+
self._sqladmin_api_endpoint = sqladmin_api_endpoint
|
|
85
|
+
self._user_agent = user_agent
|
|
86
|
+
|
|
87
|
+
async def _get_metadata(
|
|
88
|
+
self,
|
|
89
|
+
project: str,
|
|
90
|
+
region: str,
|
|
91
|
+
instance: str,
|
|
92
|
+
) -> Dict[str, Any]:
|
|
93
|
+
"""Requests metadata from the Cloud SQL Instance
|
|
94
|
+
and returns a dictionary containing the IP addresses and certificate
|
|
95
|
+
authority of the Cloud SQL Instance.
|
|
96
|
+
|
|
97
|
+
:type project: str
|
|
98
|
+
:param project:
|
|
99
|
+
A string representing the name of the project.
|
|
100
|
+
|
|
101
|
+
:type region: str
|
|
102
|
+
:param region : A string representing the name of the region.
|
|
103
|
+
|
|
104
|
+
:type instance: str
|
|
105
|
+
:param instance: A string representing the name of the instance.
|
|
106
|
+
|
|
107
|
+
:rtype: Dict[str: Union[Dict, str]]
|
|
108
|
+
:returns: Returns a dictionary containing a dictionary of all IP
|
|
109
|
+
addresses and their type and a string representing the
|
|
110
|
+
certificate authority.
|
|
111
|
+
"""
|
|
112
|
+
if not self._credentials.valid:
|
|
113
|
+
request = google.auth.transport.requests.Request()
|
|
114
|
+
self._credentials.refresh(request)
|
|
115
|
+
|
|
116
|
+
headers = {
|
|
117
|
+
"Authorization": f"Bearer {self._credentials.token}",
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
url = f"{self._sqladmin_api_endpoint}/sql/{API_VERSION}/projects/{project}/instances/{instance}/connectSettings"
|
|
121
|
+
|
|
122
|
+
logger.debug(f"['{instance}']: Requesting metadata")
|
|
123
|
+
|
|
124
|
+
resp = await self._client.get(url, headers=headers, raise_for_status=True)
|
|
125
|
+
ret_dict = await resp.json()
|
|
126
|
+
|
|
127
|
+
if ret_dict["region"] != region:
|
|
128
|
+
raise ValueError(
|
|
129
|
+
f'[{project}:{region}:{instance}]: Provided region was mismatched - got region {region}, expected {ret_dict["region"]}.'
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
ip_addresses = (
|
|
133
|
+
{ip["type"]: ip["ipAddress"] for ip in ret_dict["ipAddresses"]}
|
|
134
|
+
if "ipAddresses" in ret_dict
|
|
135
|
+
else {}
|
|
136
|
+
)
|
|
137
|
+
if "dnsName" in ret_dict:
|
|
138
|
+
ip_addresses["PSC"] = ret_dict["dnsName"]
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
"ip_addresses": ip_addresses,
|
|
142
|
+
"server_ca_cert": ret_dict["serverCaCert"]["cert"],
|
|
143
|
+
"database_version": ret_dict["databaseVersion"],
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async def _get_ephemeral(
|
|
147
|
+
self,
|
|
148
|
+
project: str,
|
|
149
|
+
instance: str,
|
|
150
|
+
pub_key: str,
|
|
151
|
+
enable_iam_auth: bool = False,
|
|
152
|
+
) -> Tuple[str, datetime.datetime]:
|
|
153
|
+
"""Asynchronously requests an ephemeral certificate from the Cloud SQL Instance.
|
|
154
|
+
|
|
155
|
+
:type project: str
|
|
156
|
+
:param project : A string representing the name of the project.
|
|
157
|
+
|
|
158
|
+
:type instance: str
|
|
159
|
+
:param instance: A string representing the name of the instance.
|
|
160
|
+
|
|
161
|
+
:type pub_key:
|
|
162
|
+
:param str: A string representing PEM-encoded RSA public key.
|
|
163
|
+
|
|
164
|
+
:type enable_iam_auth: bool
|
|
165
|
+
:param enable_iam_auth
|
|
166
|
+
Enables automatic IAM database authentication for Postgres or MySQL
|
|
167
|
+
instances.
|
|
168
|
+
|
|
169
|
+
:rtype: str
|
|
170
|
+
:returns: An ephemeral certificate from the Cloud SQL instance that allows
|
|
171
|
+
authorized connections to the instance.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
logger.debug(f"['{instance}']: Requesting ephemeral certificate")
|
|
175
|
+
|
|
176
|
+
if not self._credentials.valid:
|
|
177
|
+
request = google.auth.transport.requests.Request()
|
|
178
|
+
self._credentials.refresh(request)
|
|
179
|
+
|
|
180
|
+
headers = {
|
|
181
|
+
"Authorization": f"Bearer {self._credentials.token}",
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
url = f"{self._sqladmin_api_endpoint}/sql/{API_VERSION}/projects/{project}/instances/{instance}:generateEphemeralCert"
|
|
185
|
+
|
|
186
|
+
data = {"public_key": pub_key}
|
|
187
|
+
|
|
188
|
+
if enable_iam_auth:
|
|
189
|
+
# down-scope credentials with only IAM login scope (refreshes them too)
|
|
190
|
+
login_creds = _downscope_credentials(self._credentials)
|
|
191
|
+
data["access_token"] = login_creds.token
|
|
192
|
+
|
|
193
|
+
resp = await self._client.post(
|
|
194
|
+
url, headers=headers, json=data, raise_for_status=True
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
ret_dict = await resp.json()
|
|
198
|
+
|
|
199
|
+
ephemeral_cert: str = ret_dict["ephemeralCert"]["cert"]
|
|
200
|
+
|
|
201
|
+
# decode cert to read expiration
|
|
202
|
+
x509 = load_pem_x509_certificate(
|
|
203
|
+
ephemeral_cert.encode("UTF-8"), default_backend()
|
|
204
|
+
)
|
|
205
|
+
expiration = x509.not_valid_after_utc
|
|
206
|
+
# for IAM authentication OAuth2 token is embedded in cert so it
|
|
207
|
+
# must still be valid for successful connection
|
|
208
|
+
if enable_iam_auth:
|
|
209
|
+
token_expiration: datetime.datetime = login_creds.expiry
|
|
210
|
+
# google.auth library strips timezone info for backwards compatibality
|
|
211
|
+
# reasons with Python 2. Add it back to allow timezone aware datetimes.
|
|
212
|
+
# Ref: https://github.com/googleapis/google-auth-library-python/blob/49a5ff7411a2ae4d32a7d11700f9f961c55406a9/google/auth/_helpers.py#L93-L99
|
|
213
|
+
token_expiration = token_expiration.replace(tzinfo=datetime.timezone.utc)
|
|
214
|
+
|
|
215
|
+
if expiration > token_expiration:
|
|
216
|
+
expiration = token_expiration
|
|
217
|
+
return ephemeral_cert, expiration
|
|
218
|
+
|
|
219
|
+
async def close(self) -> None:
|
|
220
|
+
"""Close CloudSQLClient gracefully."""
|
|
221
|
+
await self._client.close()
|