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
This commit is contained in:
Andrey Klychkov 2019-03-22 15:39:33 +03:00 committed by John R Barker
parent ab47142fa0
commit 76f1f96163
3 changed files with 352 additions and 71 deletions

View file

@ -1,130 +1,174 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: Ansible Project
# 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 = '''
DOCUMENTATION = r'''
---
module: postgresql_ext
short_description: Add or remove PostgreSQL extensions from a database.
short_description: Add or remove PostgreSQL extensions from a database
description:
- Add or remove PostgreSQL extensions from a database.
version_added: "1.9"
- Add or remove PostgreSQL extensions from a database.
version_added: '1.9'
options:
name:
description:
- name of the extension to add or remove
- Name of the extension to add or remove.
required: true
type: str
db:
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
type: str
aliases:
- login_db
schema:
description:
- name of the schema to add the extension to
version_added: "2.8"
- Name of the schema to add the extension to.
version_added: '2.8'
type: str
login_user:
description:
- The username used to authenticate with
- The username used to authenticate with.
type: str
login_password:
description:
- The password used to authenticate with
- The password used to authenticate with.
type: str
login_host:
description:
- Host running the database
- Host running the database.
type: str
default: localhost
login_unix_socket:
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'
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.
- 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.
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'
ssl_rootcert:
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.
- 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: path
version_added: '2.8'
port:
description:
- Database port to connect to.
- Database port to connect to.
default: 5432
type: int
session_role:
version_added: "2.8"
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.
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
version_added: '2.8'
state:
description:
- The database extension state
- The database extension state.
default: present
choices: [ "present", "absent" ]
choices: [ absent, present ]
type: str
cascade:
description:
- Automatically install/remove any extensions that this extension depends on
that are not already installed/removed (supported since PostgreSQL 9.6).
- Automatically install/remove any extensions that this extension depends on
that are not already installed/removed (supported since PostgreSQL 9.6).
type: bool
default: no
version_added: '2.8'
notes:
- The default authentication assumes that you are either logging in as or sudo'ing to the C(postgres) account on the host.
- This module uses I(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 C(postgresql), C(libpq-dev), and C(python-psycopg2) packages on the remote host before using
this module.
- The default authentication assumes that you are either logging in as
or sudo'ing to the C(postgres) account on the host.
- This module uses I(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 C(postgresql), C(libpq-dev),
and C(python-psycopg2) packages on the remote host before using this module.
requirements: [ psycopg2 ]
author:
- "Daniel Schep (@dschep)"
- "Thomas O'Donnell (@andytom)"
- Daniel Schep (@dschep)
- Thomas O'Donnell (@andytom)
'''
EXAMPLES = '''
# Adds postgis to the database "acme"
- postgresql_ext:
EXAMPLES = r'''
- name: Adds postgis extension to the database acme in the schema foo
postgresql_ext:
name: postgis
db: acme
schema: extensions
schema: foo
# Adds earthdistance to the database "template1"
- postgresql_ext:
- name: Removes postgis extension to the database acme
postgresql_ext:
name: postgis
db: acme
state: absent
- name: Adds earthdistance extension to the database template1 cascade
postgresql_ext:
name: earthdistance
db: template1
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
PSYCOPG2_IMP_ERR = None
try:
import psycopg2
import psycopg2.extras
HAS_PSYCOPG2 = True
except ImportError:
PSYCOPG2_IMP_ERR = traceback.format_exc()
postgresqldb_found = False
else:
postgresqldb_found = True
HAS_PSYCOPG2 = False
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._text import to_native
from ansible.module_utils.database import pg_quote_identifier
executed_queries = []
class NotSupportedError(Exception):
pass
@ -146,6 +190,7 @@ def ext_delete(cursor, ext, cascade):
if cascade:
query += " CASCADE"
cursor.execute(query)
executed_queries.append(query)
return True
else:
return False
@ -159,6 +204,7 @@ def ext_create(cursor, ext, schema, cascade):
if cascade:
query += " CASCADE"
cursor.execute(query)
executed_queries.append(query)
return True
else:
return False
@ -169,27 +215,26 @@ def ext_create(cursor, ext, schema, cascade):
def main():
module = AnsibleModule(
argument_spec=dict(
login_user=dict(default="postgres"),
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
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"),
)
if not postgresqldb_found:
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
if not HAS_PSYCOPG2:
module.fail_json(msg=missing_required_lib('psycopg2'), exception=PSYCOPG2_IMP_ERR)
db = module.params["db"]
@ -233,8 +278,7 @@ def main():
db_connection.set_isolation_level(psycopg2
.extensions
.ISOLATION_LEVEL_AUTOCOMMIT)
cursor = db_connection.cursor(
cursor_factory=psycopg2.extras.DictCursor)
cursor = db_connection.cursor(cursor_factory=psycopg2.extras.DictCursor)
except TypeError as e:
if 'sslrootcert' in e.args[0]:
@ -268,7 +312,7 @@ def main():
except Exception as e:
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__':

View file

@ -784,13 +784,23 @@
# Test postgresql_privs
- include: postgresql_privs.yml
# Test postgresql_facts module:
# Test postgresql_facts module
- include: postgresql_facts.yml
# Test default_privs with target_role
- include: test_target_role.yml
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
# ============================================================
- include: state_dump_restore.yml test_fixture=user file=dbdata.sql

View file

@ -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', '<=')