From 5cbac14469da45995602c4911b6315f4a0638514 Mon Sep 17 00:00:00 2001 From: Andrey Klychkov Date: Tue, 9 Apr 2019 15:32:09 +0300 Subject: [PATCH] New module postgresql_owner: change ownership and reassign all db objects owned by role to another (#54984) --- .../database/postgresql/postgresql_owner.py | 517 ++++++++ .../targets/postgresql/tasks/main.yml | 5 +- .../postgresql/tasks/postgresql_owner.yml | 1039 +++++++++++++++++ 3 files changed, 1560 insertions(+), 1 deletion(-) create mode 100644 lib/ansible/modules/database/postgresql/postgresql_owner.py create mode 100644 test/integration/targets/postgresql/tasks/postgresql_owner.yml diff --git a/lib/ansible/modules/database/postgresql/postgresql_owner.py b/lib/ansible/modules/database/postgresql/postgresql_owner.py new file mode 100644 index 00000000000..f81274fac6c --- /dev/null +++ b/lib/ansible/modules/database/postgresql/postgresql_owner.py @@ -0,0 +1,517 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# 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', + 'supported_by': 'community', + 'status': ['preview'] +} + +DOCUMENTATION = r''' +--- +module: postgresql_owner +short_description: Change an owner of PostgreSQL database object +description: +- Change an owner of PostgreSQL database object. +- Also allows to reassign the ownership of database objects owned by a database role to another role. +- For more information about REASSIGN OWNED BY command + see U(https://www.postgresql.org/docs/current/sql-reassign-owned.html). +version_added: '2.8' + +options: + new_owner: + description: + - Role (user/group) to set as an I(obj_name) owner. + type: str + required: yes + obj_name: + description: + - Name of a database object to change ownership. + - Mutually exclusive with I(reassign_owned_by). + type: str + obj_type: + description: + - Type of a database object. + - Mutually exclusive with I(reassign_owned_by). + type: str + required: yes + choices: [ database, function, matview, sequence, schema, table, tablespace, view ] + aliases: + - type + reassign_owned_by: + description: + - The list of role names. The ownership of all the objects within the current database, + and of all shared objects (databases, tablespaces), owned by this role(s) will be reassigned to I(owner). + - Pay attention - it reassignes all objects owned by this role(s) in the I(db)! + - If role(s) exists, always returns changed True. + - Cannot reassign ownership of objects that are required by the database system. + - For more information see U(https://www.postgresql.org/docs/current/sql-reassign-owned.html). + - Mutually exclusive with C(obj_type). + type: list + fail_on_role: + description: + - If C(yes), fail when I(reassign_owned_by) role does not exist. + Otherwise just warn and continue. + - Mutually exclusive with I(obj_name) and I(obj_type). + default: yes + type: bool + db: + description: + - Name of database to connect to. + type: str + aliases: + - login_db + port: + description: + - Database port to connect. + type: int + default: 5432 + aliases: + - login_port + login_user: + description: + - User (role) used to authenticate with PostgreSQL. + type: str + default: postgres + 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_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 + default: prefer + choices: [ allow, disable, prefer, require, verify-ca, verify-full ] + 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. + type: str + 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. + 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. +- 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''' +# Set owner as alice for function myfunc in database bar by ansible ad-hoc command: +# ansible -m postgresql_owner -a "db=bar new_owner=alice obj_name=myfunc obj_type=function" + +- name: The same as above by playbook + postgresql_owner: + db: bar + new_owner: alice + obj_name: myfunc + obj_type: function + +- name: Set owner as bob for table acme in database bar + postgresql_owner: + db: bar + new_owner: bob + obj_name: acme + obj_type: table + +- name: Set owner as alice for view test_view in database bar + postgresql_owner: + db: bar + new_owner: alice + obj_name: test_view + obj_type: view + +- name: Set owner as bob for tablespace ssd in database foo + postgresql_owner: + db: foo + new_owner: bob + obj_name: ssd + obj_type: tablespace + +- name: Reassign all object in database bar owned by bob to alice + postgresql_owner: + db: bar + new_owner: alice + reassign_owned_by: bob + +- name: Reassign all object in database bar owned by bob and bill to alice + postgresql_owner: + db: bar + new_owner: alice + reassign_owned_by: + - bob + - bill +''' + +RETURN = r''' +queries: + description: List of executed queries. + returned: always + type: str + sample: [ 'REASSIGN OWNED BY "bob" TO "alice"' ] +''' + +try: + import psycopg2 + HAS_PSYCOPG2 = True +except ImportError: + HAS_PSYCOPG2 = False + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils.database import SQLParseError, pg_quote_identifier +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 + + +def connect_to_db(module, kw, autocommit=False): + try: + db_connection = psycopg2.connect(**kw) + if autocommit: + if psycopg2.__version__ >= '2.4.2': + db_connection.set_session(autocommit=True) + else: + db_connection.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) + + except TypeError as e: + if 'sslrootcert' in e.args[0]: + module.fail_json(msg='Postgresql server must be at least ' + 'version 8.4 to support sslrootcert') + + module.fail_json(msg="unable to connect to database: %s" % to_native(e)) + + except Exception as e: + module.fail_json(msg="unable to connect to database: %s" % to_native(e)) + + return db_connection + + +class PgOwnership(object): + def __init__(self, module, cursor, role): + self.module = module + self.cursor = cursor + self.check_role_exists(role) + self.role = role + self.changed = False + self.executed_queries = [] + self.obj_name = '' + self.obj_type = '' + + def check_role_exists(self, role, fail_on_role=True): + if not self.__role_exists(role): + if fail_on_role: + self.module.fail_json(msg="Role '%s' does not exist" % role) + else: + self.module.warn("Role '%s' does not exist, pass" % role) + + return False + + else: + return True + + def reassign(self, old_owners, fail_on_role): + roles = [] + for r in old_owners: + if self.check_role_exists(r, fail_on_role): + roles.append(pg_quote_identifier(r, 'role')) + + # Roles do not exist, nothing to do, exit: + if not roles: + return False + + old_owners = ','.join(roles) + + query = ['REASSIGN OWNED BY'] + query.append(old_owners) + query.append('TO %s' % pg_quote_identifier(self.role, 'role')) + query = ' '.join(query) + + self.changed = self.__exec_sql(query, ddl=True) + + def set_owner(self, obj_type, obj_name): + self.obj_name = obj_name + self.obj_type = obj_type + + # if a new_owner is the object owner now, + # nothing to do: + if self.__is_owner(): + return False + + if obj_type == 'database': + self.__set_db_owner() + + elif obj_type == 'function': + self.__set_func_owner() + + elif obj_type == 'sequence': + self.__set_seq_owner() + + elif obj_type == 'schema': + self.__set_schema_owner() + + elif obj_type == 'table': + self.__set_table_owner() + + elif obj_type == 'tablespace': + self.__set_tablespace_owner() + + elif obj_type == 'view': + self.__set_view_owner() + + elif obj_type == 'matview': + self.__set_mat_view_owner() + + def __is_owner(self): + if self.obj_type == 'table': + query = ("SELECT 1 FROM pg_tables WHERE tablename = '%s' " + "AND tableowner = '%s'" % (self.obj_name, self.role)) + + elif self.obj_type == 'database': + query = ("SELECT 1 FROM pg_database AS d " + "JOIN pg_roles AS r ON d.datdba = r.oid " + "WHERE d.datname = '%s' " + "AND r.rolname = '%s'" % (self.obj_name, self.role)) + + elif self.obj_type == 'function': + query = ("SELECT 1 FROM pg_proc AS f " + "JOIN pg_roles AS r ON f.proowner = r.oid " + "WHERE f.proname = '%s' " + "AND r.rolname = '%s'" % (self.obj_name, self.role)) + + elif self.obj_type == 'sequence': + query = ("SELECT 1 FROM pg_class AS c " + "JOIN pg_roles AS r ON c.relowner = r.oid " + "WHERE c.relkind = 'S' AND c.relname = '%s' " + "AND r.rolname = '%s'" % (self.obj_name, self.role)) + + elif self.obj_type == 'schema': + query = ("SELECT 1 FROM information_schema.schemata " + "WHERE schema_name = '%s' " + "AND schema_owner = '%s'" % (self.obj_name, self.role)) + + elif self.obj_type == 'tablespace': + query = ("SELECT 1 FROM pg_tablespace AS t " + "JOIN pg_roles AS r ON t.spcowner = r.oid " + "WHERE t.spcname = '%s' " + "AND r.rolname = '%s'" % (self.obj_name, self.role)) + + elif self.obj_type == 'view': + query = ("SELECT 1 FROM pg_views " + "WHERE viewname = '%s' " + "AND viewowner = '%s'" % (self.obj_name, self.role)) + + elif self.obj_type == 'matview': + query = ("SELECT 1 FROM pg_matviews " + "WHERE matviewname = '%s' " + "AND matviewowner = '%s'" % (self.obj_name, self.role)) + + return self.__exec_sql(query, add_to_executed=False) + + def __set_db_owner(self): + query = "ALTER DATABASE %s OWNER TO %s" % (pg_quote_identifier(self.obj_name, 'database'), + pg_quote_identifier(self.role, 'role')) + self.changed = self.__exec_sql(query, ddl=True) + + def __set_func_owner(self): + query = "ALTER FUNCTION %s OWNER TO %s" % (self.obj_name, + pg_quote_identifier(self.role, 'role')) + self.changed = self.__exec_sql(query, ddl=True) + + def __set_seq_owner(self): + query = "ALTER SEQUENCE %s OWNER TO %s" % (pg_quote_identifier(self.obj_name, 'table'), + pg_quote_identifier(self.role, 'role')) + self.changed = self.__exec_sql(query, ddl=True) + + def __set_schema_owner(self): + query = "ALTER SCHEMA %s OWNER TO %s" % (pg_quote_identifier(self.obj_name, 'schema'), + pg_quote_identifier(self.role, 'role')) + self.changed = self.__exec_sql(query, ddl=True) + + def __set_table_owner(self): + query = "ALTER TABLE %s OWNER TO %s" % (pg_quote_identifier(self.obj_name, 'table'), + pg_quote_identifier(self.role, 'role')) + self.changed = self.__exec_sql(query, ddl=True) + + def __set_tablespace_owner(self): + query = "ALTER TABLESPACE %s OWNER TO %s" % (pg_quote_identifier(self.obj_name, 'database'), + pg_quote_identifier(self.role, 'role')) + self.changed = self.__exec_sql(query, ddl=True) + + def __set_view_owner(self): + query = "ALTER VIEW %s OWNER TO %s" % (pg_quote_identifier(self.obj_name, 'table'), + pg_quote_identifier(self.role, 'role')) + self.changed = self.__exec_sql(query, ddl=True) + + def __set_mat_view_owner(self): + query = "ALTER MATERIALIZED VIEW %s OWNER TO %s" % (pg_quote_identifier(self.obj_name, 'table'), + pg_quote_identifier(self.role, 'role')) + self.changed = self.__exec_sql(query, ddl=True) + + def __role_exists(self, role): + return self.__exec_sql("SELECT 1 FROM pg_roles WHERE rolname = '%s'" % role, add_to_executed=False) + + def __exec_sql(self, query, ddl=False, add_to_executed=True): + try: + self.cursor.execute(query) + + if add_to_executed: + self.executed_queries.append(query) + + if not ddl: + res = self.cursor.fetchall() + return res + return True + except SQLParseError as e: + self.module.fail_json(msg=to_native(e)) + except psycopg2.ProgrammingError as e: + self.module.fail_json(msg="Cannot execute SQL '%s': %s" % (query, to_native(e))) + return False + + +# =========================================== +# Module execution. +# + + +def main(): + argument_spec = postgres_common_argument_spec() + argument_spec.update( + new_owner=dict(type='str', required=True), + obj_name=dict(type='str'), + obj_type=dict(type='str', aliases=['type'], choices=[ + 'database', 'function', 'matview', 'sequence', 'schema', 'table', 'tablespace', 'view']), + reassign_owned_by=dict(type='list'), + fail_on_role=dict(type='bool', default=True), + db=dict(type='str', aliases=['login_db']), + session_role=dict(type='str'), + ) + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[ + ['obj_name', 'reassign_owned_by'], + ['obj_type', 'reassign_owned_by'], + ['obj_name', 'fail_on_role'], + ['obj_type', 'fail_on_role'], + ], + supports_check_mode=True, + ) + + if not HAS_PSYCOPG2: + module.fail_json(msg=missing_required_lib('psycopg2')) + + new_owner = module.params['new_owner'] + obj_name = module.params['obj_name'] + obj_type = module.params['obj_type'] + reassign_owned_by = module.params['reassign_owned_by'] + fail_on_role = module.params['fail_on_role'] + 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"] is None 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 ssl_rootcert parameter') + + db_connection = connect_to_db(module, kw, autocommit=False) + cursor = db_connection.cursor(cursor_factory=psycopg2.extras.DictCursor) + + # Switch role, if specified: + if session_role: + try: + cursor.execute('SET ROLE %s' % session_role) + except Exception as e: + module.fail_json(msg="Could not switch role: %s" % to_native(e)) + + ############## + # Create the object and do main job: + pg_ownership = PgOwnership(module, cursor, new_owner) + + # if we want to change ownership: + if obj_name: + pg_ownership.set_owner(obj_type, obj_name) + + # if we want to reassign objects owned by roles: + elif reassign_owned_by: + pg_ownership.reassign(reassign_owned_by, fail_on_role) + + # Rollback if it's possible and check_mode: + if module.check_mode: + db_connection.rollback() + else: + db_connection.commit() + + cursor.close() + db_connection.close() + + module.exit_json( + changed=pg_ownership.changed, + queries=pg_ownership.executed_queries, + ) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/postgresql/tasks/main.yml b/test/integration/targets/postgresql/tasks/main.yml index e097b8e6ad0..b6141b50d77 100644 --- a/test/integration/targets/postgresql/tasks/main.yml +++ b/test/integration/targets/postgresql/tasks/main.yml @@ -833,7 +833,7 @@ - include: postgresql_ext.yml when: postgres_version_resp.stdout is version('9.1', '>=') and ansible_distribution == 'Fedora' -# Test postgresql_schema module: +# Test postgresql_schema module - include: postgresql_schema.yml # Test postgresql_membership module @@ -851,6 +851,9 @@ - include: postgresql_lang.yml when: ansible_distribution == 'CentOS' +# Test postgresql_owner module +- include: postgresql_owner.yml + # 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_owner.yml b/test/integration/targets/postgresql/tasks/postgresql_owner.yml new file mode 100644 index 00000000000..5894f0ab93c --- /dev/null +++ b/test/integration/targets/postgresql/tasks/postgresql_owner.yml @@ -0,0 +1,1039 @@ +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#################### +# Prepare for tests: + +# Create test roles: +- name: postgresql_owner - create test roles + become_user: "{{ pg_user }}" + become: yes + postgresql_user: + login_user: "{{ pg_user }}" + db: postgres + name: "{{ item }}" + ignore_errors: yes + with_items: + - alice + - bob + +# Create test database: +- name: postgresql_owner - create test database + become_user: "{{ pg_user }}" + become: yes + postgresql_db: + login_user: "{{ pg_user }}" + db: acme + +# Create test table: +- name: postgresql_owner - create test table + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + login_user: "{{ pg_user }}" + db: acme + query: "CREATE TABLE my_table (id int)" + +- name: postgresql_owner - set owner + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: my_table + obj_type: table + +# Create test sequence: +- name: postgresql_owner - create test sequence + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + login_user: "{{ pg_user }}" + db: acme + query: "CREATE SEQUENCE test_seq" + +# Create test function: +- name: postgresql_owner - create test function + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + login_user: "{{ pg_user }}" + db: acme + query: "CREATE FUNCTION increment(integer) RETURNS integer AS 'select $1 + 1;' LANGUAGE SQL IMMUTABLE RETURNS NULL ON NULL INPUT;" + +# Create test schema: +- name: postgresql_owner - create test schema + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + login_user: "{{ pg_user }}" + db: acme + query: "CREATE SCHEMA test_schema" + +# Create test view: +- name: postgresql_owner - create test view + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + login_user: "{{ pg_user }}" + db: acme + query: "CREATE VIEW test_view AS SELECT * FROM my_table" + +# Create test materialized view (available from PG ver 9.4): +- name: postgresql_owner - create test materialized view + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + login_user: "{{ pg_user }}" + db: acme + query: "CREATE MATERIALIZED VIEW test_mat_view AS SELECT * FROM my_table" + when: postgres_version_resp.stdout is version('9.4', '>=') + +# Create test tablespace +- name: postgresql_owner - create a new tablespace called acme and set bob as an its owner + become_user: "{{ pg_user }}" + become: yes + postgresql_tablespace: + db: acme + login_user: "{{ pg_user }}" + name: acme + owner: alice + location: /ssd + +################ +# Do main tests: + +# +# check reassign_owned_by param +# +# try to reassign ownership to non existent user: +- name: postgresql_owner - reassign_owned_by to non existent user + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: non_existent + reassign_owned_by: bob + register: result + ignore_errors: yes + +- assert: + that: + - result.failed == true + +# try to reassign ownership from existent user with fail_on_role: +- name: postgresql_owner - reassign_owned_by, check fail_on_role + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: alice + reassign_owned_by: non_existent + fail_on_role: no + register: result + +- assert: + that: + - result.failed == false + +# check_mode: +- name: postgresql_owner - reassign_owned_by in check_mode + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: alice + reassign_owned_by: bob + check_mode: yes + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['REASSIGN OWNED BY "bob" TO "alice"'] + +# Check, rowcount must be 0 +- name: postgresql_owner - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_tables WHERE tablename = 'my_table' AND tableowner = 'alice'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 0 + +# True mode: +- name: postgresql_owner - reassign_owned_by + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: alice + reassign_owned_by: bob + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['REASSIGN OWNED BY "bob" TO "alice"'] + +# Check, rowcount must be 1 +- name: postgresql_owner - check that ownership has been changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_tables WHERE tablename = 'my_table' AND tableowner = 'alice'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 1 + +# +# Check obj_type for each type +# + +# ############################# +# check_mode obj_type: database +- name: postgresql_owner - set db owner in check_mode + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: acme + obj_type: database + check_mode: yes + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER DATABASE "acme" OWNER TO "bob"'] + +# Check, rowcount must be 0 +- name: postgresql_owner - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_database AS d JOIN pg_roles AS r ON d.datdba = r.oid WHERE d.datname = 'acme' AND r.rolname = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 0 + +# true mode obj_type: database +- name: postgresql_owner - set db owner + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: acme + obj_type: database + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER DATABASE "acme" OWNER TO "bob"'] + +# Check, rowcount must be 1 +- name: postgresql_owner - check that db owner has been changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_database AS d JOIN pg_roles AS r ON d.datdba = r.oid WHERE d.datname = 'acme' AND r.rolname = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 1 + +# true mode obj_type: database, try to set again +- name: postgresql_owner - set db owner again + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: acme + obj_type: database + register: result + +- assert: + that: + - result.changed == false + - result.queries == [] + +# Check, rowcount must be 1 +- name: postgresql_owner - check that db owner is bob + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_database AS d JOIN pg_roles AS r ON d.datdba = r.oid WHERE d.datname = 'acme' AND r.rolname = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 1 + +# ########################## +# check_mode obj_type: table +- name: postgresql_owner - set table owner in check_mode + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: my_table + obj_type: table + check_mode: yes + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER TABLE "my_table" OWNER TO "bob"'] + +# Check, rowcount must be 0 +- name: postgresql_owner - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_tables WHERE tablename = 'my_table' AND tableowner = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 0 + +# true mode obj_type: table +- name: postgresql_owner - set db owner + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: my_table + obj_type: table + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER TABLE "my_table" OWNER TO "bob"'] + +# Check, rowcount must be 1 +- name: postgresql_owner - check that table owner has been changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_tables WHERE tablename = 'my_table' AND tableowner = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 1 + +# true mode obj_type: table again +- name: postgresql_owner - set db owner again + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: my_table + obj_type: table + register: result + +- assert: + that: + - result.changed == false + - result.queries == [] + +# Check, rowcount must be 1 +- name: postgresql_owner - check that table owner is bob + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_tables WHERE tablename = 'my_table' AND tableowner = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 1 + +# ############################# +# check_mode obj_type: sequence +- name: postgresql_owner - set sequence owner in check_mode + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: test_seq + obj_type: sequence + check_mode: yes + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER SEQUENCE "test_seq" OWNER TO "bob"'] + +# Check, rowcount must be 0 +- name: postgresql_owner - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_class AS c JOIN pg_roles AS r ON c.relowner = r.oid WHERE c.relkind = 'S' AND c.relname = 'test_seq' AND r.rolname = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 0 + +# true mode obj_type: sequence +- name: postgresql_owner - set db owner + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: test_seq + obj_type: sequence + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER SEQUENCE "test_seq" OWNER TO "bob"'] + +# Check, rowcount must be 1 +- name: postgresql_owner - check that table owner has been changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_class AS c JOIN pg_roles AS r ON c.relowner = r.oid WHERE c.relkind = 'S' AND c.relname = 'test_seq' AND r.rolname = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 1 + +# true mode obj_type: table again +- name: postgresql_owner - set db owner again + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: test_seq + obj_type: sequence + register: result + +- assert: + that: + - result.changed == false + - result.queries == [] + +# Check, rowcount must be 1 +- name: postgresql_owner - check that sequence owner is bob + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_class AS c JOIN pg_roles AS r ON c.relowner = r.oid WHERE c.relkind = 'S' AND c.relname = 'test_seq' AND r.rolname = 'bob'" + + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 1 + +# ############################# +# check_mode obj_type: function +- name: postgresql_owner - set function owner in check_mode + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: increment + obj_type: function + check_mode: yes + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER FUNCTION increment OWNER TO "bob"'] + when: postgres_version_resp.stdout is version('10', '>=') + +# Check, rowcount must be 0 +- name: postgresql_owner - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_proc AS f JOIN pg_roles AS r ON f.proowner = r.oid WHERE f.proname = 'increment' AND r.rolname = 'bob'" + ignore_errors: yes + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.rowcount == 0 + when: postgres_version_resp.stdout is version('10', '>=') + +# true mode obj_type: function +- name: postgresql_owner - set func owner + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: increment + obj_type: function + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER FUNCTION increment OWNER TO "bob"'] + when: postgres_version_resp.stdout is version('10', '>=') + +# Check, rowcount must be 1 +- name: postgresql_owner - check that func owner has been changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_proc AS f JOIN pg_roles AS r ON f.proowner = r.oid WHERE f.proname = 'increment' AND r.rolname = 'bob'" + ignore_errors: yes + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('10', '>=') + +# true mode obj_type: function again +- name: postgresql_owner - set func owner again + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: increment + obj_type: function + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.changed == false + - result.queries == [] + when: postgres_version_resp.stdout is version('10', '>=') + +# Check, rowcount must be 1 +- name: postgresql_owner - check that function owner is bob + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_proc AS f JOIN pg_roles AS r ON f.proowner = r.oid WHERE f.proname = 'increment' AND r.rolname = 'bob'" + ignore_errors: yes + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('10', '>=') + +# ########################### +# check_mode obj_type: schema +- name: postgresql_owner - set schema owner in check_mode + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: test_schema + obj_type: schema + check_mode: yes + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER SCHEMA "test_schema" OWNER TO "bob"'] + +# Check, rowcount must be 0 +- name: postgresql_owner - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM information_schema.schemata WHERE schema_name = 'test_schema' AND schema_owner = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 0 + +# true mode obj_type: schema +- name: postgresql_owner - set schema owner + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: test_schema + obj_type: schema + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER SCHEMA "test_schema" OWNER TO "bob"'] + +# Check, rowcount must be 1 +- name: postgresql_owner - check that schema owner has been changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM information_schema.schemata WHERE schema_name = 'test_schema' AND schema_owner = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 1 + +# true mode obj_type: schema again +- name: postgresql_owner - set schema owner again + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: test_seq + obj_type: sequence + register: result + +- assert: + that: + - result.changed == false + - result.queries == [] + +# Check, rowcount must be 1 +- name: postgresql_owner - check that schema owner is bob + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM information_schema.schemata WHERE schema_name = 'test_schema' AND schema_owner = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 1 + +# ########################### +# check_mode obj_type: view +- name: postgresql_owner - set view owner in check_mode + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: test_view + obj_type: view + check_mode: yes + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER VIEW "test_view" OWNER TO "bob"'] + +# Check, rowcount must be 0 +- name: postgresql_owner - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_views WHERE viewname = 'test_view' AND viewowner = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 0 + +# true mode obj_type: view +- name: postgresql_owner - set view owner + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: test_view + obj_type: view + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER VIEW "test_view" OWNER TO "bob"'] + +# Check, rowcount must be 1 +- name: postgresql_owner - check that view owner has been changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_views WHERE viewname = 'test_view' AND viewowner = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 1 + +# true mode obj_type: view again +- name: postgresql_owner - set vatview owner again + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: test_view + obj_type: view + register: result + +- assert: + that: + - result.changed == false + - result.queries == [] + +# Check, rowcount must be 1 +- name: postgresql_owner - check that view owner is bob + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_views WHERE viewname = 'test_view' AND viewowner = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 1 + +# ########################### +# check_mode obj_type: matview +- name: postgresql_owner - set matview owner in check_mode + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: test_mat_view + obj_type: matview + check_mode: yes + register: result + when: postgres_version_resp.stdout is version('9.4', '>=') + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER MATERIALIZED VIEW "test_mat_view" OWNER TO "bob"'] + when: postgres_version_resp.stdout is version('9.4', '>=') + +# Check, rowcount must be 0 +- name: postgresql_owner - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_matviews WHERE matviewname = 'test_view' AND matviewowner = 'bob'" + ignore_errors: yes + register: result + when: postgres_version_resp.stdout is version('9.4', '>=') + +- assert: + that: + - result.rowcount == 0 + when: postgres_version_resp.stdout is version('9.4', '>=') + +# true mode obj_type: matview +- name: postgresql_owner - set matview owner + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: test_mat_view + obj_type: matview + register: result + when: postgres_version_resp.stdout is version('9.4', '>=') + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER MATERIALIZED VIEW "test_mat_view" OWNER TO "bob"'] + when: postgres_version_resp.stdout is version('9.4', '>=') + +# Check, rowcount must be 1 +- name: postgresql_owner - check that matview owner has been changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_matviews WHERE matviewname = 'test_mat_view' AND matviewowner = 'bob'" + ignore_errors: yes + register: result + when: postgres_version_resp.stdout is version('9.4', '>=') + +- assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('9.4', '>=') + +# true mode obj_type: matview again +- name: postgresql_owner - set matview owner again + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: test_mat_view + obj_type: matview + register: result + when: postgres_version_resp.stdout is version('9.4', '>=') + +- assert: + that: + - result.changed == false + - result.queries == [] + when: postgres_version_resp.stdout is version('9.4', '>=') + +# Check, rowcount must be 1 +- name: postgresql_owner - check that matview owner is bob + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_matviews WHERE matviewname = 'test_mat_view' AND matviewowner = 'bob'" + ignore_errors: yes + register: result + when: postgres_version_resp.stdout is version('9.4', '>=') + +- assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('9.4', '>=') + +# ########################### +# check_mode obj_type: tablespace +- name: postgresql_owner - set tablespace owner in check_mode + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: acme + obj_type: tablespace + check_mode: yes + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER TABLESPACE "acme" OWNER TO "bob"'] + +# Check, rowcount must be 0 +- name: postgresql_owner - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_tablespace AS t JOIN pg_roles AS r ON t.spcowner = r.oid WHERE t.spcname = 'acme' AND r.rolname = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 0 + +# true mode obj_type: tablespace +- name: postgresql_owner - set tablespace owner + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: acme + obj_type: tablespace + register: result + +- assert: + that: + - result.changed == true + - result.queries == ['ALTER TABLESPACE "acme" OWNER TO "bob"'] + +# Check, rowcount must be 1 +- name: postgresql_owner - check that tablespace owner has been changed after the previous step + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_tablespace AS t JOIN pg_roles AS r ON t.spcowner = r.oid WHERE t.spcname = 'acme' AND r.rolname = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 1 + +# true mode obj_type: tablespace again +- name: postgresql_owner - set tablespace owner again + become_user: "{{ pg_user }}" + become: yes + postgresql_owner: + login_user: "{{ pg_user }}" + db: acme + new_owner: bob + obj_name: acme + obj_type: tablespace + register: result + +- assert: + that: + - result.changed == false + - result.queries == [] + +# Check, rowcount must be 1 +- name: postgresql_owner - check that tablespace owner is bob + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: acme + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_tablespace AS t JOIN pg_roles AS r ON t.spcowner = r.oid WHERE t.spcname = 'acme' AND r.rolname = 'bob'" + ignore_errors: yes + register: result + +- assert: + that: + - result.rowcount == 1 + +# +# Crean up +# + +# Drop test database: +- name: postgresql_owner - create test database + become_user: "{{ pg_user }}" + become: yes + postgresql_db: + login_user: "{{ pg_user }}" + db: acme + state: absent + +# Drop test tablespace: +- name: postgresql_owner - drop test tablespace + become_user: "{{ pg_user }}" + become: yes + postgresql_tablespace: + db: postgres + login_user: "{{ pg_user }}" + name: acme + state: absent