Coverage for .tox/coverage/lib/python3.11/site-packages/wuttamess/postgres.py: 100%
34 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-12-19 07:00 -0600
« prev ^ index » next coverage.py v7.6.1, created at 2024-12-19 07:00 -0600
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# WuttaMess -- Fabric Automation Helpers
5# Copyright © 2024 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24PostgreSQL DB utilities
25"""
28def sql(c, sql, database='', port=None, **kwargs):
29 """
30 Execute some SQL as the ``postgres`` user.
32 :param c: Fabric connection.
34 :param sql: SQL string to execute.
36 :param database: Name of the database on which to execute the SQL.
37 If not specified, default ``postgres`` is assumed.
39 :param port: Optional port for PostgreSQL; default is 5432.
40 """
41 port = f' --port={port}' if port else ''
42 return c.sudo(f'psql{port} --tuples-only --no-align --command="{sql}" {database}',
43 user='postgres', **kwargs)
46def user_exists(c, name, port=None):
47 """
48 Determine if a given PostgreSQL user exists.
50 :param c: Fabric connection.
52 :param name: Username to check for.
54 :param port: Optional port for PostgreSQL; default is 5432.
56 :returns: ``True`` if user exists, else ``False``.
57 """
58 user = sql(c, f"SELECT rolname FROM pg_roles WHERE rolname = '{name}'", port=port).stdout.strip()
59 return bool(user)
62def create_user(c, name, password=None, port=None, checkfirst=True):
63 """
64 Create a PostgreSQL user account.
66 :param c: Fabric connection.
68 :param name: Username to create.
70 :param password: Optional password for the new user. If set, will
71 call :func:`set_user_password()`.
73 :param port: Optional port for PostgreSQL; default is 5432.
75 :param checkfirst: If true (the default), first check if user
76 exists and skip creating if already present. If false, then
77 try to create user with no check.
78 """
79 if not checkfirst or not user_exists(c, name, port=port):
80 portarg = f' --port={port}' if port else ''
81 c.sudo(f'createuser{portarg} --no-createrole --no-superuser {name}',
82 user='postgres')
83 if password:
84 set_user_password(c, name, password, port=port)
87def set_user_password(c, name, password, port=None):
88 """
89 Set the password for a PostgreSQL user account.
91 :param c: Fabric connection.
93 :param name: Username whose password is to be set.
95 :param password: Password for the new user.
97 :param port: Optional port for PostgreSQL; default is 5432.
98 """
99 sql(c, f"ALTER USER \\\"{name}\\\" PASSWORD '{password}';", port=port, hide=True, echo=False)
102def db_exists(c, name, port=None):
103 """
104 Determine if a given PostgreSQL database exists.
106 :param c: Fabric connection.
108 :param name: Name of the database to check for.
110 :param port: Optional port for PostgreSQL; default is 5432.
112 :returns: ``True`` if database exists, else ``False``.
113 """
114 db = sql(c, f"SELECT datname FROM pg_database WHERE datname = '{name}'", port=port).stdout.strip()
115 return db == name
118def create_db(c, name, owner=None, port=None, checkfirst=True):
119 """
120 Create a PostgreSQL database.
122 :param c: Fabric connection.
124 :param name: Name of the database to create.
126 :param owner: Optional role name to set as owner for the database.
128 :param port: Optional port for PostgreSQL; default is 5432.
130 :param checkfirst: If true (the default), first check if DB exists
131 and skip creating if already present. If false, then try to
132 create DB with no check.
133 """
134 if not checkfirst or not db_exists(c, name, port=port):
135 port = f' --port={port}' if port else ''
136 owner = f' --owner={owner}' if owner else ''
137 c.sudo(f'createdb{port}{owner} {name}',
138 user='postgres')
141def drop_db(c, name, checkfirst=True):
142 """
143 Drop a PostgreSQL database.
145 :param c: Fabric connection.
147 :param name: Name of the database to drop.
149 :param checkfirst: If true (the default), first check if DB exists
150 and skip dropping if not present. If false, then try to drop
151 DB with no check.
152 """
153 if not checkfirst or db_exists(c, name):
154 c.sudo(f'dropdb {name}', user='postgres')
157def dump_db(c, name):
158 """
159 Dump a PostgreSQL database to file.
161 This uses the ``pg_dump`` and ``gzip`` commands to produce a
162 compressed SQL dump. The filename returned is based on the
163 ``name`` provided, e.g. ``mydbname.sql.gz``.
165 :param c: Fabric connection.
167 :param name: Name of the database to dump.
169 :returns: Base name of the output file. We only return the
170 filename and not the path, since the file is expected to exist
171 in the connected user's home folder.
172 """
173 sql_name = f'{name}.sql'
174 gz_name = f'{sql_name}.gz'
175 tmp_name = f'/tmp/{gz_name}'
177 # TODO: when pg_dump fails the command still succeeds! (would this work?)
178 #cmd = f'set -e && pg_dump {name} | gzip -c > {tmp_name}'
179 cmd = f'pg_dump {name} | gzip -c > {tmp_name}'
181 c.sudo(cmd, user='postgres')
182 c.run(f"cp {tmp_name} {gz_name}")
183 c.run(f"rm {tmp_name}")
185 return gz_name