1+ # version 1.2
2+ from azure .identity import DefaultAzureCredential
3+ from azure .keyvault .secrets import SecretClient
4+ import psycopg2
5+ from psycopg2 import sql
6+ import logging
7+ import sys
8+
9+
10+ ################################################################################################
11+ # Initialization:
12+ # Parameters and logging
13+ # Retrieve secrets from Azure Key Vault
14+ ################################################################################################
15+
16+
17+ key_vault_name = None
18+
19+ # Below parameters will be retrieve from Key Vault
20+ postgresql_end_point = None
21+ postgresql_admin_login = None
22+ mid_name = None
23+ postgresql_db_name = None
24+
25+
26+ # postgresql_admin_password = "YourValue" # Only used for local testing.
27+ # key_vault_name = "yourKeyVaultNameOnly" # if test locally
28+
29+
30+
31+ # Configure logging
32+ logging .basicConfig (
33+ level = logging .INFO ,
34+ format = "%(asctime)s - %(levelname)s - %(message)s" ,
35+ handlers = [logging .StreamHandler (sys .stdout )]
36+ )
37+ logging .info ("Starting the script..." )
38+ # Retrieve secrets from Key Vault
39+
40+ try :
41+ logging .info (f"Retrieving secrets from Key Vault '{ key_vault_name } '..." )
42+ key_vault_uri = f"https://{ key_vault_name } .vault.azure.net/"
43+ credential = DefaultAzureCredential ()
44+ secret_client = SecretClient (vault_url = key_vault_uri , credential = credential )
45+ postgresql_end_point = secret_client .get_secret ("postgresql-server-end-point" ).value
46+ postgresql_admin_login = secret_client .get_secret ("postgresql-admin-login" ).value
47+ mid_name = secret_client .get_secret ("mid-name" ).value
48+ postgresql_db_name = secret_client .get_secret ("postgresql-db-name" ).value
49+
50+ logging .info (f"Retrieved values from Key Vault:" )
51+ logging .info (f"PostgreSql End Point: { postgresql_end_point } " )
52+ logging .info (f"Admin Principal Name: { postgresql_admin_login } " )
53+ logging .info (f"Managed Identity Name: { mid_name } " )
54+ logging .info (f"Database Name: { postgresql_db_name } " )
55+
56+ except Exception as e :
57+ logging .error (f"An error occurred while retrieving secrets: { e } " )
58+ sys .exit (1 )
59+
60+ # The rest of your script (e.g., database connection and table creation) goes here...
61+
62+ # Grant Permission Function
63+ def grant_permissions (cursor , db_name , schema_name , principal_name ):
64+ """
65+ Grants database and schema-level permissions to a specified principal.
66+ """
67+ try :
68+ logging .info (f"Granting permissions to principal: { principal_name } " )
69+
70+ # Check if the principal exists in the database
71+ cursor .execute (
72+ sql .SQL ("SELECT 1 FROM pg_roles WHERE rolname = {principal}" ).format (
73+ principal = sql .Literal (principal_name )
74+ )
75+ )
76+ if cursor .fetchone () is None :
77+ logging .info (f"Principal '{ principal_name } ' does not exist. Creating it..." )
78+ add_principal_user_query = sql .SQL (
79+ "SELECT * FROM pgaadauth_create_principal({principal}, false, false)"
80+ )
81+ cursor .execute (
82+ add_principal_user_query .format (
83+ principal = sql .Literal (principal_name ),
84+ )
85+ )
86+
87+ # Grant CONNECT on database
88+ grant_connect_query = sql .SQL ("GRANT CONNECT ON DATABASE {database} TO {principal}" )
89+ cursor .execute (
90+ grant_connect_query .format (
91+ database = sql .Identifier (db_name ),
92+ principal = sql .Identifier (principal_name ),
93+ )
94+ )
95+ logging .info (f"Granted CONNECT on database '{ db_name } ' to '{ principal_name } '" )
96+
97+ # Grant SELECT, INSERT, UPDATE, DELETE on schema tables
98+ grant_permissions_query = sql .SQL (
99+ "GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA {schema} TO {principal}"
100+ )
101+ cursor .execute (
102+ grant_permissions_query .format (
103+ schema = sql .Identifier (schema_name ),
104+ principal = sql .Identifier (principal_name ),
105+ )
106+ )
107+ logging .info (f"Granted table-level permissions on schema '{ schema_name } ' to '{ principal_name } '" )
108+
109+ except Exception as e :
110+ logging .error (f"Error granting permissions to '{ principal_name } ': { e } " )
111+ raise
112+
113+ #####################################################################################################
114+ # Main Program
115+ #####################################################################################################
116+ try :
117+ # Acquire the access token
118+ logging .info ("Acquiring access token..." )
119+ cred = DefaultAzureCredential ()
120+ access_token = cred .get_token ("https://ossrdbms-aad.database.windows.net/.default" )
121+ logging .info ("Access token acquired successfully." )
122+
123+ # Combine the token with the connection string to establish the connection
124+ logging .info ("Establishing connection to the PostgreSQL server..." )
125+ conn_string = "host={0} user={1} dbname={2} password={3} sslmode=require" .format (
126+ postgresql_end_point , mid_name , postgresql_db_name , access_token .token
127+ )
128+
129+ # Below can be used for local testing.
130+ # conn_string = "host={0} user={1} dbname={2} password={3} sslmode=require".format(
131+ # postgresql_end_point, postgresql_admin_login, postgresql_db_name, postgresql_admin_password
132+ # )
133+
134+ conn = psycopg2 .connect (conn_string )
135+ cursor = conn .cursor ()
136+ logging .info ("Connection established successfully." )
137+
138+ # Drop and recreate the products table
139+ logging .info ("Dropping and recreating the 'products' table..." )
140+ cursor .execute ("DROP TABLE IF EXISTS products" )
141+ conn .commit ()
142+
143+ create_products_sql = """
144+ CREATE TABLE IF NOT EXISTS products
145+ (
146+ id integer,
147+ product_name character varying(100),
148+ price numeric(10,2) NOT NULL,
149+ category character varying(50),
150+ brand character varying(50),
151+ product_description text
152+ );
153+ """
154+ cursor .execute (create_products_sql )
155+ conn .commit ()
156+ logging .info ("'products' table created successfully." )
157+
158+ # Drop and recreate the customers table
159+ logging .info ("Dropping and recreating the 'customers' table..." )
160+ cursor .execute ("DROP TABLE IF EXISTS customers" )
161+ conn .commit ()
162+
163+ create_customers_sql = """
164+ CREATE TABLE customers
165+ (
166+ id integer,
167+ first_name character varying(50),
168+ last_name character varying(50),
169+ gender character varying(10),
170+ date_of_birth date,
171+ age integer,
172+ email character varying(100),
173+ phone character varying(20),
174+ post_address character varying(255),
175+ membership character varying(50)
176+ );
177+ """
178+ cursor .execute (create_customers_sql )
179+ conn .commit ()
180+ logging .info ("'customers' table created successfully." )
181+
182+ # Drop and recreate the orders table
183+ logging .info ("Dropping and recreating the 'orders' table..." )
184+ cursor .execute ("DROP TABLE IF EXISTS orders" )
185+ conn .commit ()
186+
187+ create_orders_sql = """
188+ CREATE TABLE orders
189+ (
190+ id integer,
191+ customer_id integer,
192+ customer_first_name character varying(50),
193+ customer_last_name character varying(50),
194+ customer_gender character varying(10),
195+ customer_age integer,
196+ customer_email character varying(100),
197+ customer_phone character varying(20),
198+ order_date date,
199+ product_id integer,
200+ product_name character varying(100),
201+ quantity integer,
202+ unit_price numeric(10,2),
203+ total numeric(10,2),
204+ category character varying(50),
205+ brand character varying(50),
206+ product_description text,
207+ return_status BOOLEAN DEFAULT FALSE
208+ );
209+ """
210+ cursor .execute (create_orders_sql )
211+ conn .commit ()
212+ logging .info ("'orders' table created successfully." )
213+
214+ # Add Vector extension
215+ logging .info ("Adding 'vector' extension..." )
216+ cursor .execute ("CREATE EXTENSION IF NOT EXISTS vector CASCADE;" )
217+ conn .commit ()
218+
219+ cursor .execute ("DROP TABLE IF EXISTS vector_store;" )
220+ conn .commit ()
221+
222+ create_vs_sql = """
223+ CREATE TABLE IF NOT EXISTS vector_store(
224+ id text,
225+ title text,
226+ chunk integer,
227+ chunk_id text,
228+ "offset" integer,
229+ page_number integer,
230+ content text,
231+ source text,
232+ metadata text,
233+ content_vector public.vector(1536)
234+ );
235+ """
236+ cursor .execute (create_vs_sql )
237+ conn .commit ()
238+
239+ cursor .execute (
240+ "CREATE INDEX vector_store_content_vector_idx ON vector_store USING hnsw (content_vector vector_cosine_ops);"
241+ )
242+ conn .commit ()
243+ logging .info ("'vector_store' table and index created successfully." )
244+
245+ # Grant permissions to the admin principal if provided
246+ if postgresql_admin_login and postgresql_admin_login .strip ():
247+ logging .info (f"Granting permissions to admin principal: { postgresql_admin_login } " )
248+ grant_permissions (cursor , postgresql_db_name , "public" , postgresql_admin_login )
249+ conn .commit ()
250+
251+ # Grant permissions to the additional principal if provided
252+ if mid_name and mid_name .strip ():
253+ logging .info (f"Granting permissions to identity: { mid_name } " )
254+ grant_permissions (cursor , postgresql_db_name , "public" , mid_name )
255+ conn .commit ()
256+
257+ # Set default privileges for future tables in the public schema
258+ logging .info ("Setting default privileges for future tables in the 'public' schema..." )
259+ cursor .execute ("""
260+ ALTER DEFAULT PRIVILEGES IN SCHEMA public
261+ GRANT ALL PRIVILEGES ON TABLES TO azure_pg_admin;
262+ """ )
263+ conn .commit ()
264+ logging .info ("Default privileges set successfully." )
265+
266+ except Exception as e :
267+ logging .error (f"An error occurred: { e } " )
268+ finally :
269+ # Close the cursor and connection
270+ if 'cursor' in locals () and cursor :
271+ cursor .close ()
272+ if 'conn' in locals () and conn :
273+ conn .close ()
274+ logging .info ("Database connection closed." )
0 commit comments