postgresql_ext: Update param handling, fix doc formatting, added: CI tests, examples, a return value (#54027)

* postgresql_ext: instead_of_3196, initial

* postgresql_ext: fixes

* postgresql_ext: fixes
pull/54244/head
Andrey Klychkov 6 years ago committed by John R Barker
parent ab47142fa0
commit 76f1f96163

@ -1,130 +1,174 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright: Ansible Project # Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1', ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'], 'status': ['preview'],
'supported_by': 'community'} 'supported_by': 'community'}
DOCUMENTATION = r'''
DOCUMENTATION = '''
--- ---
module: postgresql_ext module: postgresql_ext
short_description: Add or remove PostgreSQL extensions from a database. short_description: Add or remove PostgreSQL extensions from a database
description: description:
- Add or remove PostgreSQL extensions from a database. - Add or remove PostgreSQL extensions from a database.
version_added: "1.9" version_added: '1.9'
options: options:
name: name:
description: description:
- name of the extension to add or remove - Name of the extension to add or remove.
required: true required: true
type: str
db: db:
description: description:
- name of the database to add or remove the extension to/from - Name of the database to add or remove the extension to/from.
required: true required: true
type: str
aliases:
- login_db
schema: schema:
description: description:
- name of the schema to add the extension to - Name of the schema to add the extension to.
version_added: "2.8" version_added: '2.8'
type: str
login_user: login_user:
description: description:
- The username used to authenticate with - The username used to authenticate with.
type: str
login_password: login_password:
description: description:
- The password used to authenticate with - The password used to authenticate with.
type: str
login_host: login_host:
description: description:
- Host running the database - Host running the database.
type: str
default: localhost default: localhost
login_unix_socket: login_unix_socket:
description: description:
- Path to a Unix domain socket for local connections. - Path to a Unix domain socket for local connections.
type: str
version_added: '2.8' version_added: '2.8'
ssl_mode: ssl_mode:
description: description:
- Determines whether or with what priority a secure SSL TCP/IP connection - Determines whether or with what priority a secure SSL TCP/IP connection
will be negotiated with the server. will be negotiated with the server.
- See U(https://www.postgresql.org/docs/current/static/libpq-ssl.html) for - See U(https://www.postgresql.org/docs/current/static/libpq-ssl.html) for
more information on the modes. more information on the modes.
- Default of C(prefer) matches libpq default. - Default of C(prefer) matches libpq default.
default: prefer default: prefer
choices: ["disable", "allow", "prefer", "require", "verify-ca", "verify-full"] choices: [allow, disable, prefer, require, verify-ca, verify-full]
type: str
version_added: '2.8' version_added: '2.8'
ssl_rootcert: ssl_rootcert:
description: description:
- Specifies the name of a file containing SSL certificate authority (CA) - Specifies the name of a file containing SSL certificate authority (CA)
certificate(s). If the file exists, the server's certificate will be certificate(s). If the file exists, the server's certificate will be
verified to be signed by one of these authorities. verified to be signed by one of these authorities.
type: path
version_added: '2.8' version_added: '2.8'
port: port:
description: description:
- Database port to connect to. - Database port to connect to.
default: 5432 default: 5432
type: int
session_role: session_role:
version_added: "2.8" description:
description: | - Switch to session_role after connecting.
Switch to session_role after connecting. The specified session_role must be a role that the current login_user is a member of. - 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. - Permissions checking for SQL commands is carried out as though the session_role were the one that had logged in originally.
type: str
version_added: '2.8'
state: state:
description: description:
- The database extension state - The database extension state.
default: present default: present
choices: [ "present", "absent" ] choices: [ absent, present ]
type: str
cascade: cascade:
description: description:
- Automatically install/remove any extensions that this extension depends on - Automatically install/remove any extensions that this extension depends on
that are not already installed/removed (supported since PostgreSQL 9.6). that are not already installed/removed (supported since PostgreSQL 9.6).
type: bool type: bool
default: no default: no
version_added: '2.8' version_added: '2.8'
notes: notes:
- The default authentication assumes that you are either logging in as or sudo'ing to the C(postgres) account on the host. - The default authentication assumes that you are either logging in as
- This module uses I(psycopg2), a Python PostgreSQL database adapter. You must ensure that psycopg2 is installed on or sudo'ing to the C(postgres) account on the host.
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 - This module uses I(psycopg2), a Python PostgreSQL database adapter.
on the remote host. For Ubuntu-based systems, install the C(postgresql), C(libpq-dev), and C(python-psycopg2) packages on the remote host before using - You must ensure that psycopg2 is installed on the host before using this module.
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 C(postgresql), C(libpq-dev),
and C(python-psycopg2) packages on the remote host before using this module.
requirements: [ psycopg2 ] requirements: [ psycopg2 ]
author: author:
- "Daniel Schep (@dschep)" - Daniel Schep (@dschep)
- "Thomas O'Donnell (@andytom)" - Thomas O'Donnell (@andytom)
''' '''
EXAMPLES = ''' EXAMPLES = r'''
# Adds postgis to the database "acme" - name: Adds postgis extension to the database acme in the schema foo
- postgresql_ext: postgresql_ext:
name: postgis
db: acme
schema: foo
- name: Removes postgis extension to the database acme
postgresql_ext:
name: postgis name: postgis
db: acme db: acme
schema: extensions state: absent
# Adds earthdistance to the database "template1" - name: Adds earthdistance extension to the database template1 cascade
- postgresql_ext: postgresql_ext:
name: earthdistance name: earthdistance
db: template1 db: template1
cascade: true cascade: true
# In the example below, if earthdistance extension is installed,
# it will be removed too because it depends on cube:
- name: Removes cube extension from the database acme cascade
postgresql_ext:
name: cube
db: acme
cascade: yes
state: absent
'''
RETURN = r'''
query:
description: List of executed queries.
returned: always
type: list
sample: ["DROP EXTENSION \"acme\""]
''' '''
import traceback import traceback
PSYCOPG2_IMP_ERR = None PSYCOPG2_IMP_ERR = None
try: try:
import psycopg2 import psycopg2
import psycopg2.extras import psycopg2.extras
HAS_PSYCOPG2 = True
except ImportError: except ImportError:
PSYCOPG2_IMP_ERR = traceback.format_exc() PSYCOPG2_IMP_ERR = traceback.format_exc()
postgresqldb_found = False HAS_PSYCOPG2 = False
else:
postgresqldb_found = True
from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.postgres import postgres_common_argument_spec
from ansible.module_utils.six import iteritems from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_native from ansible.module_utils._text import to_native
from ansible.module_utils.database import pg_quote_identifier from ansible.module_utils.database import pg_quote_identifier
executed_queries = []
class NotSupportedError(Exception): class NotSupportedError(Exception):
pass pass
@ -146,6 +190,7 @@ def ext_delete(cursor, ext, cascade):
if cascade: if cascade:
query += " CASCADE" query += " CASCADE"
cursor.execute(query) cursor.execute(query)
executed_queries.append(query)
return True return True
else: else:
return False return False
@ -159,6 +204,7 @@ def ext_create(cursor, ext, schema, cascade):
if cascade: if cascade:
query += " CASCADE" query += " CASCADE"
cursor.execute(query) cursor.execute(query)
executed_queries.append(query)
return True return True
else: else:
return False return False
@ -169,27 +215,26 @@ def ext_create(cursor, ext, schema, cascade):
def main(): def main():
argument_spec = postgres_common_argument_spec()
argument_spec.update(
db=dict(type="str", required=True, aliases=["login_db"]),
port=dict(type="int", default=5432, aliases=["login_port"]),
ext=dict(type="str", required=True, aliases=['name']),
schema=dict(type="str"),
state=dict(type="str", default="present", choices=["absent", "present"]),
cascade=dict(type='bool', default=False),
ssl_mode=dict(type='str', default='prefer', choices=[
'disable', 'allow', 'prefer', 'require', 'verify-ca', 'verify-full']),
ssl_rootcert=dict(type="path", default=None),
session_role=dict(type="str"),
)
module = AnsibleModule( module = AnsibleModule(
argument_spec=dict( argument_spec=argument_spec,
login_user=dict(default="postgres"), supports_check_mode=True,
login_password=dict(default="", no_log=True),
login_host=dict(default=""),
login_unix_socket=dict(default=""),
port=dict(default="5432"),
db=dict(required=True),
ext=dict(required=True, aliases=['name']),
schema=dict(default=""),
state=dict(default="present", choices=["absent", "present"]),
cascade=dict(type='bool', default=False),
ssl_mode=dict(default='prefer', choices=[
'disable', 'allow', 'prefer', 'require', 'verify-ca', 'verify-full']),
ssl_rootcert=dict(default=None),
session_role=dict(),
),
supports_check_mode=True
) )
if not postgresqldb_found: if not HAS_PSYCOPG2:
module.fail_json(msg=missing_required_lib('psycopg2'), exception=PSYCOPG2_IMP_ERR) module.fail_json(msg=missing_required_lib('psycopg2'), exception=PSYCOPG2_IMP_ERR)
db = module.params["db"] db = module.params["db"]
@ -233,8 +278,7 @@ def main():
db_connection.set_isolation_level(psycopg2 db_connection.set_isolation_level(psycopg2
.extensions .extensions
.ISOLATION_LEVEL_AUTOCOMMIT) .ISOLATION_LEVEL_AUTOCOMMIT)
cursor = db_connection.cursor( cursor = db_connection.cursor(cursor_factory=psycopg2.extras.DictCursor)
cursor_factory=psycopg2.extras.DictCursor)
except TypeError as e: except TypeError as e:
if 'sslrootcert' in e.args[0]: if 'sslrootcert' in e.args[0]:
@ -268,7 +312,7 @@ def main():
except Exception as e: except Exception as e:
module.fail_json(msg="Database query failed: %s" % to_native(e), exception=traceback.format_exc()) module.fail_json(msg="Database query failed: %s" % to_native(e), exception=traceback.format_exc())
module.exit_json(changed=changed, db=db, ext=ext) module.exit_json(changed=changed, db=db, ext=ext, queries=executed_queries)
if __name__ == '__main__': if __name__ == '__main__':

@ -784,13 +784,23 @@
# Test postgresql_privs # Test postgresql_privs
- include: postgresql_privs.yml - include: postgresql_privs.yml
# Test postgresql_facts module: # Test postgresql_facts module
- include: postgresql_facts.yml - include: postgresql_facts.yml
# Test default_privs with target_role # Test default_privs with target_role
- include: test_target_role.yml - include: test_target_role.yml
when: postgres_version_resp.stdout is version('9.1', '>=') when: postgres_version_resp.stdout is version('9.1', '>=')
# Test postgresql_ext.
# pg_extension system view is available from PG 9.1.
# The tests are restricted by Fedora because there will be errors related with
# attempts to change the environment during postgis installation or
# missing postgis package in repositories.
# Anyway, these tests completely depend on Postgres version,
# not specific distributions.
- include: postgresql_ext.yml
when: postgres_version_resp.stdout is version('9.1', '>=') and ansible_distribution == 'Fedora'
# dump/restore tests per format # dump/restore tests per format
# ============================================================ # ============================================================
- include: state_dump_restore.yml test_fixture=user file=dbdata.sql - include: state_dump_restore.yml test_fixture=user file=dbdata.sql

@ -0,0 +1,227 @@
# 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)
# Create test schema:
- name: postgresql_ext - install postgis
package: name=postgis state=present
when: ansible_os_family != "Windows"
- name: postgresql_ext - install postgis RedHat
win_package: name=postgis state=present
when: ansible_os_family == "Windows"
- name: postgresql_ext - create schema schema1
become_user: "{{ pg_user }}"
become: yes
postgresql_schema:
database: postgres
name: schema1
state: present
- name: postgresql_ext - drop extension if exists
become_user: "{{ pg_user }}"
become: yes
postgresql_query:
db: postgres
query: "DROP EXTENSION IF EXISTS postgis"
ignore_errors: yes
##############
# Start tests:
# Create extension in check_mode, also check aliases for db and port params:
- name: postgresql_ext - create extension postgis in check_mode
become_user: "{{ pg_user }}"
become: yes
postgresql_ext:
login_db: postgres
login_port: 5432
name: postgis
check_mode: yes
ignore_errors: yes
register: result
- assert:
that:
- result.changed == true
- result.queries == []
# Check that extension doesn't exist after the previous step, rowcount must be 0
- name: postgresql_ext - check that extension doesn't exist after the previous step
become_user: "{{ pg_user }}"
become: yes
postgresql_query:
db: postgres
query: "SELECT extname FROM pg_extension WHERE extname='postgis'"
ignore_errors: yes
register: result
- assert:
that:
- result.rowcount == 0
# Create extension postgis, also check aliases for db and port params
- name: postgresql_ext - create extension postgis
become_user: "{{ pg_user }}"
become: yes
postgresql_ext:
login_db: postgres
login_port: 5432
name: postgis
ignore_errors: yes
register: result
- assert:
that:
- result.changed == true
- result.queries == ['CREATE EXTENSION "postgis"']
# Check that extension exists after the previous step, rowcount must be 1
- name: postgresql_ext - check that extension exists after the previous step
become_user: "{{ pg_user }}"
become: yes
postgresql_query:
db: postgres
query: "SELECT extname FROM pg_extension WHERE extname='postgis'"
ignore_errors: yes
register: result
- assert:
that:
- result.rowcount == 1
# Drop extension postgis:
- name: postgresql_ext - drop extension postgis
become_user: "{{ pg_user }}"
become: yes
postgresql_ext:
db: postgres
name: postgis
state: absent
ignore_errors: yes
register: result
- assert:
that:
- result.changed == true
- result.queries == ['DROP EXTENSION "postgis"']
# Check that extension doesn't exist after the previous step, rowcount must be 0
- name: postgresql_ext - check that extension doesn't exist after the previous step
become_user: "{{ pg_user }}"
become: yes
postgresql_query:
db: postgres
query: "SELECT extname FROM pg_extension WHERE extname='postgis'"
ignore_errors: yes
register: result
- assert:
that:
- result.rowcount == 0
# Create extension postgis in particular schema
- name: postgresql_ext - create extension postgis
become_user: "{{ pg_user }}"
become: yes
postgresql_ext:
db: postgres
name: postgis
schema: schema1
ignore_errors: yes
register: result
- assert:
that:
- result.changed == true
- result.queries == ['CREATE EXTENSION "postgis" WITH SCHEMA "schema1"']
# Check that extension exists after the previous step, rowcount must be 1
- name: postgresql_ext - check that extension exists after the previous step
become_user: "{{ pg_user }}"
become: yes
postgresql_query:
db: postgres
query: |
SELECT extname FROM pg_extension AS e LEFT JOIN pg_catalog.pg_namespace AS n
ON n.oid = e.extnamespace WHERE e.extname='postgis' AND n.nspname='schema1'
ignore_errors: yes
register: result
- assert:
that:
- result.rowcount == 1
#
# Check cascade option. For creation it's available from PG 9.6.
# I couldn't check it for two or more extension in one time
# because most of the common extensions are available in postgresql-contrib package
# that tries to change the default python interpreter and fails during tests respectively.
# Anyway, that's enough to be sure that the proper SQL was exequted.
#
# Drop extension cascade
- name: postgresql_ext - drop extension postgis cascade
become_user: "{{ pg_user }}"
become: yes
postgresql_ext:
db: postgres
name: postgis
state: absent
cascade: yes
ignore_errors: yes
register: result
- assert:
that:
- result.changed == true
- result.queries == ['DROP EXTENSION "postgis" CASCADE']
# Check that extension doesn't exist after the previous step, rowcount must be 0
- name: postgresql_ext - check that extension doesn't exist after the previous step
become_user: "{{ pg_user }}"
become: yes
postgresql_query:
db: postgres
query: "SELECT extname FROM pg_extension WHERE extname='postgis'"
ignore_errors: yes
register: result
- assert:
that:
- result.rowcount == 0
# Create extension postgis cascade.
# CASCADE for CREATE command is available from PG 9.6
- name: postgresql_ext - create extension postgis cascade
become_user: "{{ pg_user }}"
become: yes
postgresql_ext:
db: postgres
name: postgis
cascade: yes
ignore_errors: yes
register: result
when: postgres_version_resp.stdout is version('9.6', '<=')
- assert:
that:
- result.changed == true
- result.queries == ['CREATE EXTENSION "postgis" CASCADE"']
when: postgres_version_resp.stdout is version('9.6', '<=')
# Check that extension exists after the previous step, rowcount must be 1
- name: postgresql_ext - check that extension exists after the previous step
become_user: "{{ pg_user }}"
become: yes
postgresql_query:
db: postgres
query: "SELECT extname FROM pg_extension WHERE extname='postgis'"
ignore_errors: yes
register: result
when: postgres_version_resp.stdout is version('9.6', '<=')
- assert:
that:
- result.rowcount == 1
when: postgres_version_resp.stdout is version('9.6', '<=')
Loading…
Cancel
Save