|
3 | 3 | Supports both traditional ODBC connections and passwordless Azure AD authentication |
4 | 4 | """ |
5 | 5 |
|
6 | | -from multiprocessing import connection |
| 6 | +import logging |
7 | 7 | import os |
8 | 8 | import struct |
9 | | -import pyodbc |
10 | | -import logging |
11 | | -from typing import Optional, List, Dict, Any |
12 | 9 | from contextlib import contextmanager |
| 10 | +from typing import Any, Dict, List, Optional |
| 11 | + |
| 12 | +import pyodbc |
13 | 13 | from azure.identity import DefaultAzureCredential |
| 14 | +from azure.keyvault.secrets import SecretClient |
14 | 15 |
|
15 | 16 | # Configure logging |
16 | 17 | logger = logging.getLogger(__name__) |
@@ -61,44 +62,36 @@ def __init__( |
61 | 62 | @classmethod |
62 | 63 | def from_env(cls) -> 'SqlHelper': |
63 | 64 | """ |
64 | | - Create a SqlHelper instance from environment variables. |
65 | | -
|
66 | | - You can either pass the following set of variables, if you plan to use DefaultAzureCredential: |
67 | | - - AZURE_CLIENT_ID |
68 | | - - AZURE_CLIENT_SECRET |
69 | | - - AZURE_TENANT_ID |
70 | | -
|
71 | | - Instead, if you plan to use the connection string directly, you can set: |
72 | | - - SQL_SERVER |
73 | | - - SQL_DATABASE |
74 | | - - SQL_USERNAME |
75 | | - - SQL_PASSWORD |
| 65 | + Create a SqlHelper instance using KEY_VAULT_NAME and SECRET_NAME environment variables. |
| 66 | + The secret must contain a SQL connection string. Uses DefaultAzureCredential for authentication. |
76 | 67 | """ |
77 | | - connection_string = os.environ.get("SQL_CONNECTION_STRING") |
78 | | - if connection_string: |
79 | | - return cls.from_connection_string(connection_string) |
| 68 | + key_vault_name = os.environ.get("KEY_VAULT_NAME") |
| 69 | + secret_name = os.environ.get("SECRET_NAME") |
80 | 70 |
|
81 | | - client_id = os.environ.get("AZURE_CLIENT_ID") |
82 | | - client_secret = os.environ.get("AZURE_CLIENT_SECRET") |
83 | | - tenant_id = os.environ.get("AZURE_TENANT_ID") |
84 | | - server = os.environ.get("SQL_SERVER") |
85 | | - database = os.environ.get("SQL_DATABASE") |
86 | | - username = os.environ.get("SQL_USERNAME") |
87 | | - password = os.environ.get("SQL_PASSWORD") |
88 | | - |
89 | | - if not any([client_id, client_secret, tenant_id, server, database, username, password]): |
90 | | - raise ValueError("You properly need to define environment variables.") |
91 | | - |
92 | | - logger.info("Environment variables loaded successfully") |
93 | | - |
94 | | - return cls( |
95 | | - server=server, |
96 | | - database=database, |
97 | | - username=username, |
98 | | - password=password, |
99 | | - use_azure_credential=all([client_id, client_secret, tenant_id]) |
100 | | - ) |
101 | | - |
| 71 | + if not key_vault_name or not secret_name: |
| 72 | + raise ValueError("KEY_VAULT_NAME and SECRET_NAME environment variables are required") |
| 73 | + |
| 74 | + return cls.from_key_vault(key_vault_name, secret_name) |
| 75 | + |
| 76 | + @classmethod |
| 77 | + def from_key_vault(cls, vault_name: str, secret_name: str) -> 'SqlHelper': |
| 78 | + """ |
| 79 | + Create a SqlHelper instance by reading the connection string from Azure Key Vault. |
| 80 | + |
| 81 | + """ |
| 82 | + vault_url = f"https://{vault_name}.vault.azure.net" |
| 83 | + credential = DefaultAzureCredential(exclude_interactive_browser_credential=False) |
| 84 | + client = SecretClient(vault_url=vault_url, credential=credential) |
| 85 | + |
| 86 | + logger.info(f"Retrieving secret [{secret_name}] from Key Vault [{vault_name}]...") |
| 87 | + secret = client.get_secret(secret_name) |
| 88 | + |
| 89 | + if not secret.value: |
| 90 | + raise ValueError(f"Secret [{secret_name}] in Key Vault [{vault_name}] has no value") |
| 91 | + |
| 92 | + logger.info(f"Secret [{secret_name}] retrieved successfully from Key Vault [{vault_name}]") |
| 93 | + return cls.from_connection_string(secret.value) |
| 94 | + |
102 | 95 | @classmethod |
103 | 96 | def from_connection_string(cls, connection_string: str) -> 'SqlHelper': |
104 | 97 | """ |
@@ -127,7 +120,7 @@ def from_connection_string(cls, connection_string: str) -> 'SqlHelper': |
127 | 120 | ) |
128 | 121 |
|
129 | 122 | logger.info("Connection string parsed successfully") |
130 | | - logger.debug(f"Server: {server}, Database: {database}, Username: {username}") |
| 123 | + logger.info(f"Server: {server}, Database: {database}, Username: {username}") |
131 | 124 |
|
132 | 125 | return cls( |
133 | 126 | server=server, |
|
0 commit comments