From 48ec8d13a0f5b6a21873c596f46ab7a5868eb715 Mon Sep 17 00:00:00 2001 From: Andrey Klychkov Date: Mon, 1 Apr 2019 16:39:00 +0300 Subject: [PATCH] postgresql_lang module: fixed doc formatting, added aliases, pg_utils, added missing CI tests (#54672) * postgresql_lang: fixed doc * postgresql_lang: add pg_utils * postgresql_lang: added return value - queries * postgresql_lang: added CI tests * postgresql_lang: restricted CI using CentOS * postgresql_lang: fixed typos in CI comments --- .../database/postgresql/postgresql_lang.py | 230 ++++++++++------- .../targets/postgresql/tasks/main.yml | 11 +- .../postgresql/tasks/postgresql_lang.yml | 233 ++++++++++++++++++ 3 files changed, 379 insertions(+), 95 deletions(-) create mode 100644 test/integration/targets/postgresql/tasks/postgresql_lang.yml diff --git a/lib/ansible/modules/database/postgresql/postgresql_lang.py b/lib/ansible/modules/database/postgresql/postgresql_lang.py index ee78da727e7..4d3e29d29b9 100644 --- a/lib/ansible/modules/database/postgresql/postgresql_lang.py +++ b/lib/ansible/modules/database/postgresql/postgresql_lang.py @@ -12,186 +12,225 @@ ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} - -DOCUMENTATION = ''' +DOCUMENTATION = r''' --- module: postgresql_lang -short_description: Adds, removes or changes procedural languages with a PostgreSQL database. +short_description: Adds, removes or changes procedural languages with a PostgreSQL database description: - - Adds, removes or changes procedural languages with a PostgreSQL database. - - This module allows you to add a language, remote a language or change the trust - relationship with a PostgreSQL database. The module can be used on the machine - where executed or on a remote host. - - When removing a language from a database, it is possible that dependencies prevent - the database from being removed. In that case, you can specify casade to - automatically drop objects that depend on the language (such as functions in the - language). In case the language can't be deleted because it is required by the - database system, you can specify fail_on_drop=no to ignore the error. - - Be carefull when marking a language as trusted since this could be a potential - security breach. Untrusted languages allow only users with the PostgreSQL superuser - privilege to use this language to create new functions. -version_added: "1.7" +- Adds, removes or changes procedural languages with a PostgreSQL database. +- This module allows you to add a language, remote a language or change the trust + relationship with a PostgreSQL database. +- The module can be used on the machine where executed or on a remote host. +- When removing a language from a database, it is possible that dependencies prevent + the database from being removed. In that case, you can specify casade to + automatically drop objects that depend on the language (such as functions in the + language). +- In case the language can't be deleted because it is required by the + database system, you can specify fail_on_drop=no to ignore the error. +- Be carefull when marking a language as trusted since this could be a potential + security breach. Untrusted languages allow only users with the PostgreSQL superuser + privilege to use this language to create new functions. +- For more information about PostgreSQL languages see the official documentation + U(https://www.postgresql.org/docs/current/sql-createlanguage.html), + U(https://www.postgresql.org/docs/current/sql-alterlanguage.html), + U(https://www.postgresql.org/docs/current/sql-droplanguage.html). +version_added: '1.7' options: lang: description: - - name of the procedural language to add, remove or change + - Name of the procedural language to add, remove or change. required: true + type: str + aliases: + - name trust: description: - - make this language trusted for the selected db + - Make this language trusted for the selected db. type: bool default: 'no' db: description: - - name of database where the language will be added, removed or changed + - Name of database to connect to and where the language will be added, removed or changed. + type: str + aliases: + - login_db force_trust: description: - - marks the language as trusted, even if it's marked as untrusted in pg_pltemplate. - - use with care! + - Marks the language as trusted, even if it's marked as untrusted in pg_pltemplate. + - Use with care! type: bool default: 'no' fail_on_drop: description: - - if C(yes), fail when removing a language. Otherwise just log and continue - - in some cases, it is not possible to remove a language (used by the db-system). When dependencies block the removal, consider using C(cascade). + - If C(yes), fail when removing a language. Otherwise just log and continue. + - In some cases, it is not possible to remove a language (used by the db-system). + - When dependencies block the removal, consider using C(cascade). type: bool default: 'yes' cascade: description: - - when dropping a language, also delete object that depend on this language. - - only used when C(state=absent). + - When dropping a language, also delete object that depend on this language. + - Only used when C(state=absent). type: bool default: 'no' port: description: - - Database port to connect to. + - Database port to connect to. default: 5432 + type: int + aliases: + - login_port login_user: description: - - User used to authenticate with PostgreSQL + - User to authenticate with PostgreSQL. + type: str default: postgres login_password: description: - - Password used to authenticate with PostgreSQL (must match C(login_user)) + - Password used to authenticate with PostgreSQL (must match C(login_user)). + type: str login_host: description: - - Host running PostgreSQL where you want to execute the actions. + - Host running PostgreSQL where you want to execute the actions. + type: str default: localhost 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. + 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. + type: str state: description: - - The state of the language for the selected database + - The state of the language for the selected database. + type: str default: present - choices: [ "present", "absent" ] + choices: [ absent, present ] 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. + type: str default: prefer - choices: ["disable", "allow", "prefer", "require", "verify-ca", "verify-full"] + choices: [ allow, disable, prefer, require, verify-ca, verify-full ] version_added: '2.8' 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. + - 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 version_added: '2.8' 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. - - 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. +- The default authentication assumes that you are either logging in as or + sudo'ing to the postgres account on the host. +- 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: - - "Jens Depuydt (@jensdepuydt)" - - "Thomas O'Donnell (@andytom)" +- Jens Depuydt (@jensdepuydt) +- Thomas O'Donnell (@andytom) ''' -EXAMPLES = ''' -# Add language pltclu to database testdb if it doesn't exist: -- postgresql_lang db=testdb lang=pltclu state=present +EXAMPLES = r''' +- name: Add language pltclu to database testdb if it doesn't exist + postgresql_lang: db=testdb lang=pltclu state=present -# Add language pltclu to database testdb if it doesn't exist and mark it as trusted: -# Marks the language as trusted if it exists but isn't trusted yet +# Add language pltclu to database testdb if it doesn't exist and mark it as trusted. +# Marks the language as trusted if it exists but isn't trusted yet. # force_trust makes sure that the language will be marked as trusted -- postgresql_lang: +- name: Add language pltclu to database testdb if it doesn't exist and mark it as trusted + postgresql_lang: db: testdb lang: pltclu state: present trust: yes force_trust: yes -# Remove language pltclu from database testdb: -- postgresql_lang: +- name: Remove language pltclu from database testdb + postgresql_lang: db: testdb lang: pltclu state: absent -# Remove language pltclu from database testdb and remove all dependencies: -- postgresql_lang: +- name: Remove language pltclu from database testdb and remove all dependencies + postgresql_lang: db: testdb lang: pltclu state: absent cascade: yes -# Remove language c from database testdb but ignore errors if something prevents the removal: -- postgresql_lang: +- name: Remove language c from database testdb but ignore errors if something prevents the removal + postgresql_lang: db: testdb lang: pltclu state: absent fail_on_drop: no ''' + +RETURN = r''' +queries: + description: List of executed queries. + returned: always + type: list + sample: ['CREATE LANGUAGE "acme"'] + version_added: '2.8' +''' + import traceback PSYCOPG2_IMP_ERR = None try: import psycopg2 + postgresqldb_found = True except ImportError: PSYCOPG2_IMP_ERR = traceback.format_exc() postgresqldb_found = False -else: - postgresqldb_found = True 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 = [] + def lang_exists(cursor, lang): """Checks if language exists for db""" - query = "SELECT lanname FROM pg_language WHERE lanname='%s'" % lang + query = "SELECT lanname FROM pg_language WHERE lanname = '%s'" % lang cursor.execute(query) return cursor.rowcount > 0 def lang_istrusted(cursor, lang): """Checks if language is trusted for db""" - query = "SELECT lanpltrusted FROM pg_language WHERE lanname='%s'" % lang + query = "SELECT lanpltrusted FROM pg_language WHERE lanname = '%s'" % lang cursor.execute(query) return cursor.fetchone()[0] def lang_altertrust(cursor, lang, trust): """Changes if language is trusted for db""" - query = "UPDATE pg_language SET lanpltrusted = %s WHERE lanname=%s" - cursor.execute(query, (trust, lang)) + query = "UPDATE pg_language SET lanpltrusted = '%s' WHERE lanname = '%s'" % (trust, lang) + executed_queries.append(query) + cursor.execute(query) return True @@ -201,6 +240,7 @@ def lang_add(cursor, lang, trust): query = 'CREATE TRUSTED LANGUAGE "%s"' % lang else: query = 'CREATE LANGUAGE "%s"' % lang + executed_queries.append(query) cursor.execute(query) return True @@ -210,9 +250,11 @@ def lang_drop(cursor, lang, cascade): cursor.execute("SAVEPOINT ansible_pgsql_lang_drop") try: if cascade: - cursor.execute("DROP LANGUAGE \"%s\" CASCADE" % lang) + query = "DROP LANGUAGE \"%s\" CASCADE" % lang else: - cursor.execute("DROP LANGUAGE \"%s\"" % lang) + query = "DROP LANGUAGE \"%s\"" % lang + executed_queries.append(query) + cursor.execute(query) except Exception: cursor.execute("ROLLBACK TO SAVEPOINT ansible_pgsql_lang_drop") cursor.execute("RELEASE SAVEPOINT ansible_pgsql_lang_drop") @@ -222,26 +264,25 @@ def lang_drop(cursor, lang, cascade): 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"]), + lang=dict(type="str", required=True, aliases=["name"]), + state=dict(type="str", default="present", choices=["absent", "present"]), + trust=dict(type="bool", default="no"), + force_trust=dict(type="bool", default="no"), + cascade=dict(type="bool", default="no"), + fail_on_drop=dict(type="bool", default="yes"), + ssl_mode=dict(default='prefer', choices=[ + 'allow', 'disable', 'prefer', 'require', 'verify-ca', 'verify-full']), + ca_cert=dict(default=None, aliases=["ssl_rootcert"]), + session_role=dict(type="str"), + ) + 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=""), - db=dict(required=True), - port=dict(default='5432'), - lang=dict(required=True), - state=dict(default="present", choices=["absent", "present"]), - trust=dict(type='bool', default='no'), - force_trust=dict(type='bool', default='no'), - cascade=dict(type='bool', default='no'), - fail_on_drop=dict(type='bool', default='yes'), - ssl_mode=dict(default='prefer', choices=[ - 'disable', 'allow', 'prefer', 'require', 'verify-ca', 'verify-full']), - ca_cert=dict(default=None, aliases=['ssl_rootcert']), - session_role=dict(), - ), - supports_check_mode=True + argument_spec=argument_spec, + supports_check_mode=True, ) db = module.params["db"] @@ -336,6 +377,7 @@ def main(): db_connection.commit() kw['changed'] = changed + kw['queries'] = executed_queries module.exit_json(**kw) diff --git a/test/integration/targets/postgresql/tasks/main.yml b/test/integration/targets/postgresql/tasks/main.yml index 5a14400bcc7..e097b8e6ad0 100644 --- a/test/integration/targets/postgresql/tasks/main.yml +++ b/test/integration/targets/postgresql/tasks/main.yml @@ -839,9 +839,18 @@ # Test postgresql_membership module - include: postgresql_membership.yml -# postgresql_table module tests +# Test postgresql_table module - include: postgresql_table.yml +# Test postgresql_lang module. +# To implement tests, it needs to install some additional packages +# that may cause problems on different distributions, +# so I restricted the tests using CentOS because the results +# depend only on Postgres version +# (CentOS 6 repo contains the oldest PG version in these tests - 9.0): +- include: postgresql_lang.yml + when: ansible_distribution == 'CentOS' + # dump/restore tests per format # ============================================================ - include: state_dump_restore.yml test_fixture=user file=dbdata.sql diff --git a/test/integration/targets/postgresql/tasks/postgresql_lang.yml b/test/integration/targets/postgresql/tasks/postgresql_lang.yml new file mode 100644 index 00000000000..cbeb643dc14 --- /dev/null +++ b/test/integration/targets/postgresql/tasks/postgresql_lang.yml @@ -0,0 +1,233 @@ +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Preparation for tests: +- name: postgresql_lang - install lang plperl + become: yes + package: + name: "{{ item }}" + state: present + with_items: + - postgresql-plperl + - postgresql-plpython + +############### +# Do main tests +# + +# Create language in check_mode: +- name: postgresql_lang - create plperl in check_mode + become_user: "{{ pg_user }}" + become: yes + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: plperl + register: result + ignore_errors: yes + check_mode: yes + +- assert: + that: + - result.changed == true + - result.queries == [] + +- name: postgresql_lang - check that lang doesn't exist after previous step, rowcount must be 0 + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_language WHERE lanname = 'plperl'" + register: result + +- assert: + that: + - result.rowcount == 0 + +# Create language: +- name: postgresql_lang - create plperl + become_user: "{{ pg_user }}" + become: yes + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: plperl + register: result + ignore_errors: yes + +- assert: + that: + - result.changed == true + - result.queries == ['CREATE LANGUAGE "plperl"'] + +- name: postgresql_lang - check that lang exists after previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_language WHERE lanname = 'plperl'" + register: result + +- assert: + that: + - result.rowcount == 1 + +# Drop language in check_mode: +- name: postgresql_lang - drop plperl in check_mode + become_user: "{{ pg_user }}" + become: yes + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: plperl + state: absent + register: result + ignore_errors: yes + check_mode: yes + +- assert: + that: + - result.changed == true + - result.queries == [] + +- name: postgresql_lang - check that lang exists after previous step, rowcount must be 1 + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_language WHERE lanname = 'plperl'" + register: result + +- assert: + that: + - result.rowcount == 1 + +# Drop language: +- name: postgresql_lang - drop plperl + become_user: "{{ pg_user }}" + become: yes + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: plperl + state: absent + register: result + ignore_errors: yes + +- assert: + that: + - result.changed == true + - result.queries == ['DROP LANGUAGE "plperl"'] + +- name: postgresql_lang - check that lang doesn't exist after previous step, rowcount must be 0 + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_language WHERE lanname = 'plperl'" + register: result + +- assert: + that: + - result.rowcount == 0 + +# Check fail_on_drop yes +- name: postgresql_lang - drop c language to check fail_on_drop yes + become_user: "{{ pg_user }}" + become: yes + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: c + state: absent + fail_on_drop: yes + register: result + ignore_errors: yes + +- assert: + that: + - result.failed == true + +# Check fail_on_drop no +- name: postgresql_lang - drop c language to check fail_on_drop no + become_user: "{{ pg_user }}" + become: yes + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: c + state: absent + fail_on_drop: no + register: result + ignore_errors: yes + +- assert: + that: + - result.failed == false + +# Create trusted language: +- name: postgresql_lang - create plpythonu + become_user: "{{ pg_user }}" + become: yes + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: plpythonu + trust: yes + force_trust: yes + register: result + ignore_errors: yes + +- assert: + that: + - result.changed == true + - result.queries == ['CREATE TRUSTED LANGUAGE "plpythonu"', "UPDATE pg_language SET lanpltrusted = 'True' WHERE lanname = 'plpythonu'"] + +- name: postgresql_lang - check that lang exists and it's trusted after previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_language WHERE lanname = 'plpythonu' AND lanpltrusted = 't'" + register: result + +- assert: + that: + - result.rowcount == 1 + +# Drop language cascade, tests of aliases: +- name: postgresql_lang - drop plpythonu cascade + become_user: "{{ pg_user }}" + become: yes + postgresql_lang: + login_db: postgres + login_user: "{{ pg_user }}" + login_port: 5432 + lang: plpythonu + state: absent + cascade: yes + register: result + ignore_errors: yes + +- assert: + that: + - result.changed == true + - result.queries == ['DROP LANGUAGE "plpythonu" CASCADE'] + +- name: postgresql_lang - check that lang doesn't exist after previous step, rowcount must be 0 + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_language WHERE lanname = 'plpythonu'" + register: result + +- assert: + that: + - result.rowcount == 0