1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00
community.general/plugins/modules/database/postgresql/postgresql_info.py
Andrew Klychkov da7f9ffc3f
postgresql_info: add in_recovery return value to show if a service in recovery mode or not (#1091)
* postgresql_info: add in_recovery return value to show if a service is in recovery mode or not

* add changelog fragment

* fix sanity
2020-10-14 14:30:19 +03:00

1030 lines
34 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: postgresql_info
short_description: Gather information about PostgreSQL servers
description:
- Gathers information about PostgreSQL servers.
options:
filter:
description:
- Limit the collected information by comma separated string or YAML list.
- Allowable values are C(version),
C(databases), C(in_recovery), C(settings), C(tablespaces), C(roles),
C(replications), C(repl_slots).
- By default, collects all subsets.
- You can use shell-style (fnmatch) wildcard to pass groups of values (see Examples).
- You can use '!' before value (for example, C(!settings)) to exclude it from the information.
- If you pass including and excluding values to the filter, for example, I(filter=!settings,ver),
the excluding values will be ignored.
type: list
elements: str
db:
description:
- Name of database to connect.
type: str
aliases:
- login_db
session_role:
description:
- Switch to session_role after connecting. The specified session_role must
be a role that the current login_user is a member of.
- Permissions checking for SQL commands is carried out as though
the session_role were the one that had logged in originally.
type: str
trust_input:
description:
- If C(no), check whether a value of I(session_role) is potentially dangerous.
- It makes sense to use C(yes) only when SQL injections via I(session_role) are possible.
type: bool
default: yes
version_added: '0.2.0'
seealso:
- module: community.general.postgresql_ping
author:
- Andrew Klychkov (@Andersson007)
extends_documentation_fragment:
- community.general.postgres
'''
EXAMPLES = r'''
# Display info from postgres hosts.
# ansible postgres -m postgresql_info
# Display only databases and roles info from all hosts using shell-style wildcards:
# ansible all -m postgresql_info -a 'filter=dat*,rol*'
# Display only replications and repl_slots info from standby hosts using shell-style wildcards:
# ansible standby -m postgresql_info -a 'filter=repl*'
# Display all info from databases hosts except settings:
# ansible databases -m postgresql_info -a 'filter=!settings'
- name: Collect PostgreSQL version and extensions
become: yes
become_user: postgres
community.general.postgresql_info:
filter: ver*,ext*
- name: Collect all info except settings and roles
become: yes
become_user: postgres
community.general.postgresql_info:
filter: "!settings,!roles"
# On FreeBSD with PostgreSQL 9.5 version and lower use pgsql user to become
# and pass "postgres" as a database to connect to
- name: Collect tablespaces and repl_slots info
become: yes
become_user: pgsql
community.general.postgresql_info:
db: postgres
filter:
- tablesp*
- repl_sl*
- name: Collect all info except databases
become: yes
become_user: postgres
community.general.postgresql_info:
filter:
- "!databases"
'''
RETURN = r'''
version:
description: Database server version U(https://www.postgresql.org/support/versioning/).
returned: always
type: dict
sample: { "version": { "major": 10, "minor": 6 } }
contains:
major:
description: Major server version.
returned: always
type: int
sample: 11
minor:
description: Minor server version.
returned: always
type: int
sample: 1
in_recovery:
description: Indicates if the service is in recovery mode or not.
returned: always
type: bool
sample: false
databases:
description: Information about databases.
returned: always
type: dict
sample:
- { "postgres": { "access_priv": "", "collate": "en_US.UTF-8",
"ctype": "en_US.UTF-8", "encoding": "UTF8", "owner": "postgres", "size": "7997 kB" } }
contains:
database_name:
description: Database name.
returned: always
type: dict
sample: template1
contains:
access_priv:
description: Database access privileges.
returned: always
type: str
sample: "=c/postgres_npostgres=CTc/postgres"
collate:
description:
- Database collation U(https://www.postgresql.org/docs/current/collation.html).
returned: always
type: str
sample: en_US.UTF-8
ctype:
description:
- Database LC_CTYPE U(https://www.postgresql.org/docs/current/multibyte.html).
returned: always
type: str
sample: en_US.UTF-8
encoding:
description:
- Database encoding U(https://www.postgresql.org/docs/current/multibyte.html).
returned: always
type: str
sample: UTF8
owner:
description:
- Database owner U(https://www.postgresql.org/docs/current/sql-createdatabase.html).
returned: always
type: str
sample: postgres
size:
description: Database size in bytes.
returned: always
type: str
sample: 8189415
extensions:
description:
- Extensions U(https://www.postgresql.org/docs/current/sql-createextension.html).
returned: always
type: dict
sample:
- { "plpgsql": { "description": "PL/pgSQL procedural language",
"extversion": { "major": 1, "minor": 0 } } }
contains:
extdescription:
description: Extension description.
returned: if existent
type: str
sample: PL/pgSQL procedural language
extversion:
description: Extension description.
returned: always
type: dict
contains:
major:
description: Extension major version.
returned: always
type: int
sample: 1
minor:
description: Extension minor version.
returned: always
type: int
sample: 0
nspname:
description: Namespace where the extension is.
returned: always
type: str
sample: pg_catalog
languages:
description: Procedural languages U(https://www.postgresql.org/docs/current/xplang.html).
returned: always
type: dict
sample: { "sql": { "lanacl": "", "lanowner": "postgres" } }
contains:
lanacl:
description:
- Language access privileges
U(https://www.postgresql.org/docs/current/catalog-pg-language.html).
returned: always
type: str
sample: "{postgres=UC/postgres,=U/postgres}"
lanowner:
description:
- Language owner U(https://www.postgresql.org/docs/current/catalog-pg-language.html).
returned: always
type: str
sample: postgres
namespaces:
description:
- Namespaces (schema) U(https://www.postgresql.org/docs/current/sql-createschema.html).
returned: always
type: dict
sample: { "pg_catalog": { "nspacl": "{postgres=UC/postgres,=U/postgres}", "nspowner": "postgres" } }
contains:
nspacl:
description:
- Access privileges U(https://www.postgresql.org/docs/current/catalog-pg-namespace.html).
returned: always
type: str
sample: "{postgres=UC/postgres,=U/postgres}"
nspowner:
description:
- Schema owner U(https://www.postgresql.org/docs/current/catalog-pg-namespace.html).
returned: always
type: str
sample: postgres
publications:
description:
- Information about logical replication publications (available for PostgreSQL 10 and higher)
U(https://www.postgresql.org/docs/current/logical-replication-publication.html).
- Content depends on PostgreSQL server version.
returned: if configured
type: dict
sample: { "pub1": { "ownername": "postgres", "puballtables": true, "pubinsert": true, "pubupdate": true } }
version_added: '0.2.0'
subscriptions:
description:
- Information about replication subscriptions (available for PostgreSQL 10 and higher)
U(https://www.postgresql.org/docs/current/logical-replication-subscription.html).
- Content depends on PostgreSQL server version.
returned: if configured
type: dict
sample:
- { "my_subscription": {"ownername": "postgres", "subenabled": true, "subpublications": ["first_publication"] } }
version_added: '0.2.0'
repl_slots:
description:
- Replication slots (available in 9.4 and later)
U(https://www.postgresql.org/docs/current/view-pg-replication-slots.html).
returned: if existent
type: dict
sample: { "slot0": { "active": false, "database": null, "plugin": null, "slot_type": "physical" } }
contains:
active:
description:
- True means that a receiver has connected to it, and it is currently reserving archives.
returned: always
type: bool
sample: true
database:
description: Database name this slot is associated with, or null.
returned: always
type: str
sample: acme
plugin:
description:
- Base name of the shared object containing the output plugin
this logical slot is using, or null for physical slots.
returned: always
type: str
sample: pgoutput
slot_type:
description: The slot type - physical or logical.
returned: always
type: str
sample: logical
replications:
description:
- Information about the current replications by process PIDs
U(https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-STATS-VIEWS-TABLE).
returned: if pg_stat_replication view existent
type: dict
sample:
- { "76580": { "app_name": "standby1", "backend_start": "2019-02-03 00:14:33.908593+03",
"client_addr": "10.10.10.2", "client_hostname": "", "state": "streaming", "usename": "postgres" } }
contains:
usename:
description:
- Name of the user logged into this WAL sender process ('usename' is a column name in pg_stat_replication view).
returned: always
type: str
sample: replication_user
app_name:
description: Name of the application that is connected to this WAL sender.
returned: if existent
type: str
sample: acme_srv
client_addr:
description:
- IP address of the client connected to this WAL sender.
- If this field is null, it indicates that the client is connected
via a Unix socket on the server machine.
returned: always
type: str
sample: 10.0.0.101
client_hostname:
description:
- Host name of the connected client, as reported by a reverse DNS lookup of client_addr.
- This field will only be non-null for IP connections, and only when log_hostname is enabled.
returned: always
type: str
sample: dbsrv1
backend_start:
description: Time when this process was started, i.e., when the client connected to this WAL sender.
returned: always
type: str
sample: "2019-02-03 00:14:33.908593+03"
state:
description: Current WAL sender state.
returned: always
type: str
sample: streaming
tablespaces:
description:
- Information about tablespaces U(https://www.postgresql.org/docs/current/catalog-pg-tablespace.html).
returned: always
type: dict
sample:
- { "test": { "spcacl": "{postgres=C/postgres,andreyk=C/postgres}", "spcoptions": [ "seq_page_cost=1" ],
"spcowner": "postgres" } }
contains:
spcacl:
description: Tablespace access privileges.
returned: always
type: str
sample: "{postgres=C/postgres,andreyk=C/postgres}"
spcoptions:
description: Tablespace-level options.
returned: always
type: list
sample: [ "seq_page_cost=1" ]
spcowner:
description: Owner of the tablespace.
returned: always
type: str
sample: test_user
roles:
description:
- Information about roles U(https://www.postgresql.org/docs/current/user-manag.html).
returned: always
type: dict
sample:
- { "test_role": { "canlogin": true, "member_of": [ "user_ro" ], "superuser": false,
"valid_until": "9999-12-31T23:59:59.999999+00:00" } }
contains:
canlogin:
description: Login privilege U(https://www.postgresql.org/docs/current/role-attributes.html).
returned: always
type: bool
sample: true
member_of:
description:
- Role membership U(https://www.postgresql.org/docs/current/role-membership.html).
returned: always
type: list
sample: [ "read_only_users" ]
superuser:
description: User is a superuser or not.
returned: always
type: bool
sample: false
valid_until:
description:
- Password expiration date U(https://www.postgresql.org/docs/current/sql-alterrole.html).
returned: always
type: str
sample: "9999-12-31T23:59:59.999999+00:00"
pending_restart_settings:
description:
- List of settings that are pending restart to be set.
returned: always
type: list
sample: [ "shared_buffers" ]
settings:
description:
- Information about run-time server parameters
U(https://www.postgresql.org/docs/current/view-pg-settings.html).
returned: always
type: dict
sample:
- { "work_mem": { "boot_val": "4096", "context": "user", "max_val": "2147483647",
"min_val": "64", "setting": "8192", "sourcefile": "/var/lib/pgsql/10/data/postgresql.auto.conf",
"unit": "kB", "vartype": "integer", "val_in_bytes": 4194304 } }
contains:
setting:
description: Current value of the parameter.
returned: always
type: str
sample: 49152
unit:
description: Implicit unit of the parameter.
returned: always
type: str
sample: kB
boot_val:
description:
- Parameter value assumed at server startup if the parameter is not otherwise set.
returned: always
type: str
sample: 4096
min_val:
description:
- Minimum allowed value of the parameter (null for non-numeric values).
returned: always
type: str
sample: 64
max_val:
description:
- Maximum allowed value of the parameter (null for non-numeric values).
returned: always
type: str
sample: 2147483647
sourcefile:
description:
- Configuration file the current value was set in.
- Null for values set from sources other than configuration files,
or when examined by a user who is neither a superuser or a member of pg_read_all_settings.
- Helpful when using include directives in configuration files.
returned: always
type: str
sample: /var/lib/pgsql/10/data/postgresql.auto.conf
context:
description:
- Context required to set the parameter's value.
- For more information see U(https://www.postgresql.org/docs/current/view-pg-settings.html).
returned: always
type: str
sample: user
vartype:
description:
- Parameter type (bool, enum, integer, real, or string).
returned: always
type: str
sample: integer
val_in_bytes:
description:
- Current value of the parameter in bytes.
returned: if supported
type: int
sample: 2147483647
pretty_val:
description:
- Value presented in the pretty form.
returned: always
type: str
sample: 2MB
pending_restart:
description:
- True if the value has been changed in the configuration file but needs a restart; or false otherwise.
- Returns only if C(settings) is passed.
returned: always
type: bool
sample: false
'''
from fnmatch import fnmatch
try:
from psycopg2.extras import DictCursor
except ImportError:
# psycopg2 is checked by connect_to_db()
# from ansible.module_utils.postgres
pass
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.database import (
check_input,
)
from ansible_collections.community.general.plugins.module_utils.postgres import (
connect_to_db,
get_conn_params,
postgres_common_argument_spec,
)
from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_native
# ===========================================
# PostgreSQL module specific support methods.
#
class PgDbConn(object):
"""Auxiliary class for working with PostgreSQL connection objects.
Arguments:
module (AnsibleModule): Object of AnsibleModule class that
contains connection parameters.
"""
def __init__(self, module):
self.module = module
self.db_conn = None
self.cursor = None
def connect(self):
"""Connect to a PostgreSQL database and return a cursor object.
Note: connection parameters are passed by self.module object.
"""
conn_params = get_conn_params(self.module, self.module.params, warn_db_default=False)
self.db_conn = connect_to_db(self.module, conn_params)
return self.db_conn.cursor(cursor_factory=DictCursor)
def reconnect(self, dbname):
"""Reconnect to another database and return a PostgreSQL cursor object.
Arguments:
dbname (string): Database name to connect to.
"""
self.db_conn.close()
self.module.params['database'] = dbname
return self.connect()
class PgClusterInfo(object):
"""Class for collection information about a PostgreSQL instance.
Arguments:
module (AnsibleModule): Object of AnsibleModule class.
db_conn_obj (psycopg2.connect): PostgreSQL connection object.
"""
def __init__(self, module, db_conn_obj):
self.module = module
self.db_obj = db_conn_obj
self.cursor = db_conn_obj.connect()
self.pg_info = {
"version": {},
"in_recovery": None,
"tablespaces": {},
"databases": {},
"replications": {},
"repl_slots": {},
"settings": {},
"roles": {},
"pending_restart_settings": [],
}
def collect(self, val_list=False):
"""Collect information based on 'filter' option."""
subset_map = {
"version": self.get_pg_version,
"in_recovery": self.get_recovery_state,
"tablespaces": self.get_tablespaces,
"databases": self.get_db_info,
"replications": self.get_repl_info,
"repl_slots": self.get_rslot_info,
"settings": self.get_settings,
"roles": self.get_role_info,
}
incl_list = []
excl_list = []
# Notice: incl_list and excl_list
# don't make sense together, therefore,
# if incl_list is not empty, we collect
# only values from it:
if val_list:
for i in val_list:
if i[0] != '!':
incl_list.append(i)
else:
excl_list.append(i.lstrip('!'))
if incl_list:
for s in subset_map:
for i in incl_list:
if fnmatch(s, i):
subset_map[s]()
break
elif excl_list:
found = False
# Collect info:
for s in subset_map:
for e in excl_list:
if fnmatch(s, e):
found = True
if not found:
subset_map[s]()
else:
found = False
# Default behaviour, if include or exclude is not passed:
else:
# Just collect info for each item:
for s in subset_map:
subset_map[s]()
return self.pg_info
def get_pub_info(self):
"""Get publication statistics."""
query = ("SELECT p.*, r.rolname AS ownername "
"FROM pg_catalog.pg_publication AS p "
"JOIN pg_catalog.pg_roles AS r "
"ON p.pubowner = r.oid")
result = self.__exec_sql(query)
if result:
result = [dict(row) for row in result]
else:
return {}
publications = {}
for elem in result:
if not publications.get(elem['pubname']):
publications[elem['pubname']] = {}
for key, val in iteritems(elem):
if key != 'pubname':
publications[elem['pubname']][key] = val
return publications
def get_subscr_info(self):
"""Get subscription statistics."""
query = ("SELECT s.*, r.rolname AS ownername, d.datname AS dbname "
"FROM pg_catalog.pg_subscription s "
"JOIN pg_catalog.pg_database d "
"ON s.subdbid = d.oid "
"JOIN pg_catalog.pg_roles AS r "
"ON s.subowner = r.oid")
result = self.__exec_sql(query)
if result:
result = [dict(row) for row in result]
else:
return {}
subscr_info = {}
for elem in result:
if not subscr_info.get(elem['dbname']):
subscr_info[elem['dbname']] = {}
if not subscr_info[elem['dbname']].get(elem['subname']):
subscr_info[elem['dbname']][elem['subname']] = {}
for key, val in iteritems(elem):
if key not in ('subname', 'dbname'):
subscr_info[elem['dbname']][elem['subname']][key] = val
return subscr_info
def get_tablespaces(self):
"""Get information about tablespaces."""
# Check spcoption exists:
opt = self.__exec_sql("SELECT column_name "
"FROM information_schema.columns "
"WHERE table_name = 'pg_tablespace' "
"AND column_name = 'spcoptions'")
if not opt:
query = ("SELECT s.spcname, a.rolname, s.spcacl "
"FROM pg_tablespace AS s "
"JOIN pg_authid AS a ON s.spcowner = a.oid")
else:
query = ("SELECT s.spcname, a.rolname, s.spcacl, s.spcoptions "
"FROM pg_tablespace AS s "
"JOIN pg_authid AS a ON s.spcowner = a.oid")
res = self.__exec_sql(query)
ts_dict = {}
for i in res:
ts_name = i[0]
ts_info = dict(
spcowner=i[1],
spcacl=i[2] if i[2] else '',
)
if opt:
ts_info['spcoptions'] = i[3] if i[3] else []
ts_dict[ts_name] = ts_info
self.pg_info["tablespaces"] = ts_dict
def get_ext_info(self):
"""Get information about existing extensions."""
# Check that pg_extension exists:
res = self.__exec_sql("SELECT EXISTS (SELECT 1 FROM "
"information_schema.tables "
"WHERE table_name = 'pg_extension')")
if not res[0][0]:
return True
query = ("SELECT e.extname, e.extversion, n.nspname, c.description "
"FROM pg_catalog.pg_extension AS e "
"LEFT JOIN pg_catalog.pg_namespace AS n "
"ON n.oid = e.extnamespace "
"LEFT JOIN pg_catalog.pg_description AS c "
"ON c.objoid = e.oid "
"AND c.classoid = 'pg_catalog.pg_extension'::pg_catalog.regclass")
res = self.__exec_sql(query)
ext_dict = {}
for i in res:
ext_ver = i[1].split('.')
ext_dict[i[0]] = dict(
extversion=dict(
major=int(ext_ver[0]),
minor=int(ext_ver[1]),
),
nspname=i[2],
description=i[3],
)
return ext_dict
def get_role_info(self):
"""Get information about roles (in PgSQL groups and users are roles)."""
query = ("SELECT r.rolname, r.rolsuper, r.rolcanlogin, "
"r.rolvaliduntil, "
"ARRAY(SELECT b.rolname "
"FROM pg_catalog.pg_auth_members AS m "
"JOIN pg_catalog.pg_roles AS b ON (m.roleid = b.oid) "
"WHERE m.member = r.oid) AS memberof "
"FROM pg_catalog.pg_roles AS r "
"WHERE r.rolname !~ '^pg_'")
res = self.__exec_sql(query)
rol_dict = {}
for i in res:
rol_dict[i[0]] = dict(
superuser=i[1],
canlogin=i[2],
valid_until=i[3] if i[3] else '',
member_of=i[4] if i[4] else [],
)
self.pg_info["roles"] = rol_dict
def get_rslot_info(self):
"""Get information about replication slots if exist."""
# Check that pg_replication_slots exists:
res = self.__exec_sql("SELECT EXISTS (SELECT 1 FROM "
"information_schema.tables "
"WHERE table_name = 'pg_replication_slots')")
if not res[0][0]:
return True
query = ("SELECT slot_name, plugin, slot_type, database, "
"active FROM pg_replication_slots")
res = self.__exec_sql(query)
# If there is no replication:
if not res:
return True
rslot_dict = {}
for i in res:
rslot_dict[i[0]] = dict(
plugin=i[1],
slot_type=i[2],
database=i[3],
active=i[4],
)
self.pg_info["repl_slots"] = rslot_dict
def get_settings(self):
"""Get server settings."""
# Check pending restart column exists:
pend_rest_col_exists = self.__exec_sql("SELECT 1 FROM information_schema.columns "
"WHERE table_name = 'pg_settings' "
"AND column_name = 'pending_restart'")
if not pend_rest_col_exists:
query = ("SELECT name, setting, unit, context, vartype, "
"boot_val, min_val, max_val, sourcefile "
"FROM pg_settings")
else:
query = ("SELECT name, setting, unit, context, vartype, "
"boot_val, min_val, max_val, sourcefile, pending_restart "
"FROM pg_settings")
res = self.__exec_sql(query)
set_dict = {}
for i in res:
val_in_bytes = None
setting = i[1]
if i[2]:
unit = i[2]
else:
unit = ''
if unit == 'kB':
val_in_bytes = int(setting) * 1024
elif unit == '8kB':
val_in_bytes = int(setting) * 1024 * 8
elif unit == 'MB':
val_in_bytes = int(setting) * 1024 * 1024
if val_in_bytes is not None and val_in_bytes < 0:
val_in_bytes = 0
setting_name = i[0]
pretty_val = self.__get_pretty_val(setting_name)
pending_restart = None
if pend_rest_col_exists:
pending_restart = i[9]
set_dict[setting_name] = dict(
setting=setting,
unit=unit,
context=i[3],
vartype=i[4],
boot_val=i[5] if i[5] else '',
min_val=i[6] if i[6] else '',
max_val=i[7] if i[7] else '',
sourcefile=i[8] if i[8] else '',
pretty_val=pretty_val,
)
if val_in_bytes is not None:
set_dict[setting_name]['val_in_bytes'] = val_in_bytes
if pending_restart is not None:
set_dict[setting_name]['pending_restart'] = pending_restart
if pending_restart:
self.pg_info["pending_restart_settings"].append(setting_name)
self.pg_info["settings"] = set_dict
def get_repl_info(self):
"""Get information about replication if the server is a master."""
# Check that pg_replication_slots exists:
res = self.__exec_sql("SELECT EXISTS (SELECT 1 FROM "
"information_schema.tables "
"WHERE table_name = 'pg_stat_replication')")
if not res[0][0]:
return True
query = ("SELECT r.pid, a.rolname, r.application_name, r.client_addr, "
"r.client_hostname, r.backend_start::text, r.state "
"FROM pg_stat_replication AS r "
"JOIN pg_authid AS a ON r.usesysid = a.oid")
res = self.__exec_sql(query)
# If there is no replication:
if not res:
return True
repl_dict = {}
for i in res:
repl_dict[i[0]] = dict(
usename=i[1],
app_name=i[2] if i[2] else '',
client_addr=i[3],
client_hostname=i[4] if i[4] else '',
backend_start=i[5],
state=i[6],
)
self.pg_info["replications"] = repl_dict
def get_lang_info(self):
"""Get information about current supported languages."""
query = ("SELECT l.lanname, a.rolname, l.lanacl "
"FROM pg_language AS l "
"JOIN pg_authid AS a ON l.lanowner = a.oid")
res = self.__exec_sql(query)
lang_dict = {}
for i in res:
lang_dict[i[0]] = dict(
lanowner=i[1],
lanacl=i[2] if i[2] else '',
)
return lang_dict
def get_namespaces(self):
"""Get information about namespaces."""
query = ("SELECT n.nspname, a.rolname, n.nspacl "
"FROM pg_catalog.pg_namespace AS n "
"JOIN pg_authid AS a ON a.oid = n.nspowner")
res = self.__exec_sql(query)
nsp_dict = {}
for i in res:
nsp_dict[i[0]] = dict(
nspowner=i[1],
nspacl=i[2] if i[2] else '',
)
return nsp_dict
def get_pg_version(self):
"""Get major and minor PostgreSQL server version."""
query = "SELECT version()"
raw = self.__exec_sql(query)[0][0]
raw = raw.split()[1].split('.')
self.pg_info["version"] = dict(
major=int(raw[0]),
minor=int(raw[1]),
)
def get_recovery_state(self):
"""Get if the service is in recovery mode."""
self.pg_info["in_recovery"] = self.__exec_sql("SELECT pg_is_in_recovery()")[0][0]
def get_db_info(self):
"""Get information about the current database."""
# Following query returns:
# Name, Owner, Encoding, Collate, Ctype, Access Priv, Size
query = ("SELECT d.datname, "
"pg_catalog.pg_get_userbyid(d.datdba), "
"pg_catalog.pg_encoding_to_char(d.encoding), "
"d.datcollate, "
"d.datctype, "
"pg_catalog.array_to_string(d.datacl, E'\n'), "
"CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT') "
"THEN pg_catalog.pg_database_size(d.datname)::text "
"ELSE 'No Access' END, "
"t.spcname "
"FROM pg_catalog.pg_database AS d "
"JOIN pg_catalog.pg_tablespace t ON d.dattablespace = t.oid "
"WHERE d.datname != 'template0'")
res = self.__exec_sql(query)
db_dict = {}
for i in res:
db_dict[i[0]] = dict(
owner=i[1],
encoding=i[2],
collate=i[3],
ctype=i[4],
access_priv=i[5] if i[5] else '',
size=i[6],
)
if self.cursor.connection.server_version >= 100000:
subscr_info = self.get_subscr_info()
for datname in db_dict:
self.cursor = self.db_obj.reconnect(datname)
db_dict[datname]['namespaces'] = self.get_namespaces()
db_dict[datname]['extensions'] = self.get_ext_info()
db_dict[datname]['languages'] = self.get_lang_info()
if self.cursor.connection.server_version >= 100000:
db_dict[datname]['publications'] = self.get_pub_info()
db_dict[datname]['subscriptions'] = subscr_info.get(datname, {})
self.pg_info["databases"] = db_dict
def __get_pretty_val(self, setting):
"""Get setting's value represented by SHOW command."""
return self.__exec_sql("SHOW %s" % setting)[0][0]
def __exec_sql(self, query):
"""Execute SQL and return the result."""
try:
self.cursor.execute(query)
res = self.cursor.fetchall()
if res:
return res
except Exception as e:
self.module.fail_json(msg="Cannot execute SQL '%s': %s" % (query, to_native(e)))
self.cursor.close()
return False
# ===========================================
# Module execution.
#
def main():
argument_spec = postgres_common_argument_spec()
argument_spec.update(
db=dict(type='str', aliases=['login_db']),
filter=dict(type='list', elements='str'),
session_role=dict(type='str'),
trust_input=dict(type='bool', default=True),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
filter_ = module.params['filter']
if not module.params['trust_input']:
# Check input for potentially dangerous elements:
check_input(module, module.params['session_role'])
db_conn_obj = PgDbConn(module)
# Do job:
pg_info = PgClusterInfo(module, db_conn_obj)
module.exit_json(**pg_info.collect(filter_))
if __name__ == '__main__':
main()