mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-26 04:11:25 -07:00
* openstack: standardize tls params * tower: tower_verify_ssl->validate_certs * docker: use standard tls config params - cacert_path -> ca_cert - cert_path -> client_cert - key_path -> client_key - tls_verify -> validate_certs * k8s: standardize tls connection params - verify_ssl -> validate_certs - ssl_ca_cert -> ca_cert - cert_file -> client_cert - key_file -> client_key * ingate: verify_ssl -> validate_certs * manageiq: standardize tls params - verify_ssl -> validate_certs - ca_bundle_path -> ca_cert * mysql: standardize tls params - ssl_ca -> ca_cert - ssl_cert -> client_cert - ssl_key -> client_key * nios: ssl_verify -> validate_certs * postgresql: ssl_rootcert -> ca_cert * rabbitmq: standardize tls params - cacert -> ca_cert - cert -> client_cert - key -> client_key * rackspace: verify_ssl -> validate_certs * vca: verify_certs -> validate_certs * kubevirt_cdi_upload: upload_host_verify_ssl -> upload_host_validate_certs * lxd: standardize tls params - key_file -> client_key - cert_file -> client_cert * get_certificate: ca_certs -> ca_cert * get_certificate.py: clarify one or more certs in a file Co-Authored-By: jamescassell <code@james.cassell.me> * zabbix: tls_issuer -> ca_cert * bigip_device_auth_ldap: standardize tls params - ssl_check_peer -> validate_certs - ssl_client_cert -> client_cert - ssl_client_key -> client_key - ssl_ca_cert -> ca_cert * vdirect: vdirect_validate_certs -> validate_certs * mqtt: standardize tls params - ca_certs -> ca_cert - certfile -> client_cert - keyfile -> client_key * pulp_repo: standardize tls params remove `importer_ssl` prefix * rhn_register: sslcacert -> ca_cert * yum_repository: standardize tls params The fix for yum_repository is not straightforward since this module is only a thin wrapper for the underlying commands and config. In this case, we add the new values as aliases, keeping the old as primary, only due to the internal structure of the module. Aliases added: - sslcacert -> ca_cert - sslclientcert -> client_cert - sslclientkey -> client_key - sslverify -> validate_certs * gitlab_hook: enable_ssl_verification -> hook_validate_certs * Adjust arguments for docker_swarm inventory plugin. * foreman callback: standardize tls params - ssl_cert -> client_cert - ssl_key -> client_key * grafana_annotations: validate_grafana_certs -> validate_certs * nrdp callback: validate_nrdp_certs -> validate_certs * kubectl connection: standardize tls params - kubectl_cert_file -> client_cert - kubectl_key_file -> client_key - kubectl_ssl_ca_cert -> ca_cert - kubectl_verify_ssl -> validate_certs * oc connection: standardize tls params - oc_cert_file -> client_cert - oc_key_file -> client_key - oc_ssl_ca_cert -> ca_cert - oc_verify_ssl -> validate_certs * psrp connection: cert_trust_path -> ca_cert TODO: cert_validation -> validate_certs (multi-valued vs bool) * k8s inventory: standardize tls params - cert_file -> client_cert - key_file -> client_key - ca_cert -> ca_cert - verify_ssl -> validate_certs * openshift inventory: standardize tls params - cert_file -> client_cert - key_file -> client_key - ca_cert -> ca_cert - verify_ssl -> validate_certs * tower inventory: verify_ssl -> validate_certs * hashi_vault lookup: cacert -> ca_cert * k8s lookup: standardize tls params - cert_file -> client_cert - key_file -> client_key - ca_cert -> ca_cert - verify_ssl -> validate_certs * laps_passord lookup: cacert_file -> ca_cert * changelog for TLS parameter standardization
1017 lines
33 KiB
Python
1017 lines
33 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
|
|
|
|
ANSIBLE_METADATA = {
|
|
'metadata_version': '1.1',
|
|
'status': ['preview'],
|
|
'supported_by': 'community'
|
|
}
|
|
|
|
DOCUMENTATION = r'''
|
|
---
|
|
module: postgresql_info
|
|
short_description: Gather information about PostgreSQL servers
|
|
description:
|
|
- Gathers information about PostgreSQL servers.
|
|
version_added: "2.8"
|
|
options:
|
|
filter:
|
|
description:
|
|
- Limit the collected information by comma separated string or YAML list.
|
|
- Allowable values are C(version),
|
|
C(databases), 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
|
|
db:
|
|
description:
|
|
- Name of database to connect.
|
|
type: str
|
|
aliases:
|
|
- login_db
|
|
port:
|
|
description:
|
|
- Database port to connect.
|
|
type: int
|
|
default: 5432
|
|
aliases:
|
|
- login_port
|
|
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
|
|
login_user:
|
|
description:
|
|
- User (role) used to authenticate with PostgreSQL.
|
|
type: str
|
|
default: postgres
|
|
login_password:
|
|
description:
|
|
- Password used to authenticate with PostgreSQL.
|
|
type: str
|
|
login_host:
|
|
description:
|
|
- Host running PostgreSQL.
|
|
type: str
|
|
login_unix_socket:
|
|
description:
|
|
- Path to a Unix domain socket for local connections.
|
|
type: str
|
|
ssl_mode:
|
|
description:
|
|
- Determines whether or with what priority a secure SSL TCP/IP connection
|
|
will be negotiated with the server.
|
|
- See U(https://www.postgresql.org/docs/current/static/libpq-ssl.html) for
|
|
more information on the modes.
|
|
- Default of C(prefer) matches libpq default.
|
|
type: str
|
|
choices: [ allow, disable, prefer, require, verify-ca, verify-full ]
|
|
default: prefer
|
|
ca_cert:
|
|
description:
|
|
- Specifies the name of a file containing SSL certificate authority (CA)
|
|
certificate(s).
|
|
- If the file exists, the server's certificate will be
|
|
verified to be signed by one of these authorities.
|
|
type: str
|
|
aliases: [ ssl_rootcert ]
|
|
notes:
|
|
- The default authentication assumes that you are either logging in as or
|
|
sudo'ing to the postgres account on the host.
|
|
- login_user or session_role must be able to read from pg_authid.
|
|
- To avoid "Peer authentication failed for user postgres" error,
|
|
use postgres user as a I(become_user).
|
|
- This module uses psycopg2, a Python PostgreSQL database adapter. You must
|
|
ensure that psycopg2 is installed on the host before using this module. If
|
|
the remote host is the PostgreSQL server (which is the default case), then
|
|
PostgreSQL must also be installed on the remote host. For Ubuntu-based
|
|
systems, install the postgresql, libpq-dev, and python-psycopg2 packages
|
|
on the remote host before using this module.
|
|
requirements: [ psycopg2 ]
|
|
author:
|
|
- Andrew Klychkov (@Andersson007)
|
|
'''
|
|
|
|
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
|
|
postgresql_info:
|
|
filter: ver*,ext*
|
|
|
|
- name: Collect all info except settings and roles
|
|
become: yes
|
|
become_user: postgres
|
|
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
|
|
postgresql_info:
|
|
db: postgres
|
|
filter:
|
|
- tablesp*
|
|
- repl_sl*
|
|
|
|
- name: Collect all info except databases
|
|
become: yes
|
|
become_user: postgres
|
|
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
|
|
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
|
|
repl_slots:
|
|
description:
|
|
- Replication slots (available in 9.4 and later)
|
|
U(https://www.postgresql.org/docs/current/catalog-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:
|
|
import psycopg2
|
|
HAS_PSYCOPG2 = True
|
|
except ImportError:
|
|
HAS_PSYCOPG2 = False
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.database import SQLParseError
|
|
from ansible.module_utils.postgres import postgres_common_argument_spec
|
|
from ansible.module_utils._text import to_native
|
|
from ansible.module_utils.six import iteritems
|
|
|
|
|
|
# ===========================================
|
|
# PostgreSQL module specific support methods.
|
|
#
|
|
|
|
class PgDbConn(object):
|
|
def __init__(self, module, params_dict, session_role):
|
|
self.params_dict = params_dict
|
|
self.module = module
|
|
self.db_conn = None
|
|
self.session_role = session_role
|
|
self.cursor = None
|
|
|
|
def connect(self):
|
|
try:
|
|
self.db_conn = psycopg2.connect(**self.params_dict)
|
|
self.cursor = self.db_conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
|
|
|
# Switch role, if specified:
|
|
if self.session_role:
|
|
try:
|
|
self.cursor.execute('SET ROLE %s' % self.session_role)
|
|
except Exception as e:
|
|
self.module.fail_json(msg="Could not switch role: %s" % to_native(e))
|
|
|
|
return self.cursor
|
|
|
|
except TypeError as e:
|
|
if 'sslrootcert' in e.args[0]:
|
|
self.module.fail_json(msg='PostgreSQL server must be at least version 8.4 '
|
|
'to support sslrootcert')
|
|
self.module.fail_json(msg="Unable to connect to database: %s" % to_native(e))
|
|
|
|
except Exception as e:
|
|
self.module.fail_json(msg="Unable to connect to database: %s" % to_native(e))
|
|
|
|
def reconnect(self, dbname):
|
|
self.db_conn.close()
|
|
|
|
self.params_dict['database'] = dbname
|
|
return self.connect()
|
|
|
|
|
|
class PgClusterInfo(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": {},
|
|
"tablespaces": {},
|
|
"databases": {},
|
|
"replications": {},
|
|
"repl_slots": {},
|
|
"settings": {},
|
|
"roles": {},
|
|
"pending_restart_settings": [],
|
|
}
|
|
|
|
def collect(self, val_list=False):
|
|
subset_map = {
|
|
"version": self.get_pg_version,
|
|
"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_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):
|
|
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_db_info(self):
|
|
# 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],
|
|
)
|
|
|
|
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()
|
|
|
|
self.pg_info["databases"] = db_dict
|
|
|
|
def __get_pretty_val(self, setting):
|
|
return self.__exec_sql("SHOW %s" % setting)[0][0]
|
|
|
|
def __exec_sql(self, query):
|
|
try:
|
|
self.cursor.execute(query)
|
|
res = self.cursor.fetchall()
|
|
if res:
|
|
return res
|
|
except SQLParseError as e:
|
|
self.module.fail_json(msg=to_native(e))
|
|
self.cursor.close()
|
|
except psycopg2.ProgrammingError 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']),
|
|
port=dict(type='int', default=5432, aliases=['login_port']),
|
|
filter=dict(type='list'),
|
|
ssl_mode=dict(type='str', default='prefer', choices=['allow', 'disable', 'prefer', 'require', 'verify-ca', 'verify-full']),
|
|
ca_cert=dict(type='str', aliases=['ssl_rootcert']),
|
|
session_role=dict(type='str'),
|
|
)
|
|
module = AnsibleModule(
|
|
argument_spec=argument_spec,
|
|
supports_check_mode=True,
|
|
)
|
|
|
|
if not HAS_PSYCOPG2:
|
|
module.fail_json(msg="The python psycopg2 module is required")
|
|
|
|
filter_ = module.params["filter"]
|
|
sslrootcert = module.params["ca_cert"]
|
|
session_role = module.params["session_role"]
|
|
|
|
# To use defaults values, keyword arguments must be absent, so
|
|
# check which values are empty and don't include in the **kw
|
|
# dictionary
|
|
params_map = {
|
|
"login_host": "host",
|
|
"login_user": "user",
|
|
"login_password": "password",
|
|
"port": "port",
|
|
"db": "database",
|
|
"ssl_mode": "sslmode",
|
|
"ca_cert": "sslrootcert"
|
|
}
|
|
kw = dict((params_map[k], v) for (k, v) in iteritems(module.params)
|
|
if k in params_map and v != "" and v is not None)
|
|
|
|
# If a login_unix_socket is specified, incorporate it here.
|
|
is_localhost = "host" not in kw or kw["host"] == "" or kw["host"] == "localhost"
|
|
if is_localhost and module.params["login_unix_socket"] != "":
|
|
kw["host"] = module.params["login_unix_socket"]
|
|
|
|
if psycopg2.__version__ < '2.4.3' and sslrootcert:
|
|
module.fail_json(msg='psycopg2 must be at least 2.4.3 in order '
|
|
'to user the ca_cert parameter')
|
|
|
|
db_conn_obj = PgDbConn(module, kw, session_role)
|
|
|
|
# Do job:
|
|
pg_info = PgClusterInfo(module, db_conn_obj)
|
|
|
|
module.exit_json(**pg_info.collect(filter_))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|