|
| 1 | +import copy |
1 | 2 | import os |
2 | 3 | from logging.config import fileConfig |
3 | 4 |
|
4 | 5 | from alembic import context |
5 | 6 | from dotenv import load_dotenv |
6 | 7 | from sqlalchemy import engine_from_config, pool, create_engine |
7 | 8 |
|
| 9 | +from services.util import get_bool_env |
8 | 10 |
|
9 | 11 | # this is the Alembic Config object, which provides |
10 | 12 | # access to the values within the .ini file in use. |
@@ -46,7 +48,10 @@ def build_database_url(): |
46 | 48 | user = os.environ.get("CLOUD_SQL_USER", "") |
47 | 49 | password = os.environ.get("CLOUD_SQL_PASSWORD", "") |
48 | 50 | database = os.environ.get("CLOUD_SQL_DATABASE", "") |
| 51 | + use_iam_auth = get_bool_env("CLOUD_SQL_IAM_AUTH", False) |
49 | 52 | # Host is provided by connector, so leave blank. |
| 53 | + if use_iam_auth: |
| 54 | + return f"postgresql+pg8000://{user}@/{database}" |
50 | 55 | return f"postgresql+pg8000://{user}:{password}@/{database}" |
51 | 56 |
|
52 | 57 | # Default/Postgres |
@@ -96,22 +101,46 @@ def run_migrations_online() -> None: |
96 | 101 | if db_driver == "cloudsql": |
97 | 102 | # Use the Cloud SQL Python Connector for direct Cloud SQL access. |
98 | 103 | from google.cloud.sql.connector import Connector |
| 104 | + from google.auth import default |
| 105 | + from google.auth.transport.requests import Request |
99 | 106 |
|
100 | 107 | instance_name = os.environ.get("CLOUD_SQL_INSTANCE_NAME") |
101 | 108 | user = os.environ.get("CLOUD_SQL_USER") |
102 | 109 | password = os.environ.get("CLOUD_SQL_PASSWORD") |
103 | 110 | database = os.environ.get("CLOUD_SQL_DATABASE") |
| 111 | + use_iam_auth = get_bool_env("CLOUD_SQL_IAM_AUTH", False) |
| 112 | + ip_type = os.environ.get("CLOUD_SQL_IP_TYPE", "public") |
104 | 113 |
|
105 | 114 | connector = Connector() |
106 | 115 |
|
| 116 | + def get_iam_login_token() -> str: |
| 117 | + scopes = ["https://www.googleapis.com/auth/sqlservice.login"] |
| 118 | + creds, _ = default() |
| 119 | + if hasattr(creds, "with_scopes"): |
| 120 | + creds = creds.with_scopes(scopes=scopes) |
| 121 | + else: |
| 122 | + creds = copy.copy(creds) |
| 123 | + creds._scopes = scopes # type: ignore[attr-defined] |
| 124 | + creds.refresh(Request()) |
| 125 | + if not getattr(creds, "token", None): |
| 126 | + raise RuntimeError("Unable to acquire IAM DB auth token.") |
| 127 | + return creds.token |
| 128 | + |
107 | 129 | def getconn(): |
| 130 | + connect_kwargs = { |
| 131 | + "user": user, |
| 132 | + "db": database, |
| 133 | + "ip_type": ip_type, |
| 134 | + "enable_iam_auth": use_iam_auth, |
| 135 | + } |
| 136 | + if use_iam_auth: |
| 137 | + connect_kwargs["password"] = get_iam_login_token() |
| 138 | + else: |
| 139 | + connect_kwargs["password"] = password |
108 | 140 | return connector.connect( |
109 | 141 | instance_name, |
110 | 142 | "pg8000", |
111 | | - user=user, |
112 | | - password=password, |
113 | | - db=database, |
114 | | - ip_type="public", |
| 143 | + **connect_kwargs, |
115 | 144 | ) |
116 | 145 |
|
117 | 146 | connectable = create_engine( |
|
0 commit comments