diff options
Diffstat (limited to 'ansible_collections/community/postgresql/tests')
202 files changed, 18040 insertions, 0 deletions
diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_copy/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_copy/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_copy/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_copy/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_copy/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_copy/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_copy/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_copy/tasks/main.yml new file mode 100644 index 000000000..359c5d3be --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_copy/tasks/main.yml @@ -0,0 +1,8 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_copy module +- import_tasks: postgresql_copy_initial.yml + when: postgres_version_resp.stdout is version('9.4', '>=') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_copy/tasks/postgresql_copy_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_copy/tasks/postgresql_copy_initial.yml new file mode 100644 index 000000000..5c51c108e --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_copy/tasks/postgresql_copy_initial.yml @@ -0,0 +1,278 @@ +# 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) +# The file for testing postgresql_copy module. + +- vars: + test_table: acme + data_file_txt: /tmp/data.txt + data_file_csv: /tmp/data.csv + task_parameters: &task_parameters + become_user: '{{ pg_user }}' + become: true + register: result + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: postgres + + block: + # Test preparation: + - name: postgresql_copy - create test table + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: '{{ test_table }}' + columns: + - id int + - name text + + # Insert the data: + - name: postgresql_copy - insert rows into test table + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "INSERT INTO {{ test_table }} (id, name) VALUES (1, 'first')" + + - name: postgresql_copy - ensure that test data files don't exist + <<: *task_parameters + file: + path: '{{ item }}' + state: absent + with_items: + - '{{ data_file_csv }}' + - '{{ data_file_txt }}' + + # ############## + # Do main tests: + + # check_mode - if it's OK, must always return changed=True: + - name: postgresql_copy - check_mode, copy test table content to data_file_txt + check_mode: true + <<: *task_parameters + postgresql_copy: + <<: *pg_parameters + copy_to: '{{ data_file_txt }}' + src: '{{ test_table }}' + trust_input: false + + - assert: + that: + - result is changed + + # check that nothing changed after the previous step: + - name: postgresql_copy - check that data_file_txt doesn't exist + <<: *task_parameters + ignore_errors: true + shell: head -n 1 '{{ data_file_txt }}' + + - assert: + that: + - result.failed == true + - result.rc == 1 + + # check_mode - if it's OK, must always return changed=True: + - name: postgresql_copy - check_mode, copy test table content from data_file_txt + check_mode: true + <<: *task_parameters + postgresql_copy: + <<: *pg_parameters + copy_from: '{{ data_file_txt }}' + dst: '{{ test_table }}' + trust_input: false + + - assert: + that: + - result is changed + + # check that nothing changed after the previous step: + - name: postgresql_copy - check that test table continue to have one row + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: 'SELECT * FROM {{ test_table }}' + + - assert: + that: + - result.rowcount == 1 + + # check_mode - test must fail because test table doesn't exist: + - name: postgresql_copy - check_mode, copy non existent table to data_file_txt + check_mode: true + ignore_errors: true + <<: *task_parameters + postgresql_copy: + <<: *pg_parameters + copy_to: '{{ data_file_txt }}' + src: non_existent_table + trust_input: false + + - assert: + that: + - result.failed == true + - result.queries is not defined + + - name: postgresql_copy - check trust_input + <<: *task_parameters + postgresql_copy: + <<: *pg_parameters + copy_to: '{{ data_file_txt }}' + src: '{{ test_table }}' + session_role: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' + trust_input: false + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg is search('is potentially dangerous') + + - name: postgresql_copy - copy test table data to data_file_txt + <<: *task_parameters + postgresql_copy: + <<: *pg_parameters + copy_to: '{{ data_file_txt }}' + src: '{{ test_table }}' + trust_input: false + + - assert: + that: + - result is changed + - result.queries == ["COPY \"{{ test_table }}\" TO '{{ data_file_txt }}'"] + - result.src == '{{ test_table }}' + - result.dst == '{{ data_file_txt }}' + + # check the prev test + - name: postgresql_copy - check data_file_txt exists and not empty + <<: *task_parameters + shell: 'head -n 1 {{ data_file_txt }}' + + - assert: + that: + - result.stdout == '1\tfirst' + + # test different options and columns + - name: postgresql_copy - copy test table data to data_file_csv with options and columns + <<: *task_parameters + postgresql_copy: + <<: *pg_parameters + copy_to: '{{ data_file_csv }}' + src: '{{ test_table }}' + columns: + - id + - name + options: + format: csv + trust_input: false + + - assert: + that: + - result is changed + - result.queries == ["COPY \"{{ test_table }}\" (id,name) TO '{{ data_file_csv }}' (format csv)"] + - result.src == '{{ test_table }}' + - result.dst == '{{ data_file_csv }}' + + # check the prev test + - name: postgresql_copy - check data_file_csv exists and not empty + <<: *task_parameters + shell: 'head -n 1 {{ data_file_csv }}' + + - assert: + that: + - result.stdout == '1,first' + + - name: postgresql_copy - copy from data_file_csv to test table + <<: *task_parameters + postgresql_copy: + <<: *pg_parameters + copy_from: '{{ data_file_csv }}' + dst: '{{ test_table }}' + columns: + - id + - name + options: + format: csv + trust_input: false + + - assert: + that: + - result is changed + - result.queries == ["COPY \"{{ test_table }}\" (id,name) FROM '{{ data_file_csv }}' (format csv)"] + - result.dst == '{{ test_table }}' + - result.src == '{{ data_file_csv }}' + + - name: postgresql_copy - check that there are two rows in test table after the prev step + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT * FROM {{ test_table }} WHERE id = '1' AND name = 'first'" + + - assert: + that: + - result.rowcount == 2 + + - name: postgresql_copy - test program option, copy to program + <<: *task_parameters + postgresql_copy: + <<: *pg_parameters + src: '{{ test_table }}' + copy_to: '/bin/true' + program: true + columns: id, name + options: + delimiter: '|' + trust_input: false + when: ansible_distribution != 'FreeBSD' + + - assert: + that: + - result is changed + - result.queries == ["COPY \"{{ test_table }}\" (id, name) TO PROGRAM '/bin/true' (delimiter '|')"] + - result.src == '{{ test_table }}' + - result.dst == '/bin/true' + when: ansible_distribution != 'FreeBSD' + + - name: postgresql_copy - test program option, copy from program + <<: *task_parameters + postgresql_copy: + <<: *pg_parameters + dst: '{{ test_table }}' + copy_from: 'echo 1,first' + program: true + columns: id, name + options: + delimiter: ',' + trust_input: false + + - assert: + that: + - result is changed + - result.queries == ["COPY \"{{ test_table }}\" (id, name) FROM PROGRAM 'echo 1,first' (delimiter ',')"] + - result.dst == '{{ test_table }}' + - result.src == 'echo 1,first' + when: ansible_distribution != 'FreeBSD' + + - name: postgresql_copy - check that there are three rows in test table after the prev step + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT * FROM {{ test_table }} WHERE id = '1' AND name = 'first'" + + - assert: + that: + - result.rowcount == 3 + + # clean up + - name: postgresql_copy - remove test table + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: '{{ test_table }}' + state: absent + + - name: postgresql_copy - remove test data files + <<: *task_parameters + file: + path: '{{ item }}' + state: absent + with_items: + - '{{ data_file_csv }}' + - '{{ data_file_txt }}' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/aliases new file mode 100644 index 000000000..2f88eca08 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/aliases @@ -0,0 +1,3 @@ +destructive +shippable/posix/group1 +postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/defaults/main.yml new file mode 100644 index 000000000..766feeecc --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/defaults/main.yml @@ -0,0 +1,11 @@ +db_name: 'ansible_db' +db_user1: 'ansible.db.user1' +db_user2: 'ansible.db.user2' +tmp_dir: '/tmp' +db_session_role1: 'session_role1' +db_session_role2: 'session_role2' + +# To test trust_input parameter and +# possibility to create a database with dots in its name +db_name_with_dot: 'db.name' +suspicious_db_name: '{{ db_name_with_dot }}"; --' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/main.yml new file mode 100644 index 000000000..dd55c3f98 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/main.yml @@ -0,0 +1,47 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- import_tasks: postgresql_db_session_role.yml + +# Initial tests of postgresql_db module: +- import_tasks: postgresql_db_initial.yml + +# General tests: +- import_tasks: postgresql_db_general.yml + +# Tests for rename value of state option +- import_tasks: state_rename.yml + +# Dump/restore tests per format: +- include_tasks: state_dump_restore.yml + vars: + test_fixture: user + file: '{{ loop_item }}' + loop: + - dbdata.sql + - dbdata.sql.gz + - dbdata.sql.bz2 + - dbdata.sql.xz + - dbdata.tar + - dbdata.tar.gz + - dbdata.tar.bz2 + - dbdata.tar.xz + - dbdata.pgc + - dbdata.dir + - dbdata.dir.gz + - dbdata.dir.bz2 + - dbdata.dir.xz + loop_control: + loop_var: loop_item + when: postgres_version_resp.stdout is version('9.1', '>=') + +# Dump/restore tests per other logins: +- import_tasks: state_dump_restore.yml + vars: + file: dbdata.tar + test_fixture: admin + +# Simple test to create and then drop with force +- import_tasks: manage_database.yml diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/manage_database.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/manage_database.yml new file mode 100644 index 000000000..42d0f4ee9 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/manage_database.yml @@ -0,0 +1,9 @@ +- name: Create a simple database mydb + postgresql_db: + name: mydb + +- name: Drop the database with force + postgresql_db: + name: mydb + state: absent + force: true diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/postgresql_db_general.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/postgresql_db_general.yml new file mode 100644 index 000000000..6a178bea1 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/postgresql_db_general.yml @@ -0,0 +1,152 @@ +- become_user: '{{ pg_user }}' + become: true + vars: + db_tablespace: bar + tblspc_location: /ssd + db_name: acme + block_parameters: + become_user: '{{ pg_user }}' + become: true + task_parameters: + register: result + pg_parameters: + login_user: '{{ pg_user }}' + block: + - name: postgresql_db - drop dir for test tablespace + become: true + become_user: root + file: + path: '{{ tblspc_location }}' + state: absent + ignore_errors: true + - name: postgresql_db - disable selinux + become: true + become_user: root + shell: setenforce 0 + ignore_errors: true + - name: postgresql_db - create dir for test tablespace + become: true + become_user: root + file: + path: '{{ tblspc_location }}' + state: directory + owner: '{{ pg_user }}' + group: '{{ pg_user }}' + mode: '0700' + - name: postgresql_db_ - create a new tablespace + postgresql_tablespace: + login_user: '{{ pg_user }}' + login_db: postgres + name: '{{ db_tablespace }}' + location: '{{ tblspc_location }}' + - register: result + name: postgresql_db_tablespace - Create DB with tablespace option in check mode + check_mode: true + postgresql_db: + login_user: '{{ pg_user }}' + maintenance_db: postgres + name: '{{ db_name }}' + tablespace: '{{ db_tablespace }}' + - assert: + that: + - result is changed + - register: result + name: postgresql_db_tablespace - Check actual DB tablespace, rowcount must be 0 because actually nothing changed + postgresql_query: + login_user: '{{ pg_user }}' + login_db: postgres + query: 'SELECT 1 FROM pg_database AS d JOIN pg_tablespace AS t ON d.dattablespace = t.oid WHERE d.datname = ''{{ db_name }}'' AND t.spcname = ''{{ db_tablespace }}'' + + ' + - assert: + that: + - result.rowcount == 0 + - register: result + name: postgresql_db_tablespace - Create DB with tablespace option + postgresql_db: + login_user: '{{ pg_user }}' + maintenance_db: postgres + name: '{{ db_name }}' + tablespace: '{{ db_tablespace }}' + - assert: + that: + - result is changed + - result.executed_commands == ['CREATE DATABASE "{{ db_name }}" TABLESPACE "{{ db_tablespace }}"'] + - register: result + name: postgresql_db_tablespace - Check actual DB tablespace, rowcount must be 1 + postgresql_query: + login_user: '{{ pg_user }}' + login_db: postgres + query: 'SELECT 1 FROM pg_database AS d JOIN pg_tablespace AS t ON d.dattablespace = t.oid WHERE d.datname = ''{{ db_name }}'' AND t.spcname = ''{{ db_tablespace }}'' + + ' + - assert: + that: + - result.rowcount == 1 + - register: result + name: postgresql_db_tablespace - The same DB with tablespace option again + postgresql_db: + login_user: '{{ pg_user }}' + maintenance_db: postgres + name: '{{ db_name }}' + tablespace: '{{ db_tablespace }}' + - assert: + that: + - result is not changed + - register: result + name: postgresql_db_tablespace - Change tablespace in check_mode + check_mode: true + postgresql_db: + login_user: '{{ pg_user }}' + maintenance_db: postgres + name: '{{ db_name }}' + tablespace: pg_default + - assert: + that: + - result is changed + - register: result + name: postgresql_db_tablespace - Check actual DB tablespace, rowcount must be 1 because actually nothing changed + postgresql_query: + login_user: '{{ pg_user }}' + login_db: postgres + query: 'SELECT 1 FROM pg_database AS d JOIN pg_tablespace AS t ON d.dattablespace = t.oid WHERE d.datname = ''{{ db_name }}'' AND t.spcname = ''{{ db_tablespace }}'' + + ' + - assert: + that: + - result.rowcount == 1 + - register: result + name: postgresql_db_tablespace - Change tablespace in actual mode + postgresql_db: + login_user: '{{ pg_user }}' + maintenance_db: postgres + name: '{{ db_name }}' + tablespace: pg_default + - assert: + that: + - result is changed + - register: result + name: postgresql_db_tablespace - Check actual DB tablespace, rowcount must be 1 + postgresql_query: + login_user: '{{ pg_user }}' + login_db: postgres + query: 'SELECT 1 FROM pg_database AS d JOIN pg_tablespace AS t ON d.dattablespace = t.oid WHERE d.datname = ''{{ db_name }}'' AND t.spcname = ''pg_default'' + + ' + - assert: + that: + - result.rowcount == 1 + - register: result + name: postgresql_db_tablespace - Drop test DB + postgresql_db: + login_user: '{{ pg_user }}' + maintenance_db: postgres + name: '{{ db_name }}' + state: absent + - register: result + name: postgresql_db_tablespace - Remove tablespace + postgresql_tablespace: + login_user: '{{ pg_user }}' + login_db: postgres + name: '{{ db_tablespace }}' + state: absent diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/postgresql_db_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/postgresql_db_initial.yml new file mode 100644 index 000000000..472524a23 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/postgresql_db_initial.yml @@ -0,0 +1,366 @@ +# +# Create and destroy db +# +- name: Create DB + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: present + name: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + +- name: assert that module reports the db was created + assert: + that: + - result is changed + - result.db == "{{ db_name }}" + - result.executed_commands == ['CREATE DATABASE "{{ db_name }}"'] + +- name: Check that database created + become_user: "{{ pg_user }}" + become: true + shell: echo "select datname from pg_database where datname = '{{ db_name }}';" | psql -d postgres + register: result + +- assert: + that: + - "result.stdout_lines[-1] == '(1 row)'" + +- name: Run create on an already created db + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: present + name: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + +- name: assert that module reports the db was unchanged + assert: + that: + - result is not changed + +- name: Destroy DB + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: absent + name: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + +- name: assert that module reports the db was changed + assert: + that: + - result is changed + - result.executed_commands == ['DROP DATABASE "{{ db_name }}"'] + +- name: Check that database was destroyed + become_user: "{{ pg_user }}" + become: true + shell: echo "select datname from pg_database where datname = '{{ db_name }}';" | psql -d postgres + register: result + +- assert: + that: + - "result.stdout_lines[-1] == '(0 rows)'" + +- name: Destroy DB + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: absent + name: "{{ db_name }}" + login_user: "{{ pg_user }}" + register: result + +- name: assert that removing an already removed db makes no change + assert: + that: + - result is not changed + + +# This corner case works to add but not to drop. This is sufficiently crazy +# that I'm not going to attempt to fix it unless someone lets me know that they +# need the functionality +# +# - postgresql_db: +# state: 'present' +# name: '"silly.""name"' +# - shell: echo "select datname from pg_database where datname = 'silly.""name';" | psql +# register: result +# +# - assert: +# that: "result.stdout_lines[-1] == '(1 row)'" +# - postgresql_db: +# state: absent +# name: '"silly.""name"' +# - shell: echo "select datname from pg_database where datname = 'silly.""name';" | psql +# register: result +# +# - assert: +# that: "result.stdout_lines[-1] == '(0 rows)'" + +# +# Test conn_limit, encoding, collate, ctype, template options +# +- name: Create a DB with conn_limit, encoding, collate, ctype, and template options + become_user: "{{ pg_user }}" + become: true + postgresql_db: + name: '{{ db_name }}' + state: 'present' + conn_limit: '100' + encoding: 'LATIN1' + lc_collate: 'pt_BR{{ locale_latin_suffix }}' + lc_ctype: 'es_ES{{ locale_latin_suffix }}' + template: 'template0' + login_user: "{{ pg_user }}" + register: result + +- assert: + that: + - result is changed + - result.executed_commands == ["CREATE DATABASE \"{{ db_name }}\" TEMPLATE \"template0\" ENCODING 'LATIN1' LC_COLLATE 'pt_BR{{ locale_latin_suffix }}' LC_CTYPE 'es_ES{{ locale_latin_suffix }}' CONNECTION LIMIT 100"] or result.executed_commands == ["CREATE DATABASE \"{{ db_name }}\" TEMPLATE \"template0\" ENCODING E'LATIN1' LC_COLLATE E'pt_BR{{ locale_latin_suffix }}' LC_CTYPE E'es_ES{{ locale_latin_suffix }}' CONNECTION LIMIT 100"] + +- name: Check that the DB has all of our options + become_user: "{{ pg_user }}" + become: true + shell: echo "select datname, datconnlimit, pg_encoding_to_char(encoding), datcollate, datctype from pg_database where datname = '{{ db_name }}';" | psql -d postgres + register: result + +- assert: + that: + - "result.stdout_lines[-1] == '(1 row)'" + - "'LATIN1' in result.stdout_lines[-2]" + - "'pt_BR' in result.stdout_lines[-2]" + - "'es_ES' in result.stdout_lines[-2]" + - "'UTF8' not in result.stdout_lines[-2]" + - "'en_US' not in result.stdout_lines[-2]" + - "'100' in result.stdout_lines[-2]" + +- name: Check that running db creation with options a second time does nothing + become_user: "{{ pg_user }}" + become: true + postgresql_db: + name: '{{ db_name }}' + state: 'present' + conn_limit: '100' + encoding: 'LATIN1' + lc_collate: 'pt_BR{{ locale_latin_suffix }}' + lc_ctype: 'es_ES{{ locale_latin_suffix }}' + template: 'template0' + login_user: "{{ pg_user }}" + register: result + +- assert: + that: + - result is not changed + + +- name: Check that attempting to change encoding returns an error + become_user: "{{ pg_user }}" + become: true + postgresql_db: + name: '{{ db_name }}' + state: 'present' + encoding: 'UTF8' + lc_collate: 'pt_BR{{ locale_utf8_suffix }}' + lc_ctype: 'es_ES{{ locale_utf8_suffix }}' + template: 'template0' + login_user: "{{ pg_user }}" + register: result + ignore_errors: true + +- assert: + that: + - result is failed + +- name: Check that changing the conn_limit actually works + become_user: "{{ pg_user }}" + become: true + postgresql_db: + name: '{{ db_name }}' + state: 'present' + conn_limit: '200' + encoding: 'LATIN1' + lc_collate: 'pt_BR{{ locale_latin_suffix }}' + lc_ctype: 'es_ES{{ locale_latin_suffix }}' + template: 'template0' + login_user: "{{ pg_user }}" + register: result + +- assert: + that: + - result is changed + - result.executed_commands == ['ALTER DATABASE "{{ db_name }}" CONNECTION LIMIT 200'] + +- name: Check that conn_limit has actually been set / updated to 200 + become_user: "{{ pg_user }}" + become: true + shell: echo "SELECT datconnlimit AS conn_limit FROM pg_database WHERE datname = '{{ db_name }}';" | psql -d postgres + register: result + +- assert: + that: + - "result.stdout_lines[-1] == '(1 row)'" + - "'200' == '{{ result.stdout_lines[-2] | trim }}'" + +- name: Cleanup test DB + become_user: "{{ pg_user }}" + become: true + postgresql_db: + name: '{{ db_name }}' + state: 'absent' + login_user: "{{ pg_user }}" + +- shell: echo "select datname, pg_encoding_to_char(encoding), datcollate, datctype from pg_database where datname = '{{ db_name }}';" | psql -d postgres + become_user: "{{ pg_user }}" + become: true + register: result + +- assert: + that: + - "result.stdout_lines[-1] == '(0 rows)'" + +# +# Test db ownership +# +- name: Create an unprivileged user to own a DB + become_user: "{{ pg_user }}" + become: true + postgresql_user: + name: "{{ item }}" + encrypted: 'true' + password: "md55c8ccfd9d6711fc69a7eae647fc54f51" + login_user: "{{ pg_user }}" + db: postgres + loop: + - "{{ db_user1 }}" + - "{{ db_user2 }}" + +- name: Create db with user ownership + become_user: "{{ pg_user }}" + become: true + postgresql_db: + name: "{{ db_name }}" + state: "present" + owner: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + register: result + +- assert: + that: + - result is changed + - result.executed_commands == ['CREATE DATABASE "{{ db_name }}" OWNER "{{ db_user1 }}"'] + +- name: Check that the user owns the newly created DB + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: > + SELECT 1 FROM pg_catalog.pg_database + WHERE datname = '{{ db_name }}' + AND pg_catalog.pg_get_userbyid(datdba) = '{{ db_user1 }}' + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: Change the owner on an existing db, username with dots + become_user: "{{ pg_user }}" + become: true + postgresql_db: + name: "{{ db_name }}" + state: "present" + owner: "{{ db_user2 }}" + login_user: "{{ pg_user }}" + register: result + +- assert: + that: + - result is changed + - result.executed_commands == ['ALTER DATABASE "{{ db_name }}" OWNER TO "{{ db_user2 }}"'] + +- name: Check the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: > + SELECT 1 FROM pg_catalog.pg_database + WHERE datname = '{{ db_name }}' + AND pg_catalog.pg_get_userbyid(datdba) = '{{ db_user2 }}' + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: Change the owner on an existing db + become_user: "{{ pg_user }}" + become: true + postgresql_db: + name: "{{ db_name }}" + state: "present" + owner: "{{ pg_user }}" + login_user: "{{ pg_user }}" + register: result + +- name: assert that ansible says it changed the db + assert: + that: + - result is changed + +- name: Check that the user owns the newly created DB + become_user: "{{ pg_user }}" + become: true + shell: echo "select pg_catalog.pg_get_userbyid(datdba) from pg_catalog.pg_database where datname = '{{ db_name }}';" | psql -d postgres + register: result + +- assert: + that: + - "result.stdout_lines[-1] == '(1 row)'" + - "'{{ pg_user }}' == '{{ result.stdout_lines[-2] | trim }}'" + +- name: Cleanup db + become_user: "{{ pg_user }}" + become: true + postgresql_db: + name: "{{ db_name }}" + state: "absent" + login_user: "{{ pg_user }}" + +- name: Check that database was destroyed + become_user: "{{ pg_user }}" + become: true + shell: echo "select datname from pg_database where datname = '{{ db_name }}';" | psql -d postgres + register: result + +- assert: + that: + - "result.stdout_lines[-1] == '(0 rows)'" + +- name: Cleanup test user + become_user: "{{ pg_user }}" + become: true + postgresql_user: + name: "{{ db_user1 }}" + state: 'absent' + login_user: "{{ pg_user }}" + db: postgres + +- name: Check that they were removed + become_user: "{{ pg_user }}" + become: true + shell: echo "select * from pg_user where usename='{{ db_user1 }}';" | psql -d postgres + register: result + +- assert: + that: + - "result.stdout_lines[-1] == '(0 rows)'" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/postgresql_db_session_role.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/postgresql_db_session_role.yml new file mode 100644 index 000000000..74f9e3ff3 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/postgresql_db_session_role.yml @@ -0,0 +1,80 @@ +- name: Check that becoming an non-existing user throws an error + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: present + name: must_fail + login_user: "{{ pg_user }}" + session_role: "{{ db_session_role1 }}" + register: result + ignore_errors: true + +- assert: + that: + - result is failed + +- name: Create a high privileged user + become: true + become_user: "{{ pg_user }}" + postgresql_user: + name: "{{ db_session_role1 }}" + state: "present" + password: "password" + role_attr_flags: "CREATEDB,LOGIN,CREATEROLE" + login_user: "{{ pg_user }}" + db: postgres + +- name: Create a low privileged user using the newly created user + become: true + become_user: "{{ pg_user }}" + postgresql_user: + name: "{{ db_session_role2 }}" + state: "present" + password: "password" + role_attr_flags: "LOGIN" + login_user: "{{ pg_user }}" + session_role: "{{ db_session_role1 }}" + db: postgres + +- name: Create DB as session_role + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: present + name: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + session_role: "{{ db_session_role1 }}" + register: result + +- name: Check that database created and is owned by correct user + become_user: "{{ pg_user }}" + become: true + shell: echo "select rolname from pg_database join pg_roles on datdba = pg_roles.oid where datname = '{{ db_session_role1 }}';" | psql -AtXq postgres + register: result + +- assert: + that: + - "result.stdout_lines[-1] == '{{ db_session_role1 }}'" + +- name: Fail when creating database as low privileged user + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: present + name: "{{ db_session_role2 }}" + login_user: "{{ pg_user }}" + session_role: "{{ db_session_role2 }}" + register: result + ignore_errors: true + +- assert: + that: + - result is failed + +- name: Drop test db + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: absent + name: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/state_dump_restore.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/state_dump_restore.yml new file mode 100644 index 000000000..dbb3bf7a1 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/state_dump_restore.yml @@ -0,0 +1,235 @@ +# test code for state dump and restore for postgresql_db module +# copied from mysql_db/tasks/state_dump_import.yml +# (c) 2014, Wayne Rosario <wrosario@ansible.com> + +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# ============================================================ + +- name: Create a test user + become: true + become_user: "{{ pg_user }}" + postgresql_user: + name: "{{ db_user1 }}" + state: "present" + encrypted: 'true' + password: "password" + role_attr_flags: "CREATEDB,LOGIN,CREATEROLE" + login_user: "{{ pg_user }}" + db: postgres + +- set_fact: db_file_name="{{tmp_dir}}/{{file}}" + +- set_fact: + admin_str: "psql -U {{ pg_user }}" + +- set_fact: + user_str: "env PGPASSWORD=password psql -h localhost -U {{ db_user1 }} {{ db_name }}" + when: test_fixture == "user" + # "-n public" is required to work around pg_restore issues with plpgsql + +- set_fact: + user_str: "psql -U {{ pg_user }} {{ db_name }}" + when: test_fixture == "admin" + + + +- set_fact: + sql_create: "create table employee(id int, name varchar(100));" + sql_insert: "insert into employee values (47,'Joe Smith');" + sql_select: "select * from employee;" + +- name: state dump/restore - create database + postgresql_db: + state: present + name: "{{ db_name }}" + owner: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + +- name: state dump/restore - create table employee + command: '{{ user_str }} -c "{{ sql_create }}"' + +- name: state dump/restore - insert data into table employee + command: '{{ user_str }} -c "{{ sql_insert }}"' + +- name: state dump/restore - file name should not exist + file: name={{ db_file_name }} state=absent + +- name: test state=dump to backup the database (expect changed=true) + postgresql_db: + name: "{{ db_name }}" + target: "{{ db_file_name }}" + owner: "{{ db_user1 }}" + login_user: '{{(test_fixture == "user")|ternary(db_user1, pg_user)}}' + target_opts: '{{(test_fixture == "user")|ternary("-n public", omit)}}' + login_host: '{{(test_fixture == "user")|ternary("localhost", omit)}}' + login_password: '{{(test_fixture == "user")|ternary("password", omit)}}' + state: dump + dump_extra_args: --exclude-table=fake + register: result + become_user: "{{ pg_user }}" + become: true + +- name: assert output message backup the database + assert: + that: + - result is changed + - result.executed_commands[0] is search("--exclude-table=fake") + +- name: assert database was backed up successfully + command: file {{ db_file_name }} + register: result + +- name: state dump/restore - remove database for restore + postgresql_db: + name: "{{ db_name }}" + target: "{{ db_file_name }}" + owner: "{{ db_user1 }}" + login_user: '{{(test_fixture == "user")|ternary(db_user1, pg_user)}}' + target_opts: '{{(test_fixture == "user")|ternary("-n public", omit)}}' + login_host: '{{(test_fixture == "user")|ternary("localhost", omit)}}' + login_password: '{{(test_fixture == "user")|ternary("password", omit)}}' + state: absent + +- name: state dump/restore - re-create database + postgresql_db: + state: present + name: "{{ db_name }}" + owner: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + +- name: test state=restore to restore the database (expect changed=true) + postgresql_db: + name: "{{ db_name }}" + target: "{{ db_file_name }}" + owner: "{{ db_user1 }}" + login_user: '{{(test_fixture == "user")|ternary(db_user1, pg_user)}}' + target_opts: '{{(test_fixture == "user")|ternary("-n public", omit)}}' + login_host: '{{(test_fixture == "user")|ternary("localhost", omit)}}' + login_password: '{{(test_fixture == "user")|ternary("password", omit)}}' + state: restore + register: result + become_user: "{{ pg_user }}" + become: true + +- name: assert output message restore the database + assert: + that: + - result is changed + +- name: select data from table employee + command: '{{ user_str }} -c "{{ sql_select }}"' + register: result + +- name: assert data in database is from the restore database + assert: + that: + - "'47' in result.stdout" + - "'Joe Smith' in result.stdout" + +############################ +# 1. Test trust_input parameter +# 2. Test db name containing dots + +- name: state dump/restore - create database, trust_input no + become: true + become_user: "{{ pg_user }}" + postgresql_db: + state: present + name: "{{ suspicious_db_name }}" + owner: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + ignore_errors: true + +- assert: + that: + - result is failed + - result.msg == 'Passed input \'{{ suspicious_db_name }}\' is potentially dangerous' + +- name: state dump/restore - create database, trust_input true explicitly + become: true + become_user: "{{ pg_user }}" + postgresql_db: + state: present + name: "{{ db_name_with_dot }}" + owner: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + trust_input: true + register: result + +- assert: + that: + - result is changed + +- name: test state=restore to restore the database (expect changed=true) + become: true + become_user: "{{ pg_user }}" + postgresql_db: + name: "{{ db_name_with_dot }}" + target: "{{ db_file_name }}" + owner: "{{ db_user1 }}" + login_user: '{{(test_fixture == "user")|ternary(db_user1, pg_user)}}' + target_opts: '{{(test_fixture == "user")|ternary("-n public", omit)}}' + login_host: '{{(test_fixture == "user")|ternary("localhost", omit)}}' + login_password: '{{(test_fixture == "user")|ternary("password", omit)}}' + state: restore + register: result + +- name: assert output message restore the database + assert: + that: + - result is changed + +- name: state dump/restore - remove databases + become: true + become_user: "{{ pg_user }}" + postgresql_db: + state: absent + name: "{{ db_name_with_dot }}" + owner: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + trust_input: true + register: result + +- assert: + that: + - result is changed + +# Clean up +- name: state dump/restore - remove database name + postgresql_db: + name: "{{ db_name }}" + target: "{{ db_file_name }}" + owner: "{{ db_user1 }}" + login_user: '{{(test_fixture == "user")|ternary(db_user1, pg_user)}}' + target_opts: '{{(test_fixture == "user")|ternary("-n public", omit)}}' + login_host: '{{(test_fixture == "user")|ternary("localhost", omit)}}' + login_password: '{{(test_fixture == "user")|ternary("password", omit)}}' + state: absent + +- name: remove file name + file: name={{ db_file_name }} state=absent + +- name: Remove the test user + become: true + become_user: "{{ pg_user }}" + postgresql_user: + name: "{{ db_user1 }}" + state: "absent" + login_user: "{{ pg_user }}" + db: postgres diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/state_rename.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/state_rename.yml new file mode 100644 index 000000000..dc87b76fb --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_db/tasks/state_rename.yml @@ -0,0 +1,261 @@ +# 0. Check necessary options. +# 1. When both databases do not exists, it must fail. +# 2. When both databases exist, it must fail. +# 3. When the source database exists and the target does not, rename it. +# 4. When the source database doesn't exist and the target does, do nothing. +# 5. Check mode + +- become_user: '{{ pg_user }}' + become: true + vars: + db_source_name: acme + db_target_name: acme1 + + task_parameters: &task_parameters + register: result + + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + + block: + # 0. Check necessary options. + - name: Miss target option, must fail + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: '{{ db_source_name }}' + state: rename + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg == 'The "target" option must be defined when the "rename" option is used.' + + - name: Target and name options are the same, must fail + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: '{{ db_source_name }}' + state: rename + target: '{{ db_source_name }}' + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg == 'The "name/db" option and the "target" option cannot be the same.' + + - name: Maintenance_db and name options are the same, must fail + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: postgres + state: rename + target: '{{ db_source_name }}' + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg == 'The "maintenance_db" option and the "name/db" option cannot be the same.' + + # 1. When both databases do not exists, it must fail. + - name: Try to rename when both do not exist, must fail + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: '{{ db_source_name }}' + state: rename + target: '{{ db_target_name}}' + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg == 'The source and the target databases do not exist.' + + - name: Try to rename when both do not exist, must fail, check_mode + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: '{{ db_source_name }}' + state: rename + target: '{{ db_target_name}}' + ignore_errors: true + check_mode: true + + - assert: + that: + - result is failed + - result.msg == 'The source and the target databases do not exist.' + + # 2. When both databases exist, it must fail. + - name: Create test DBs + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: '{{ item }}' + state: present + loop: + - '{{ db_source_name }}' + - '{{ db_target_name }}' + + - name: Try to rename when both exist, must fail + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: '{{ db_source_name }}' + state: rename + target: '{{ db_target_name}}' + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg == 'Both the source and the target databases exist.' + + - name: Try to rename when both exist, must fail + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: '{{ db_source_name }}' + state: rename + target: '{{ db_target_name}}' + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg == 'Both the source and the target databases exist.' + + # 3. When the source database exists and the target does not, rename it. + # 4. When the source database doesn't exist and the target does, do nothing. + - name: Drop the target DB + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: '{{ db_target_name }}' + state: absent + + - name: Rename DB in check mode + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: '{{ db_source_name }}' + state: rename + target: '{{ db_target_name }}' + check_mode: true + + - assert: + that: + - result is succeeded + - result.executed_commands == [] + + - name: Check that nothing really happened + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT * FROM pg_database WHERE datname = '{{ db_source_name }}'" + + - assert: + that: + - result.rowcount == 1 + + - name: Check that nothing really happened + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT * FROM pg_database WHERE datname = '{{ db_target_name }}'" + + - assert: + that: + - result.rowcount == 0 + + - name: Rename DB in actual mode + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: '{{ db_source_name }}' + state: rename + target: '{{ db_target_name}}' + + - assert: + that: + - result is changed + - result.executed_commands == ['ALTER DATABASE "{{ db_source_name }}" RENAME TO "{{ db_target_name}}"'] + + - name: Check the changes have been made + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT * FROM pg_database WHERE datname = '{{ db_source_name }}'" + + - assert: + that: + - result.rowcount == 0 + + - name: Check the changes have been made + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT * FROM pg_database WHERE datname = '{{ db_target_name }}'" + + - assert: + that: + - result.rowcount == 1 + + - name: Try to rename same DBs again in check mode + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: '{{ db_source_name }}' + state: rename + target: '{{ db_target_name}}' + check_mode: true + + - assert: + that: + - result is not changed + - result.executed_commands == [] + + - name: Try to rename same DBs again in actual mode + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: '{{ db_source_name }}' + state: rename + target: '{{ db_target_name}}' + + - assert: + that: + - result is not changed + - result.executed_commands == [] + + - name: Check the state is the same + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT * FROM pg_database WHERE datname = '{{ db_source_name }}'" + + - assert: + that: + - result.rowcount == 0 + + - name: Check the state is the same + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT * FROM pg_database WHERE datname = '{{ db_target_name }}'" + + - assert: + that: + - result.rowcount == 1 + + # Clean up + - name: Remove test DB + <<: *task_parameters + postgresql_db: + <<: *pg_parameters + name: '{{ db_target_name }}' + state: absent diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/aliases new file mode 100644 index 000000000..142e8aa07 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/aliases @@ -0,0 +1,3 @@ +destructive +shippable/posix/group1 +skip/freebsd diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/defaults/main.yml new file mode 100644 index 000000000..05bac61d4 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/defaults/main.yml @@ -0,0 +1,2 @@ +db_session_role1: 'session_role1' +db_session_role2: 'session_role2' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/meta/main.yml new file mode 100644 index 000000000..0ec7d2fcc --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_pkg_mgr + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/tasks/main.yml new file mode 100644 index 000000000..1fa365be3 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/tasks/main.yml @@ -0,0 +1,26 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- import_tasks: postgresql_ext_session_role.yml + +# Initial CI tests of postgresql_ext module. +# 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. +- import_tasks: postgresql_ext_initial.yml + when: + - postgres_version_resp.stdout is version('9.1', '>=') + - ansible_distribution == 'Fedora' + +# CI tests of "version" option. +# It uses a mock extension, see test/integration/targets/setup_postgresql_db/. +# TODO: change postgresql_ext_initial.yml to use the mock extension too. +- import_tasks: postgresql_ext_version_opt.yml + when: + - ansible_distribution == 'Ubuntu' + - postgres_version_resp.stdout is version('9.1', '>=') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/tasks/postgresql_ext_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/tasks/postgresql_ext_initial.yml new file mode 100644 index 000000000..3e3eeda83 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/tasks/postgresql_ext_initial.yml @@ -0,0 +1,208 @@ +--- +- name: postgresql_ext - install postgis on Linux + package: name=postgis state=present + when: ansible_os_family != "Windows" + +- name: postgresql_ext - create schema schema1 + become_user: '{{ pg_user }}' + become: true + postgresql_schema: + database: postgres + name: schema1 + state: present + +- name: postgresql_ext - drop extension if exists + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: postgres + query: DROP EXTENSION IF EXISTS postgis + ignore_errors: true + +- name: postgresql_ext - create extension postgis in check_mode + become_user: '{{ pg_user }}' + become: true + postgresql_ext: + login_db: postgres + login_port: 5432 + name: postgis + check_mode: true + ignore_errors: true + register: result + +- assert: + that: + - result is changed + - result.queries == [] + +- name: postgresql_ext - check that extension doesn't exist after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: postgres + query: SELECT extname FROM pg_extension WHERE extname='postgis' + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +- name: postgresql_ext - create extension postgis + become_user: '{{ pg_user }}' + become: true + postgresql_ext: + login_db: postgres + login_port: 5432 + name: postgis + ignore_errors: true + register: result + +- assert: + that: + - result is changed + - result.queries == ['CREATE EXTENSION "postgis"'] + +- name: postgresql_ext - check that extension exists after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: postgres + query: SELECT extname FROM pg_extension WHERE extname='postgis' + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_ext - drop extension postgis + become_user: '{{ pg_user }}' + become: true + postgresql_ext: + db: postgres + name: postgis + state: absent + ignore_errors: true + register: result + +- assert: + that: + - result is changed + - result.queries == ['DROP EXTENSION "postgis"'] + +- name: postgresql_ext - check that extension doesn't exist after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: postgres + query: SELECT extname FROM pg_extension WHERE extname='postgis' + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +- name: postgresql_ext - create extension postgis + become_user: '{{ pg_user }}' + become: true + postgresql_ext: + db: postgres + name: postgis + schema: schema1 + ignore_errors: true + register: result + +- assert: + that: + - result is changed + - result.queries == ['CREATE EXTENSION "postgis" WITH SCHEMA "schema1"'] + +- name: postgresql_ext - check that extension exists after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: postgres + query: "SELECT extname FROM pg_extension AS e LEFT JOIN pg_catalog.pg_namespace AS n \nON n.oid = e.extnamespace WHERE e.extname='postgis' AND n.nspname='schema1'\n" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_ext - drop extension postgis cascade + become_user: '{{ pg_user }}' + become: true + postgresql_ext: + db: postgres + name: postgis + state: absent + cascade: true + ignore_errors: true + register: result + +- assert: + that: + - result is changed + - result.queries == ['DROP EXTENSION "postgis" CASCADE'] + +- name: postgresql_ext - check that extension doesn't exist after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: postgres + query: SELECT extname FROM pg_extension WHERE extname='postgis' + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +- name: postgresql_ext - create extension postgis cascade + become_user: '{{ pg_user }}' + become: true + postgresql_ext: + db: postgres + name: postgis + cascade: true + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('9.6', '<=') + +- assert: + that: + - result is changed + - result.queries == ['CREATE EXTENSION "postgis" CASCADE"'] + when: postgres_version_resp.stdout is version('9.6', '<=') + +- name: postgresql_ext - check that extension exists after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: postgres + query: SELECT extname FROM pg_extension WHERE extname='postgis' + ignore_errors: true + 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', '<=') + +- name: postgresql_ext - check that using a dangerous name fails + postgresql_ext: + db: postgres + name: postgis + session_role: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' + trust_input: false + ignore_errors: true + register: result + +- assert: + that: + - result is failed + - result.msg is search('is potentially dangerous') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/tasks/postgresql_ext_session_role.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/tasks/postgresql_ext_session_role.yml new file mode 100644 index 000000000..29173fd0b --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/tasks/postgresql_ext_session_role.yml @@ -0,0 +1,114 @@ +- name: Create a high privileged user + become: true + become_user: "{{ pg_user }}" + postgresql_user: + name: "{{ db_session_role1 }}" + state: "present" + password: "password" + role_attr_flags: "CREATEDB,LOGIN,CREATEROLE" + login_user: "{{ pg_user }}" + db: postgres + +- name: Create DB as session_role + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: present + name: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + session_role: "{{ db_session_role1 }}" + register: result + +- name: Check that pg_extension exists (PostgreSQL >= 9.1) + become_user: "{{ pg_user }}" + become: true + shell: echo "select count(*) from pg_class where relname='pg_extension' and relkind='r'" | psql -AtXq postgres + register: pg_extension + +- name: Remove plpgsql from testdb using postgresql_ext + become_user: "{{ pg_user }}" + become: true + postgresql_ext: + name: plpgsql + db: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + state: absent + when: + "pg_extension.stdout_lines[-1] == '1'" + +- name: Fail when trying to create an extension as a mere mortal user + become_user: "{{ pg_user }}" + become: true + postgresql_ext: + name: plpgsql + db: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + session_role: "{{ db_session_role2 }}" + ignore_errors: true + register: result + when: + "pg_extension.stdout_lines[-1] == '1'" + +- assert: + that: + - result is failed + when: + "pg_extension.stdout_lines[-1] == '1'" + +- name: Install extension as session_role + become_user: "{{ pg_user }}" + become: true + postgresql_ext: + name: plpgsql + db: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + session_role: "{{ db_session_role1 }}" + trust_input: false + when: + "pg_extension.stdout_lines[-1] == '1'" + +- name: Check that extension is created and is owned by session_role + become_user: "{{ pg_user }}" + become: true + shell: echo "select rolname from pg_extension join pg_roles on extowner=pg_roles.oid where extname='plpgsql';" | psql -AtXq "{{ db_session_role1 }}" + register: result + when: + "pg_extension.stdout_lines[-1] == '1'" + +- assert: + that: + - "result.stdout_lines[-1] == '{{ db_session_role1 }}'" + when: + "pg_extension.stdout_lines[-1] == '1'" + +- name: Remove plpgsql from testdb using postgresql_ext + become_user: "{{ pg_user }}" + become: true + postgresql_ext: + name: plpgsql + db: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + state: absent + trust_input: false + when: + "pg_extension.stdout_lines[-1] == '1'" + +- name: Drop test db + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: absent + name: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + +- name: Drop test users + become: true + become_user: "{{ pg_user }}" + postgresql_user: + name: "{{ item }}" + state: absent + login_user: "{{ pg_user }}" + db: postgres + with_items: + - "{{ db_session_role1 }}" + - "{{ db_session_role2 }}" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/tasks/postgresql_ext_version_opt.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/tasks/postgresql_ext_version_opt.yml new file mode 100644 index 000000000..2443fe785 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ext/tasks/postgresql_ext_version_opt.yml @@ -0,0 +1,554 @@ +# 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) +# Tests for postgresql_ext version option + +- vars: + test_ext: dummy + test_schema: schema1 + task_parameters: &task_parameters + become_user: '{{ pg_user }}' + become: true + register: result + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: postgres + + block: + # Preparation: + - name: postgresql_ext_version - create schema schema1 + <<: *task_parameters + postgresql_schema: + <<: *pg_parameters + name: "{{ test_schema }}" + + # Do tests: + - name: postgresql_ext_version - create extension of specific version, check mode + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + schema: "{{ test_schema }}" + version: '1.0' + trust_input: false + check_mode: true + + - assert: + that: + - result is changed + + - name: postgresql_ext_version - check that nothing was actually changed + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT 1 FROM pg_extension WHERE extname = '{{ test_ext }}'" + + - assert: + that: + - result.rowcount == 0 + + - name: postgresql_ext_version - create extension of specific version + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + schema: "{{ test_schema }}" + version: '1.0' + trust_input: false + + - assert: + that: + - result is changed + - result.queries == ["CREATE EXTENSION \"{{ test_ext }}\" WITH SCHEMA \"{{ test_schema }}\" VERSION '1.0'"] + + - name: postgresql_ext_version - check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT 1 FROM pg_extension WHERE extname = '{{ test_ext }}' AND extversion = '1.0'" + + - assert: + that: + - result.rowcount == 1 + + - name: postgresql_ext_version - try to create extension of the same version again in check_mode + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + schema: "{{ test_schema }}" + version: '1.0' + trust_input: false + check_mode: true + + - assert: + that: + - result is not changed + + - name: postgresql_ext_version - check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT 1 FROM pg_extension WHERE extname = '{{ test_ext }}' AND extversion = '1.0'" + + - assert: + that: + - result.rowcount == 1 + + - name: postgresql_ext_version - try to create extension of the same version again in actual mode + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + schema: "{{ test_schema }}" + version: '1.0' + trust_input: false + + - assert: + that: + - result is not changed + + - name: postgresql_ext_version - check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT 1 FROM pg_extension WHERE extname = '{{ test_ext }}' AND extversion = '1.0'" + + - assert: + that: + - result.rowcount == 1 + + - name: postgresql_ext_version - update the extension to the next version in check_mode + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + schema: "{{ test_schema }}" + version: '2.0' + trust_input: false + check_mode: true + + - assert: + that: + - result is changed + + - name: postgresql_ext_version - check, the version must be 1.0 + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT 1 FROM pg_extension WHERE extname = '{{ test_ext }}' AND extversion = '1.0'" + + - assert: + that: + - result.rowcount == 1 + + - name: postgresql_ext_version - update the extension to the next version + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + schema: "{{ test_schema }}" + version: '2.0' + trust_input: false + + - assert: + that: + - result is changed + - result.queries == ["ALTER EXTENSION \"{{ test_ext }}\" UPDATE TO '2.0'"] + + - name: postgresql_ext_version - check, the version must be 2.0 + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT 1 FROM pg_extension WHERE extname = '{{ test_ext }}' AND extversion = '2.0'" + + - assert: + that: + - result.rowcount == 1 + + - name: postgresql_ext_version - check that version won't be changed if version won't be passed + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + schema: "{{ test_schema }}" + trust_input: false + + - assert: + that: + - result is not changed + + - name: postgresql_ext_version - check, the version must be 2.0 + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT 1 FROM pg_extension WHERE extname = '{{ test_ext }}' AND extversion = '2.0'" + + - assert: + that: + - result.rowcount == 1 + + - name: postgresql_ext_version - update the extension to the latest version + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + schema: "{{ test_schema }}" + version: latest + trust_input: false + + - assert: + that: + - result is changed + - result.queries == ["ALTER EXTENSION \"{{ test_ext }}\" UPDATE"] + + - name: postgresql_ext_version - check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT 1 FROM pg_extension WHERE extname = '{{ test_ext }}' AND extversion = '4.0'" + + - assert: + that: + - result.rowcount == 1 + + - name: postgresql_ext_version - try to update the extension to the latest version again which always runs an update. + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + schema: "{{ test_schema }}" + version: latest + trust_input: false + + - assert: + that: + - result is changed + + - name: postgresql_ext_version - check that version number did not change even though update ran + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT 1 FROM pg_extension WHERE extname = '{{ test_ext }}' AND extversion = '4.0'" + + - assert: + that: + - result.rowcount == 1 + + - name: postgresql_ext_version - try to downgrade the extension version, must fail + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + schema: "{{ test_schema }}" + version: '1.0' + trust_input: false + ignore_errors: true + + - assert: + that: + - result.failed == true + + - name: postgresql_ext_version - drop the extension in check_mode + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + state: absent + trust_input: false + check_mode: true + + - assert: + that: + - result is changed + + - name: postgresql_ext_version - check that extension exists + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT 1 FROM pg_extension WHERE extname = '{{ test_ext }}' AND extversion = '4.0'" + + - assert: + that: + - result.rowcount == 1 + + - name: postgresql_ext_version - drop the extension in actual mode + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + state: absent + trust_input: false + + - assert: + that: + - result is changed + + - name: postgresql_ext_version - check that extension doesn't exist after the prev step + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT 1 FROM pg_extension WHERE extname = '{{ test_ext }}'" + + - assert: + that: + - result.rowcount == 0 + + - name: postgresql_ext_version - try to drop the non-existent extension again + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + state: absent + trust_input: false + + - assert: + that: + - result is not changed + + - name: postgresql_ext_version - create the extension without passing version + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + trust_input: false + + - assert: + that: + - result is changed + - result.queries == ["CREATE EXTENSION \"{{ test_ext }}\""] + + - name: postgresql_ext_version - check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT 1 FROM pg_extension WHERE extname = '{{ test_ext }}' AND extversion = '4.0'" + + - assert: + that: + - result.rowcount == 1 + + - name: postgresql_ext_version - try to install non-existent extension + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: non_existent + trust_input: false + ignore_errors: true + + - assert: + that: + - result.failed == true + - result.msg == "Extension non_existent is not available" + + ###################################################################### + # https://github.com/ansible-collections/community.general/issues/1095 + - name: Install postgis + package: + name: '{{ postgis }}' + + - name: Create postgis extension + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: postgis + version: latest + + - assert: + that: + - result is changed + + # https://github.com/ansible-collections/community.postgresql/issues/137 + - name: Drop extension + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: '{{ test_ext }}' + schema: '{{ test_schema }}' + state: absent + + - name: Non standard version + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: '{{ test_ext }}' + schema: '{{ test_schema }}' + version: 0 + + - name: Test + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + + - assert: + that: + - result['databases']['postgres']['extensions']['dummy']['extversion']['major'] == 0 + - result['databases']['postgres']['extensions']['dummy']['extversion']['minor'] == None + - result['databases']['postgres']['extensions']['dummy']['extversion']['raw'] == '0' + + - name: Upgrade extension to a version that have a sub minor version 3.0-1 + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: '{{ test_ext }}' + schema: '{{ test_schema }}' + version: '3.0-1' + + - name: Test + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + + - assert: + that: + - result['databases']['postgres']['extensions']['dummy']['extversion']['major'] == 3 + - result['databases']['postgres']['extensions']['dummy']['extversion']['minor'] == 0 + - result['databases']['postgres']['extensions']['dummy']['extversion']['raw'] == '3.0-1' + + - name: Upgrade extension to version 3.0-foo + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: '{{ test_ext }}' + schema: '{{ test_schema }}' + version: '3.0-foo' + + - name: Test + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + + - assert: + that: + - result['databases']['postgres']['extensions']['dummy']['extversion']['major'] == None + - result['databases']['postgres']['extensions']['dummy']['extversion']['minor'] == None + - result['databases']['postgres']['extensions']['dummy']['extversion']['raw'] == '3.0-foo' + + - name: Upgrade extension to version 3.beta + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: '{{ test_ext }}' + schema: '{{ test_schema }}' + version: '3.beta' + + - name: Test + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + + - assert: + that: + - result['databases']['postgres']['extensions']['dummy']['extversion']['major'] == None + - result['databases']['postgres']['extensions']['dummy']['extversion']['minor'] == None + - result['databases']['postgres']['extensions']['dummy']['extversion']['raw'] == '3.beta' + + - name: Upgrade extension to version 3-1.0 + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: '{{ test_ext }}' + schema: '{{ test_schema }}' + version: '3-1.0' + + - name: Test + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + + - assert: + that: + - result['databases']['postgres']['extensions']['dummy']['extversion']['major'] == 3 + - result['databases']['postgres']['extensions']['dummy']['extversion']['minor'] == 1 + - result['databases']['postgres']['extensions']['dummy']['extversion']['raw'] == '3-1.0' + + - name: Upgrade extension to version 3-1.0-1 + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: '{{ test_ext }}' + schema: '{{ test_schema }}' + version: '3-1.0-1' + + - name: Test + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + + - assert: + that: + - result['databases']['postgres']['extensions']['dummy']['extversion']['major'] == 3 + - result['databases']['postgres']['extensions']['dummy']['extversion']['minor'] == 1 + - result['databases']['postgres']['extensions']['dummy']['extversion']['raw'] == '3-1.0-1' + + - name: Upgrade extension to version 3-1.foo + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: '{{ test_ext }}' + schema: '{{ test_schema }}' + version: '3-1.foo' + + - name: Test + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + + - assert: + that: + - result['databases']['postgres']['extensions']['dummy']['extversion']['major'] == None + - result['databases']['postgres']['extensions']['dummy']['extversion']['minor'] == None + - result['databases']['postgres']['extensions']['dummy']['extversion']['raw'] == '3-1.foo' + + - name: Upgrade extension to version v4 + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: '{{ test_ext }}' + schema: '{{ test_schema }}' + version: 'v4' + + - name: Test + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + + - assert: + that: + - result['databases']['postgres']['extensions']['dummy']['extversion']['major'] == None + - result['databases']['postgres']['extensions']['dummy']['extversion']['minor'] == None + - result['databases']['postgres']['extensions']['dummy']['extversion']['raw'] == 'v4' + + - name: Upgrade extension to the latest + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: '{{ test_ext }}' + schema: '{{ test_schema }}' + version: latest + + - name: Test + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + + - assert: + that: + - result['databases']['postgres']['extensions']['dummy']['extversion']['major'] == 4 + - result['databases']['postgres']['extensions']['dummy']['extversion']['minor'] == 0 + - result['databases']['postgres']['extensions']['dummy']['extversion']['raw'] == '4.0' + + # Cleanup: + - name: postgresql_ext_version - drop the extension + <<: *task_parameters + postgresql_ext: + <<: *pg_parameters + name: "{{ test_ext }}" + state: absent + trust_input: false + + - name: postgresql_ext_version - drop the schema + <<: *task_parameters + postgresql_schema: + <<: *pg_parameters + name: "{{ test_schema }}" + state: absent diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_idx/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_idx/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_idx/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_idx/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_idx/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_idx/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_idx/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_idx/tasks/main.yml new file mode 100644 index 000000000..2f5945616 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_idx/tasks/main.yml @@ -0,0 +1,7 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_idx module +- import_tasks: postgresql_idx_initial.yml diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_idx/tasks/postgresql_idx_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_idx/tasks/postgresql_idx_initial.yml new file mode 100644 index 000000000..3c2dc3fb6 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_idx/tasks/postgresql_idx_initial.yml @@ -0,0 +1,377 @@ +- name: postgresql_idx - create test table called test_table + become_user: '{{ pg_user }}' + become: true + shell: psql postgres -U "{{ pg_user }}" -t -c "CREATE TABLE test_table (id int, story text);" + ignore_errors: true + +- name: postgresql_idx - drop test tablespace called ssd if exists + become_user: '{{ pg_user }}' + become: true + shell: psql postgres -U "{{ pg_user }}" -t -c "DROP TABLESPACE IF EXISTS ssd;" + ignore_errors: true + +- name: postgresql_idx - drop dir for test tablespace + become: true + file: + path: /mnt/ssd + state: absent + ignore_errors: true + +- name: postgresql_idx - create dir for test tablespace + become: true + file: + path: /mnt/ssd + state: directory + owner: '{{ pg_user }}' + mode: '0755' + ignore_errors: true + +- name: postgresql_idx - create test tablespace called ssd + become_user: '{{ pg_user }}' + become: true + shell: psql postgres -U "{{ pg_user }}" -t -c "CREATE TABLESPACE ssd LOCATION '/mnt/ssd';" + ignore_errors: true + register: tablespace + +- name: postgresql_idx - create test schema + become_user: '{{ pg_user }}' + become: true + shell: psql postgres -U "{{ pg_user }}" -t -c "CREATE SCHEMA foo;" + ignore_errors: true + +- name: postgresql_idx - create table in non-default schema + become_user: '{{ pg_user }}' + become: true + shell: psql postgres -U "{{ pg_user }}" -t -c "CREATE TABLE foo.foo_table (id int, story text);" + ignore_errors: true + +- name: postgresql_idx - create btree index in check_mode + become_user: '{{ pg_user }}' + become: true + postgresql_idx: + db: postgres + login_user: '{{ pg_user }}' + table: test_table + columns: id, story + idxname: Test0_idx + check_mode: true + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.tblname == '' + - result.name == 'Test0_idx' + - result.state == 'absent' + - result.valid != '' + - result.tblspace == '' + - result.storage_params == [] + - result.schema == '' + - result.query == '' + +- name: postgresql_idx - check nothing changed after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: postgres + login_user: '{{ pg_user }}' + query: SELECT 1 FROM pg_indexes WHERE indexname = 'Test0_idx' + register: result + +- assert: + that: + - result.rowcount == 0 + +- name: postgresql_idx - create btree index concurrently + become_user: '{{ pg_user }}' + become: true + postgresql_idx: + db: postgres + login_user: '{{ pg_user }}' + table: test_table + columns: id, story + idxname: Test0_idx + trust_input: false + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.tblname == 'test_table' + - result.name == 'Test0_idx' + - result.state == 'present' + - result.valid != '' + - result.tblspace == '' + - result.storage_params == [] + - result.schema == 'public' + - result.query == 'CREATE INDEX CONCURRENTLY "Test0_idx" ON "public"."test_table" USING BTREE (id, story)' + +- name: postgresql_idx - check the index exists after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: postgres + login_user: '{{ pg_user }}' + query: SELECT 1 FROM pg_indexes WHERE indexname = 'Test0_idx' + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_idx - try to create existing index again + become_user: '{{ pg_user }}' + become: true + postgresql_idx: + db: postgres + login_user: '{{ pg_user }}' + table: test_table + columns: id, story + idxname: Test0_idx + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.tblname == 'test_table' + - result.name == 'Test0_idx' + - result.state == 'present' + - result.valid != '' + - result.tblspace == '' + - result.storage_params == [] + - result.schema == 'public' + - result.query == '' + +- name: postgresql_idx - create btree index - non-default schema, tablespace, storage parameter + become_user: '{{ pg_user }}' + become: true + postgresql_idx: + db: postgres + login_user: '{{ pg_user }}' + schema: foo + table: foo_table + columns: + - id + - story + idxname: foo_test_idx + tablespace: ssd + storage_params: fillfactor=90 + trust_input: false + register: result + ignore_errors: true + when: tablespace.rc == 0 + +- assert: + that: + - result is changed + - result.tblname == 'foo_table' + - result.name == 'foo_test_idx' + - result.state == 'present' + - result.valid != '' + - result.tblspace == 'ssd' + - result.storage_params == [ "fillfactor=90" ] + - result.schema == 'foo' + - result.query == 'CREATE INDEX CONCURRENTLY "foo_test_idx" ON "foo"."foo_table" USING BTREE (id,story) WITH (fillfactor=90) TABLESPACE "ssd"' + when: tablespace.rc == 0 + +- name: postgresql_idx - create brin index not concurrently + become_user: '{{ pg_user }}' + become: true + postgresql_idx: + db: postgres + login_user: '{{ pg_user }}' + schema: public + table: test_table + state: present + type: brin + columns: id + idxname: test_brin_idx + concurrent: false + trust_input: false + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.tblname == 'test_table' + - result.name == 'test_brin_idx' + - result.state == 'present' + - result.valid != '' + - result.tblspace == '' + - result.storage_params == [] + - result.schema == 'public' + - result.query == 'CREATE INDEX "test_brin_idx" ON "public"."test_table" USING brin (id)' + when: postgres_version_resp.stdout is version('9.5', '>=') + +- name: postgresql_idx - create index with condition + become_user: '{{ pg_user }}' + become: true + postgresql_idx: + db: postgres + login_user: '{{ pg_user }}' + table: test_table + columns: id + idxname: test1_idx + cond: id > 1 AND id != 10 + trust_input: false + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.tblname == 'test_table' + - result.name == 'test1_idx' + - result.state == 'present' + - result.valid != '' + - result.tblspace == '' + - result.storage_params == [] + - result.schema == 'public' + - result.query == 'CREATE INDEX CONCURRENTLY "test1_idx" ON "public"."test_table" USING BTREE (id) WHERE id > 1 AND id != 10' + +- name: postgresql_idx - create unique index + become_user: '{{ pg_user }}' + become: true + postgresql_idx: + db: postgres + login_user: '{{ pg_user }}' + table: test_table + columns: story + idxname: test_unique0_idx + unique: true + trust_input: false + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.tblname == 'test_table' + - result.name == 'test_unique0_idx' + - result.state == 'present' + - result.valid != '' + - result.tblspace == '' + - result.storage_params == [] + - result.schema == 'public' + - result.query == 'CREATE UNIQUE INDEX CONCURRENTLY "test_unique0_idx" ON "public"."test_table" USING BTREE (story)' + +- name: postgresql_idx - avoid unique index with type different of btree + become_user: '{{ pg_user }}' + become: true + postgresql_idx: + db: postgres + login_user: '{{ pg_user }}' + table: test_table + columns: story + idxname: test_unique0_idx + unique: true + concurrent: false + type: brin + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.msg == 'Only btree currently supports unique indexes' + +- name: postgresql_idx - drop index from specific schema cascade in check_mode + become_user: '{{ pg_user }}' + become: true + postgresql_idx: + db: postgres + login_user: '{{ pg_user }}' + schema: foo + name: foo_test_idx + cascade: true + state: absent + concurrent: false + trust_input: true + check_mode: true + register: result + ignore_errors: true + when: tablespace.rc == 0 + +- assert: + that: + - result is changed + - result.name == 'foo_test_idx' + - result.state == 'present' + - result.schema == 'foo' + - result.query == '' + when: tablespace.rc == 0 + +- name: postgresql_idx - check the index exists after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: postgres + login_user: '{{ pg_user }}' + query: SELECT 1 FROM pg_indexes WHERE indexname = 'foo_test_idx' AND schemaname = 'foo' + register: result + when: tablespace.rc == 0 + +- assert: + that: + - result.rowcount == 1 + when: tablespace.rc == 0 + +- name: postgresql_idx - drop index from specific schema cascade + become_user: '{{ pg_user }}' + become: true + postgresql_idx: + db: postgres + login_user: '{{ pg_user }}' + schema: foo + name: foo_test_idx + cascade: true + state: absent + concurrent: false + register: result + ignore_errors: true + when: tablespace.rc == 0 + +- assert: + that: + - result is changed + - result.name == 'foo_test_idx' + - result.state == 'absent' + - result.schema == 'foo' + - result.query == 'DROP INDEX "foo"."foo_test_idx" CASCADE' + when: tablespace.rc == 0 + +- name: postgresql_idx - check the index doesn't exist after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: postgres + login_user: '{{ pg_user }}' + query: SELECT 1 FROM pg_indexes WHERE indexname = 'foo_test_idx' and schemaname = 'foo' + register: result + when: tablespace.rc == 0 + +- assert: + that: + - result.rowcount == 0 + when: tablespace.rc == 0 + +- name: postgresql_idx - try to drop not existing index + become_user: '{{ pg_user }}' + become: true + postgresql_idx: + db: postgres + login_user: '{{ pg_user }}' + schema: foo + name: foo_test_idx + state: absent + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.query == '' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/aliases new file mode 100644 index 000000000..786e05315 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/aliases @@ -0,0 +1,4 @@ +destructive +shippable/posix/group1 +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/defaults/main.yml new file mode 100644 index 000000000..7a8fe2a37 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/defaults/main.yml @@ -0,0 +1,13 @@ +--- +pg_user: postgres +db_default: postgres + +test_table1: acme1 +test_pub: first_publication +test_pub2: second_publication +replication_role: logical_replication +replication_pass: alsdjfKJKDf1# +test_db: acme_db +test_subscription: test +test_subscription2: test2 +conn_timeout: 100 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/meta/main.yml new file mode 100644 index 000000000..d72e4d23c --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_replication diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/tasks/main.yml new file mode 100644 index 000000000..04c7788ad --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/tasks/main.yml @@ -0,0 +1,12 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# For testing getting publication and subscription info +- import_tasks: setup_publication.yml + when: ansible_distribution == 'Ubuntu' and ansible_distribution_major_version >= '18' + +# Initial CI tests of postgresql_info module +- import_tasks: postgresql_info_initial.yml + when: ansible_distribution == 'Ubuntu' and ansible_distribution_major_version >= '18' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/tasks/postgresql_info_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/tasks/postgresql_info_initial.yml new file mode 100644 index 000000000..6dfe50542 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/tasks/postgresql_info_initial.yml @@ -0,0 +1,243 @@ +# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- vars: + task_parameters: &task_parameters + become_user: '{{ pg_user }}' + become: true + register: result + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: '{{ db_default }}' + connect_params: + connect_timeout: 30 + + + block: + + - name: Create test subscription + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + login_db: '{{ test_db }}' + state: present + publications: '{{ test_pub }}' + connparams: + host: 127.0.0.1 + port: '{{ primary_port }}' + user: '{{ replication_role }}' + password: '{{ replication_pass }}' + dbname: '{{ test_db }}' + + - name: Create test subscription + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription2 }}' + login_db: '{{ test_db }}' + state: present + publications: '{{ test_pub2 }}' + connparams: + host: 127.0.0.1 + port: '{{ primary_port }}' + user: '{{ replication_role }}' + password: '{{ replication_pass }}' + dbname: '{{ test_db }}' + + - name: postgresql_info - create role to check session_role + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + login_port: '{{ replica_port }}' + login_user: "{{ pg_user }}" + name: session_superuser + role_attr_flags: SUPERUSER + + - name: postgresql_info - create extra DBs for testing + <<: *task_parameters + postgresql_db: + login_user: '{{ pg_user }}' + maintenance_db: postgres + login_port: '{{ replica_port }}' + name: "{{ item }}" + loop: + - db1 + - db2 + + - name: postgresql_info - create extra schemas for testing + <<: *task_parameters + postgresql_schema: + login_user: '{{ pg_user }}' + login_port: '{{ replica_port }}' + db: '{{ item[0] }}' + name: "{{ item[1] }}" + loop: + - [ "db1", "db1_schema1"] + - [ "db1", "db1_schema2"] + - [ "db2", "db2_schema1"] + - [ "db2", "db2_schema2"] + + - name: postgresql_table - create extra tables for testing + <<: *task_parameters + postgresql_table: + login_user: '{{ pg_user }}' + login_port: '{{ replica_port }}' + db: '{{ item[0] }}' + name: "{{ item[1] }}.{{ item[2] }}" + columns: waste_id int + loop: + - [ "db1", "db1_schema1", "db1_schema1_table1"] + - [ "db1", "db1_schema1", "db1_schema1_table2"] + - [ "db1", "db1_schema2", "db1_schema2_table1"] + - [ "db1", "db1_schema2", "db1_schema2_table2"] + - [ "db2", "db2_schema1", "db2_schema1_table1"] + - [ "db2", "db2_schema1", "db2_schema1_table2"] + - [ "db2", "db2_schema2", "db2_schema2_table1"] + - [ "db2", "db2_schema2", "db2_schema2_table2"] + + - name: postgresql_info - test return values and session_role param + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + login_port: '{{ replica_port }}' + session_role: session_superuser + + - assert: + that: + - result.version != {} + - result.version.raw is search('PostgreSQL') + - result.in_recovery == false + - result.databases.{{ db_default }}.collate + - result.databases.{{ db_default }}.languages + - result.databases.{{ db_default }}.namespaces + - result.databases.{{ db_default }}.extensions + - result.databases.{{ test_db }}.subscriptions.{{ test_subscription }} + - result.databases.{{ test_db }}.subscriptions.{{ test_subscription2 }} + + - result.databases.db1.namespaces.db1_schema1 + - result.databases.db1.namespaces.db1_schema2 + - result.databases.db2.namespaces.db2_schema1 + - result.databases.db2.namespaces.db2_schema2 + + - result.settings + - result.tablespaces + - result.roles + + - assert: + that: + - result.version.patch != {} + - result.version.full == '{{ result.version.major }}.{{ result.version.minor }}.{{ result.version.patch }}' + when: result.version.major == 9 + + - assert: + that: + - result.version.full == '{{ result.version.major }}.{{ result.version.minor }}' + when: result.version.major >= 10 + + - name: postgresql_info - check filter param passed by list + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + login_port: '{{ replica_port }}' + filter: + - ver* + - rol* + - in_recov* + + - assert: + that: + - result.version != {} + - result.roles + - result.in_recovery == false + - result.databases == {} + - result.repl_slots == {} + - result.replications == {} + - result.settings == {} + - result.tablespaces == {} + + - name: postgresql_info - check filter param passed by string + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + login_port: '{{ replica_port }}' + filter: ver*,role* + + - assert: + that: + - result.version != {} + - result.roles + - result.databases == {} + - result.repl_slots == {} + - result.replications == {} + - result.settings == {} + - result.tablespaces == {} + + - name: postgresql_info - check filter param passed by string + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + login_port: '{{ replica_port }}' + filter: ver* + + - assert: + that: + - result.version + - result.roles == {} + + - name: postgresql_info - check excluding filter param passed by list + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + login_port: '{{ replica_port }}' + filter: + - "!ver*" + - "!rol*" + - "!in_rec*" + + - assert: + that: + - result.version == {} + - result.in_recovery == None + - result.roles == {} + - result.databases + + - name: postgresql_info - test return publication info + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + login_db: '{{ test_db }}' + login_port: '{{ primary_port }}' + trust_input: true + + - assert: + that: + - result.version != {} + - result.in_recovery == false + - result.databases.{{ db_default }}.collate + - result.databases.{{ db_default }}.languages + - result.databases.{{ db_default }}.namespaces + - result.databases.{{ db_default }}.extensions + - result.databases.{{ test_db }}.publications.{{ test_pub }}.ownername == '{{ pg_user }}' + - result.databases.{{ test_db }}.publications.{{ test_pub2 }}.puballtables == true + - result.settings + - result.tablespaces + - result.roles + + - name: postgresql_info - test trust_input parameter + <<: *task_parameters + postgresql_info: + <<: *pg_parameters + login_db: '{{ test_db }}' + login_port: '{{ primary_port }}' + trust_input: false + session_role: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' + register: result + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg is search('is potentially dangerous') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/tasks/setup_publication.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/tasks/setup_publication.yml new file mode 100644 index 000000000..c68edd456 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_info/tasks/setup_publication.yml @@ -0,0 +1,64 @@ +# 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) +# Preparation for further tests of postgresql_subscription module. + +- vars: + task_parameters: &task_parameters + become_user: '{{ pg_user }}' + become: true + register: result + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: '{{ test_db }}' + connect_params: + connect_timeout: 30 + + block: + - name: Create test db + <<: *task_parameters + postgresql_db: + login_user: '{{ pg_user }}' + login_port: '{{ item }}' + maintenance_db: '{{ db_default }}' + name: '{{ test_db }}' + loop: + - '{{ primary_port }}' + - '{{ replica_port }}' + + - name: Create test role + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + login_port: '{{ item }}' + name: '{{ replication_role }}' + password: '{{ replication_pass }}' + role_attr_flags: LOGIN,REPLICATION + loop: + - '{{ primary_port }}' + - '{{ replica_port }}' + + - name: Create test table + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + login_port: '{{ item }}' + name: '{{ test_table1 }}' + columns: + - id int + loop: + - '{{ primary_port }}' + - '{{ replica_port }}' + + - name: Create publication + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + login_port: '{{ primary_port }}' + name: '{{ test_pub }}' + + - name: Create publication + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + login_port: '{{ primary_port }}' + name: '{{ test_pub2 }}' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/tasks/main.yml new file mode 100644 index 000000000..799501432 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/tasks/main.yml @@ -0,0 +1,25 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Include distribution specific variables + include_vars: "{{ lookup('first_found', params) }}" + vars: + params: + files: + - "{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml" + - default.yml + paths: + - vars + +# Only run on CentOS 7 because there is a stack trace on CentOS 8 because the module +# is looking for the incorrect version of plpython. +# https://gist.github.com/samdoran/8fc1b4ae834d3e66d1895d087419b8d8 +- name: Initial CI tests of postgresql_lang module + when: + - ansible_facts.distribution == 'CentOS' + - ansible_facts.distribution_major_version is version ('7', '==') + block: + - include_tasks: postgresql_lang_initial.yml + - include_tasks: postgresql_lang_add_owner_param.yml diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/tasks/postgresql_lang_add_owner_param.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/tasks/postgresql_lang_add_owner_param.yml new file mode 100644 index 000000000..a08ff82f2 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/tasks/postgresql_lang_add_owner_param.yml @@ -0,0 +1,199 @@ +# 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) + +- vars: + test_user1: alice + test_user2: bob + test_lang: plperl + non_existent_role: fake_role + task_parameters: &task_parameters + become_user: '{{ pg_user }}' + become: true + register: result + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: postgres + + block: + - name: Create roles for tests + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ item }}' + loop: + - '{{ test_user1 }}' + - '{{ test_user2 }}' + + - name: Create lang with owner in check_mode + <<: *task_parameters + postgresql_lang: + <<: *pg_parameters + name: '{{ test_lang }}' + owner: '{{ test_user1 }}' + trust_input: false + check_mode: true + + - assert: + that: + - result is changed + - result.queries == [] + + - name: Check that nothing was actually changed + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT r.rolname FROM pg_language l + JOIN pg_roles r ON l.lanowner = r.oid + WHERE l.lanname = '{{ test_lang }}' + AND r.rolname = '{{ test_user1 }}' + + - assert: + that: + - result.rowcount == 0 + + - name: Create lang with owner + <<: *task_parameters + postgresql_lang: + <<: *pg_parameters + name: '{{ test_lang }}' + owner: '{{ test_user1 }}' + trust_input: false + + - assert: + that: + - result is changed + - result.queries == ['CREATE LANGUAGE "{{ test_lang }}"', 'ALTER LANGUAGE "{{ test_lang }}" OWNER TO "{{ test_user1 }}"'] + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT r.rolname FROM pg_language l + JOIN pg_roles r ON l.lanowner = r.oid + WHERE l.lanname = '{{ test_lang }}' + AND r.rolname = '{{ test_user1 }}' + + - assert: + that: + - result.rowcount == 1 + + - name: Change lang owner in check_mode + <<: *task_parameters + postgresql_lang: + <<: *pg_parameters + name: '{{ test_lang }}' + owner: '{{ test_user2 }}' + trust_input: true + check_mode: true + + - assert: + that: + - result is changed + - result.queries == ['ALTER LANGUAGE "{{ test_lang }}" OWNER TO "{{ test_user2 }}"'] + + - name: Check that nothing was actually changed + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT r.rolname FROM pg_language l + JOIN pg_roles r ON l.lanowner = r.oid + WHERE l.lanname = '{{ test_lang }}' + AND r.rolname = '{{ test_user2 }}' + + - assert: + that: + - result.rowcount == 0 + + - name: Change lang owner + <<: *task_parameters + postgresql_lang: + <<: *pg_parameters + name: '{{ test_lang }}' + owner: '{{ test_user2 }}' + + - assert: + that: + - result is changed + # TODO: the first elem of the returned list below + # looks like a bug, not related with the option owner, needs to be checked + - result.queries == ["UPDATE pg_language SET lanpltrusted = false WHERE lanname = '{{ test_lang }}'", 'ALTER LANGUAGE "{{ test_lang }}" OWNER TO "{{ test_user2 }}"'] + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT r.rolname FROM pg_language l + JOIN pg_roles r ON l.lanowner = r.oid + WHERE l.lanname = '{{ test_lang }}' + AND r.rolname = '{{ test_user2 }}' + + - assert: + that: + - result.rowcount == 1 + + - name: Try to change lang owner again to the same role + <<: *task_parameters + postgresql_lang: + <<: *pg_parameters + name: '{{ test_lang }}' + owner: '{{ test_user2 }}' + + - assert: + that: + - result is not changed + - result.queries == [] + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT r.rolname FROM pg_language l + JOIN pg_roles r ON l.lanowner = r.oid + WHERE l.lanname = '{{ test_lang }}' + AND r.rolname = '{{ test_user2 }}' + + - assert: + that: + - result.rowcount == 1 + + - name: Drop test lang with owner, must ignore + <<: *task_parameters + postgresql_lang: + <<: *pg_parameters + name: '{{ test_lang }}' + state: absent + owner: '{{ non_existent_role }}' + + - assert: + that: + - result is changed + - result.queries == ["DROP LANGUAGE \"{{ test_lang }}\""] + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT r.rolname FROM pg_language l + JOIN pg_roles r ON l.lanowner = r.oid + WHERE l.lanname = '{{ test_lang }}' + + - assert: + that: + - result.rowcount == 0 + + # Clean up + - name: Drop test roles + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ item }}' + state: absent + loop: + - '{{ test_user1 }}' + - '{{ test_user2 }}' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/tasks/postgresql_lang_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/tasks/postgresql_lang_initial.yml new file mode 100644 index 000000000..1d24778b4 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/tasks/postgresql_lang_initial.yml @@ -0,0 +1,231 @@ +# 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) + +# Preparation for tests: +- name: Install PostgreSQL support packages + become: true + action: "{{ ansible_facts.pkg_mgr }}" + args: + name: "{{ postgresql_lang_packages }}" + state: present + +############### +# Do main tests +# + +# Create language in check_mode: +- name: postgresql_lang - create plperl in check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: plperl + register: result + ignore_errors: true + check_mode: true + +- assert: + that: + - result is changed + - result.queries == [] + +- name: postgresql_lang - check that lang doesn't exist after previous step, rowcount must be 0 + become_user: "{{ pg_user }}" + become: true + 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: true + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: plperl + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.queries == ['CREATE LANGUAGE "plperl"'] + +- name: postgresql_lang - check that lang exists after previous step + become_user: "{{ pg_user }}" + become: true + 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: true + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: plperl + state: absent + register: result + ignore_errors: true + check_mode: true + +- assert: + that: + - result is changed + - result.queries == [] + +- name: postgresql_lang - check that lang exists after previous step, rowcount must be 1 + become_user: "{{ pg_user }}" + become: true + 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: true + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: plperl + state: absent + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - 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: true + 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 true +- name: postgresql_lang - drop c language to check fail_on_drop true + become_user: "{{ pg_user }}" + become: true + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: c + state: absent + fail_on_drop: true + register: result + ignore_errors: true + +- 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: true + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: c + state: absent + fail_on_drop: false + register: result + ignore_errors: true + +- assert: + that: + - result.failed == false + +# Create trusted language: +- name: postgresql_lang - create plpythonu + become_user: "{{ pg_user }}" + become: true + postgresql_lang: + db: postgres + login_user: "{{ pg_user }}" + name: plpythonu + trust: true + force_trust: true + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - 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: true + 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: true + postgresql_lang: + login_db: postgres + login_user: "{{ pg_user }}" + login_port: 5432 + lang: plpythonu + state: absent + cascade: true + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - 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: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_language WHERE lanname = 'plpythonu'" + register: result + +- assert: + that: + - result.rowcount == 0 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/vars/CentOS-7.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/vars/CentOS-7.yml new file mode 100644 index 000000000..8d4bcc7e2 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/vars/CentOS-7.yml @@ -0,0 +1,3 @@ +postgresql_lang_packages: + - postgresql-plperl + - postgresql-plpython diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/vars/CentOS-8.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/vars/CentOS-8.yml new file mode 100644 index 000000000..5da004c8f --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/vars/CentOS-8.yml @@ -0,0 +1,3 @@ +postgresql_lang_packages: + - postgresql-plperl + - postgresql-plpython3 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/vars/default.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/vars/default.yml new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_lang/vars/default.yml diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/defaults/main.yml new file mode 100644 index 000000000..7b1d49e44 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/defaults/main.yml @@ -0,0 +1,6 @@ +test_group1: group1 +test_group2: group2 +test_group3: group.with.dots +test_user1: user1 +test_user2: user.with.dots +dangerous_name: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/tasks/main.yml new file mode 100644 index 000000000..ea058d084 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/tasks/main.yml @@ -0,0 +1,7 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_membership module +- import_tasks: postgresql_membership_initial.yml diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/tasks/postgresql_membership_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/tasks/postgresql_membership_initial.yml new file mode 100644 index 000000000..3c8ef17a8 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_membership/tasks/postgresql_membership_initial.yml @@ -0,0 +1,736 @@ +# 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) + +#################### +# Prepare for tests: + +# Create test roles: +- name: postgresql_membership - create test roles + become_user: "{{ pg_user }}" + become: true + postgresql_user: + login_user: "{{ pg_user }}" + db: postgres + name: "{{ item }}" + ignore_errors: true + with_items: + - "{{ test_group1 }}" + - "{{ test_group2 }}" + - "{{ test_group3 }}" + - "{{ test_user1 }}" + - "{{ test_user2 }}" + +################ +# Do main tests: + +### Test check_mode +# Grant test_group1 to test_user1 in check_mode: +- name: postgresql_membership - grant test_group1 to test_user1 in check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: "{{ test_group1 }}" + user: "{{ test_user1 }}" + state: present + register: result + ignore_errors: true + check_mode: true + +- assert: + that: + - result is changed + - result.groups == ["{{ test_group1 }}"] + - result.queries == ["GRANT \"{{ test_group1 }}\" TO \"{{ test_user1 }}\""] + - result.granted.{{ test_group1 }} == ["{{ test_user1 }}"] + - result.state == "present" + - result.target_roles == ["{{ test_user1 }}"] + +# Try to revoke test_group1 from test_user1 to check that +# nothing actually changed in check_mode at the previous step: +- name: postgresql_membership - try to revoke test_group1 from test_user1 for checking check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: "{{ test_group1 }}" + user: "{{ test_user1 }}" + state: absent + register: result + ignore_errors: true + check_mode: true + +- assert: + that: + - result is not changed + - result.groups == ["{{ test_group1 }}"] + - result.queries == [] + - result.revoked.{{ test_group1 }} == [] + - result.state == "absent" + - result.target_roles == ["{{ test_user1 }}"] +### End of test check_mode + +# Grant test_group1 to test_user1: +- name: postgresql_membership - grant test_group1 to test_user1 + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: "{{ test_group1 }}" + user: "{{ test_user1 }}" + state: present + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.groups == ["{{ test_group1 }}"] + - result.queries == ["GRANT \"{{ test_group1 }}\" TO \"{{ test_user1 }}\""] + - result.granted.{{ test_group1 }} == ["{{ test_user1 }}"] + - result.state == "present" + - result.target_roles == ["{{ test_user1 }}"] + +# Grant test_group1 to test_user1 again to check that nothing changes: +- name: postgresql_membership - grant test_group1 to test_user1 again + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: "{{ test_group1 }}" + user: "{{ test_user1 }}" + state: present + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.groups == ["{{ test_group1 }}"] + - result.queries == [] + - result.granted.{{ test_group1 }} == [] + - result.state == "present" + - result.target_roles == ["{{ test_user1 }}"] + +# Revoke test_group1 from test_user1: +- name: postgresql_membership - revoke test_group1 from test_user1 + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: "{{ test_group1 }}" + user: "{{ test_user1 }}" + state: absent + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.groups == ["{{ test_group1 }}"] + - result.queries == ["REVOKE \"{{ test_group1 }}\" FROM \"{{ test_user1 }}\""] + - result.revoked.{{ test_group1 }} == ["{{ test_user1 }}"] + - result.state == "absent" + - result.target_roles == ["{{ test_user1 }}"] + +# Revoke test_group1 from test_user1 again to check that nothing changes: +- name: postgresql_membership - revoke test_group1 from test_user1 again + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: "{{ test_group1 }}" + user: "{{ test_user1 }}" + state: absent + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.groups == ["{{ test_group1 }}"] + - result.queries == [] + - result.revoked.{{ test_group1 }} == [] + - result.state == "absent" + - result.target_roles == ["{{ test_user1 }}"] + +# Grant test_group1 and test_group2 to test_user1 and test_user2: +- name: postgresql_membership - grant two groups to two users + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: + - "{{ test_group1 }}" + - "{{ test_group2 }}" + user: + - "{{ test_user1 }}" + - "{{ test_user2 }}" + state: present + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.groups == ["{{ test_group1 }}", "{{ test_group2 }}"] + - result.queries == ["GRANT \"{{ test_group1 }}\" TO \"{{ test_user1 }}\"", "GRANT \"{{ test_group1 }}\" TO \"{{ test_user2 }}\"", "GRANT \"{{ test_group2 }}\" TO \"{{ test_user1 }}\"", "GRANT \"{{ test_group2 }}\" TO \"{{ test_user2 }}\""] + - result.granted.{{ test_group1 }} == ["{{ test_user1 }}", "{{ test_user2 }}"] + - result.granted.{{ test_group2 }} == ["{{ test_user1 }}", "{{ test_user2 }}"] + - result.state == "present" + - result.target_roles == ["{{ test_user1 }}", "{{ test_user2 }}"] + +# Grant test_group1 and test_group2 to test_user1 and test_user2 again to check that nothing changes: +- name: postgresql_membership - grant two groups to two users again + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: + - "{{ test_group1 }}" + - "{{ test_group2 }}" + user: + - "{{ test_user1 }}" + - "{{ test_user2 }}" + state: present + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.groups == ["{{ test_group1 }}", "{{ test_group2 }}"] + - result.queries == [] + - result.granted.{{ test_group1 }} == [] + - result.granted.{{ test_group2 }} == [] + - result.state == "present" + - result.target_roles == ["{{ test_user1 }}", "{{ test_user2 }}"] + +# Revoke only test_group1 from test_user1: +- name: postgresql_membership - revoke one group from one user + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: "{{ test_group1 }}" + user: "{{ test_user1 }}" + state: absent + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.groups == ["{{ test_group1 }}"] + - result.queries == ["REVOKE \"{{ test_group1 }}\" FROM \"{{ test_user1 }}\""] + - result.revoked.{{ test_group1 }} == ["{{ test_user1 }}"] + - result.state == "absent" + - result.target_roles == ["{{ test_user1 }}"] + +# Try to grant test_group1 and test_group2 to test_user1 and test_user2 again +# to check that nothing changes with test_user2: +- name: postgresql_membership - grant two groups to two users again + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: + - "{{ test_group1 }}" + - "{{ test_group2 }}" + user: + - "{{ test_user1 }}" + - "{{ test_user2 }}" + state: present + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.groups == ["{{ test_group1 }}", "{{ test_group2 }}"] + - result.queries == ["GRANT \"{{ test_group1 }}\" TO \"{{ test_user1 }}\""] + - result.granted.{{ test_group1 }} == ["{{ test_user1 }}"] + - result.granted.{{ test_group2 }} == [] + - result.state == "present" + - result.target_roles == ["{{ test_user1 }}", "{{ test_user2 }}"] + +##################### +# Check fail_on_role: + +# Try to grant non existent group to non existent role with fail_on_role=true: +- name: postgresql_membership - revoke non existen group from non existen role + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: fake_group + user: fake_user + state: present + fail_on_role: true + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + +# Try to grant non existent group to non existent role with fail_on_role=no: +- name: postgresql_membership - revoke non existen group from non existen role + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: fake_group + user: fake_user + state: present + fail_on_role: false + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.granted == {} + - result.groups == [] + - result.target_roles == [] + - result.state == 'present' + +# Try to revoke non existent group from non existent role with fail_on_role=no: +- name: postgresql_membership - revoke non existen group from non existen role + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: fake_group + user: fake_user + state: absent + fail_on_role: false + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.revoked == {} + - result.groups == [] + - result.target_roles == [] + - result.state == 'absent' + +# Grant test_group3 with a name containing dots to test_user1. +- name: postgresql_membership - grant test_group3 with dots to test_user1 + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: "{{ test_group3 }}" + user: "{{ test_user1 }}" + state: present + register: result + +- assert: + that: + - result is changed + - result.queries == ["GRANT \"{{ test_group3 }}\" TO \"{{ test_user1 }}\""] + +############################# +# Check trust_input parameter + +- name: postgresql_membership - try to use dangerous input, don't trust + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: + - "{{ test_group3}}" + - "{{ dangerous_name }}" + user: "{{ test_user1 }}" + state: present + trust_input: false + register: result + ignore_errors: true + +- assert: + that: + - result is failed + - result.msg == 'Passed input \'{{ dangerous_name }}\' is potentially dangerous' + +- name: postgresql_membership - try to use dangerous input, trust explicitly + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + group: + - "{{ test_group3}}" + - "{{ dangerous_name }}" + user: "{{ test_user1 }}" + state: present + trust_input: true + register: result + ignore_errors: true + +- assert: + that: + - result is failed + - result.msg == 'Role {{ dangerous_name }} does not exist' + +######################## +# Tests for match method +- name: Revoke all groups from a role check mode + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + groups: [] + user: "{{ test_user1 }}" + state: exact + register: result + check_mode: true + +- assert: + that: + - result is changed + - result.queries == ["REVOKE \"{{ test_group1 }}\" FROM \"{{ test_user1 }}\"", "REVOKE \"{{ test_group2 }}\" FROM \"{{ test_user1 }}\"", "REVOKE \"{{ test_group3 }}\" FROM \"{{ test_user1 }}\""], + - result.revoked["group.with.dots"] == ["{{ test_user1 }}"] + - result.revoked["group1"] == ["{{ test_user1 }}"] + - result.revoked["group2"] == ["{{ test_user1 }}"] + - result.granted == {} + + +- name: Check result of prev task + become_user: "{{ pg_user }}" + become: true + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "SELECT ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) FROM pg_catalog.pg_roles r WHERE r.rolname = '{{ test_user1 }}'" + register: result + +- assert: + that: + - result.query_result.0.array == ['{{ test_group1 }}', '{{ test_group2 }}', '{{ test_group3 }}'] + +- name: Revoke all groups from a role actual mode + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + groups: [] + user: "{{ test_user1 }}" + state: exact + register: result + +- assert: + that: + - result is changed + - result.queries == ["REVOKE \"{{ test_group1 }}\" FROM \"{{ test_user1 }}\"", "REVOKE \"{{ test_group2 }}\" FROM \"{{ test_user1 }}\"", "REVOKE \"{{ test_group3 }}\" FROM \"{{ test_user1 }}\""], + - result.revoked["group.with.dots"] == ["{{ test_user1 }}"] + - result.revoked["group1"] == ["{{ test_user1 }}"] + - result.revoked["group2"] == ["{{ test_user1 }}"] + - result.granted == {} + + +- name: Check result of prev task + become_user: "{{ pg_user }}" + become: true + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "SELECT ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) FROM pg_catalog.pg_roles r WHERE r.rolname = '{{ test_user1 }}'" + register: result + +- assert: + that: + - result.query_result.0.array == [] + +- name: Grant all groups to a role check mode + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + groups: + - "{{ test_group1 }}" + - "{{ test_group2 }}" + - "{{ test_group3 }}" + user: "{{ test_user1 }}" + state: exact + register: result + check_mode: true + +- assert: + that: + - result is changed + - result.queries == ["GRANT \"{{ test_group1 }}\" TO \"{{ test_user1 }}\"", "GRANT \"{{ test_group2 }}\" TO \"{{ test_user1 }}\"", "GRANT \"{{ test_group3 }}\" TO \"{{ test_user1 }}\""], + - result.granted["group.with.dots"] == ["{{ test_user1 }}"] + - result.granted["group1"] == ["{{ test_user1 }}"] + - result.granted["group2"] == ["{{ test_user1 }}"] + - result.revoked == {} + + +- name: Check result of prev task + become_user: "{{ pg_user }}" + become: true + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "SELECT ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) FROM pg_catalog.pg_roles r WHERE r.rolname = '{{ test_user1 }}'" + register: result + +- assert: + that: + - result.query_result.0.array == [] + +- name: Grant all groups to a role real mode + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + groups: + - "{{ test_group1 }}" + - "{{ test_group2 }}" + - "{{ test_group3 }}" + user: "{{ test_user1 }}" + state: exact + register: result + +- assert: + that: + - result is changed + - result.queries == ["GRANT \"{{ test_group1 }}\" TO \"{{ test_user1 }}\"", "GRANT \"{{ test_group2 }}\" TO \"{{ test_user1 }}\"", "GRANT \"{{ test_group3 }}\" TO \"{{ test_user1 }}\""], + - result.granted["group.with.dots"] == ["{{ test_user1 }}"] + - result.granted["group1"] == ["{{ test_user1 }}"] + - result.granted["group2"] == ["{{ test_user1 }}"] + - result.revoked == {} + + +- name: Check result of prev task + become_user: "{{ pg_user }}" + become: true + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "SELECT ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) FROM pg_catalog.pg_roles r WHERE r.rolname = '{{ test_user1 }}'" + register: result + +- assert: + that: + - result.query_result.0.array == ['{{ test_group1 }}', '{{ test_group2 }}', '{{ test_group3 }}'] + +- name: Change groups 1 + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + groups: + - "{{ test_group1 }}" + user: "{{ test_user1 }}" + state: exact + register: result + +- assert: + that: + - result is changed + - result.queries == ["REVOKE \"{{ test_group2 }}\" FROM \"{{ test_user1 }}\"", "REVOKE \"{{ test_group3 }}\" FROM \"{{ test_user1 }}\""], + - result.revoked["group.with.dots"] == ["{{ test_user1 }}"] + - result.revoked["group2"] == ["{{ test_user1 }}"] + - result.granted == {} + + +- name: Check result of prev task + become_user: "{{ pg_user }}" + become: true + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "SELECT ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) FROM pg_catalog.pg_roles r WHERE r.rolname = '{{ test_user1 }}'" + register: result + +- assert: + that: + - result.query_result.0.array == ['{{ test_group1 }}'] + +- name: Change groups 2 + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + groups: + - "{{ test_group2 }}" + - "{{ test_group3 }}" + user: "{{ test_user1 }}" + state: exact + register: result + +- assert: + that: + - result is changed + - result.queries == ["REVOKE \"{{ test_group1 }}\" FROM \"{{ test_user1 }}\"", "GRANT \"{{ test_group2 }}\" TO \"{{ test_user1 }}\"", "GRANT \"{{ test_group3 }}\" TO \"{{ test_user1 }}\""], + - result.granted["group.with.dots"] == ["{{ test_user1 }}"] + - result.granted["group2"] == ["{{ test_user1 }}"] + - result.revoked["group1"] == ["{{ test_user1 }}"] + +- name: Check result of prev task + become_user: "{{ pg_user }}" + become: true + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "SELECT ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) FROM pg_catalog.pg_roles r WHERE r.rolname = '{{ test_user1 }}'" + register: result + +- assert: + that: + - result.query_result.0.array == ['{{ test_group2 }}', '{{ test_group3 }}'] + +- name: Change groups 2 again + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + groups: + - "{{ test_group2 }}" + - "{{ test_group3 }}" + user: "{{ test_user1 }}" + state: exact + register: result + +- assert: + that: + - result is not changed + - result.queries == [], + - result.granted == {} + - result.revoked == {} + + +- name: Check result of prev task + become_user: "{{ pg_user }}" + become: true + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "SELECT ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) FROM pg_catalog.pg_roles r WHERE r.rolname = '{{ test_user1 }}'" + register: result + +- assert: + that: + - result.query_result.0.array == ['{{ test_group2 }}', '{{ test_group3 }}'] + +- name: Change groups for two users + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + groups: + - "{{ test_group1 }}" + - "{{ test_group2 }}" + - "{{ test_group3 }}" + users: + - "{{ test_user1 }}" + - "{{ test_user2 }}" + state: exact + register: result + +- assert: + that: + - result is changed + - result.queries == ["GRANT \"{{ test_group1 }}\" TO \"{{ test_user1 }}\"", "GRANT \"{{ test_group3 }}\" TO \"{{ test_user2 }}\""], + - result.granted["group1"] == ["{{ test_user1 }}"] + - result.granted["group.with.dots"] == ["{{ test_user2 }}"] + - result.revoked == {} + +- name: Check result of prev task for {{ test_user1 }} + become_user: "{{ pg_user }}" + become: true + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "SELECT ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) FROM pg_catalog.pg_roles r WHERE r.rolname = '{{ test_user1 }}'" + register: result + +- assert: + that: + - result.query_result.0.array == ['{{ test_group1 }}', '{{ test_group2 }}', '{{ test_group3 }}'] + +- name: Check result of prev task for {{ test_user2 }} + become_user: "{{ pg_user }}" + become: true + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "SELECT ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) FROM pg_catalog.pg_roles r WHERE r.rolname = '{{ test_user2 }}'" + register: result + +- assert: + that: + - result.query_result.0.array == ['{{ test_group1 }}', '{{ test_group2 }}', '{{ test_group3 }}'] + +- name: Change groups for two users 2 + become_user: "{{ pg_user }}" + become: true + postgresql_membership: + login_user: "{{ pg_user }}" + db: postgres + groups: + - "{{ test_group2 }}" + - "{{ test_group3 }}" + users: + - "{{ test_user1 }}" + - "{{ test_user2 }}" + state: exact + register: result + +- assert: + that: + - result is changed + - result.queries == ["REVOKE \"{{ test_group1 }}\" FROM \"{{ test_user1 }}\"", "REVOKE \"{{ test_group1 }}\" FROM \"{{ test_user2 }}\""], + - result.revoked["group1"] == ["{{ test_user1 }}", "{{ test_user2 }}"] + - result.granted == {} + +- name: Check result of prev task for {{ test_user1 }} + become_user: "{{ pg_user }}" + become: true + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "SELECT ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) FROM pg_catalog.pg_roles r WHERE r.rolname = '{{ test_user1 }}'" + register: result + +- assert: + that: + - result.query_result.0.array == ['{{ test_group2 }}', '{{ test_group3 }}'] + +- name: Check result of prev task for {{ test_user2 }} + become_user: "{{ pg_user }}" + become: true + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "SELECT ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) FROM pg_catalog.pg_roles r WHERE r.rolname = '{{ test_user2 }}'" + register: result + +- assert: + that: + - result.query_result.0.array == ['{{ test_group2 }}', '{{ test_group3 }}'] diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/defaults/main.yml new file mode 100644 index 000000000..e43723c47 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/defaults/main.yml @@ -0,0 +1,3 @@ +test_tablespace_path: "/ssd" + +dangerous_name: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/tasks/main.yml new file mode 100644 index 000000000..4b2f57510 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/tasks/main.yml @@ -0,0 +1,9 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_owner module +- import_tasks: postgresql_owner_initial.yml + when: + - postgres_version_resp.stdout is version('9.4', '>=') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/tasks/postgresql_owner_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/tasks/postgresql_owner_initial.yml new file mode 100644 index 000000000..a21160282 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_owner/tasks/postgresql_owner_initial.yml @@ -0,0 +1,1073 @@ +# 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) + +#################### +# Prepare for tests: + +# Create test roles: +- name: postgresql_owner - create test roles + become_user: '{{ pg_user }}' + become: true + postgresql_user: + login_user: '{{ pg_user }}' + db: postgres + name: '{{ item }}' + ignore_errors: true + with_items: + - alice + - bob + +- name: postgresql_owner - create test database + become_user: '{{ pg_user }}' + become: true + postgresql_db: + login_user: '{{ pg_user }}' + db: acme + +- name: postgresql_owner - create test table + become_user: '{{ pg_user }}' + become: true + 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: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: my_table + obj_type: table + +- name: postgresql_owner - create test sequence + become_user: '{{ pg_user }}' + become: true + postgresql_query: + login_user: '{{ pg_user }}' + db: acme + query: CREATE SEQUENCE test_seq + +- name: postgresql_owner - create test function + become_user: '{{ pg_user }}' + become: true + 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; + +- name: postgresql_owner - create test schema + become_user: '{{ pg_user }}' + become: true + postgresql_query: + login_user: '{{ pg_user }}' + db: acme + query: CREATE SCHEMA test_schema + +- name: postgresql_owner - create test view + become_user: '{{ pg_user }}' + become: true + postgresql_query: + login_user: '{{ pg_user }}' + db: acme + query: CREATE VIEW test_view AS SELECT * FROM my_table + +- name: postgresql_owner - create test materialized view + become_user: '{{ pg_user }}' + become: true + 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', '>=') + +- name: postgresql_owner - drop dir for test tablespace + become: true + file: + path: '{{ test_tablespace_path }}' + state: absent + ignore_errors: true + +- name: postgresql_owner - disable selinux + become: true + shell: setenforce 0 + ignore_errors: true + +- name: postgresql_owner - create dir for test tablespace + become: true + file: + path: '{{ test_tablespace_path }}' + state: directory + owner: '{{ pg_user }}' + group: '{{ pg_user }}' + mode: '0700' + ignore_errors: true + +- name: > + postgresql_owner - create a new tablespace called acme and + set bob as an its owner + become_user: '{{ pg_user }}' + become: true + postgresql_tablespace: + db: acme + login_user: '{{ pg_user }}' + name: acme + owner: alice + location: '{{ test_tablespace_path }}' + +################ +# 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: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: non_existent + reassign_owned_by: bob + register: result + ignore_errors: true + +- assert: + that: + - result.failed == true + +- name: postgresql_owner - reassign_owned_by, check fail_on_role + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: alice + reassign_owned_by: non_existent + fail_on_role: false + register: result + +- assert: + that: + - result.failed == false + +- name: postgresql_owner - reassign_owned_by in check_mode + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: alice + reassign_owned_by: bob + check_mode: true + register: result + +- assert: + that: + - result is changed + - result.queries == ['REASSIGN OWNED BY "bob" TO "alice"'] + +- name: postgresql_owner - check that nothing changed after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: acme + login_user: '{{ pg_user }}' + query: > + SELECT 1 FROM pg_tables WHERE tablename = 'my_table' + AND tableowner = 'alice' + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +- name: postgresql_owner - reassign_owned_by + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: alice + reassign_owned_by: bob + trust_input: true + register: result + +- assert: + that: + - result is changed + - result.queries == ['REASSIGN OWNED BY "bob" TO "alice"'] + +- name: postgresql_owner - check that ownership has been changed after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: acme + login_user: '{{ pg_user }}' + query: SELECT 1 FROM pg_tables WHERE tablename = 'my_table' AND tableowner = 'alice' + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +########################### +# Test trust_inpt parameter + +- name: postgresql_owner - reassign_owned_by, trust_input false + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: '{{ dangerous_name }}' + reassign_owned_by: alice + trust_input: false + register: result + ignore_errors: true + +- assert: + that: + - result is failed + - result.msg == 'Passed input \'{{ dangerous_name }}\' is potentially dangerous' + +- name: postgresql_owner - reassign_owned_by, trust_input true by default + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: '{{ dangerous_name }}' + reassign_owned_by: alice + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.msg is search('does not exist') +# End of testing trust_input + +# +# 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: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: acme + obj_type: database + check_mode: true + register: result + +- assert: + that: + - result is changed + - result.queries == ['ALTER DATABASE "acme" OWNER TO "bob"'] + +- name: postgresql_owner - check that nothing changed after the previous step + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + +- assert: + that: + - result.rowcount == 0 + +- name: postgresql_owner - set db owner + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: acme + obj_type: database + register: result + +- assert: + that: + - result is changed + - result.queries == ['ALTER DATABASE "acme" OWNER TO "bob"'] + +- name: postgresql_owner - check that db owner has been changed after the previous step + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_owner - set db owner again + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: acme + obj_type: database + register: result + +- assert: + that: + - result is not changed + - result.queries == [] + +- name: postgresql_owner - check that db owner is bob + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_owner - set table owner in check_mode + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: my_table + obj_type: table + check_mode: true + register: result + +- assert: + that: + - result is changed + - result.queries == ['ALTER TABLE "my_table" OWNER TO "bob"'] + +- name: postgresql_owner - check that nothing changed after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: acme + login_user: '{{ pg_user }}' + query: > + SELECT 1 FROM pg_tables WHERE tablename = 'my_table' + AND tableowner = 'bob' + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +- name: postgresql_owner - set db owner + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: my_table + obj_type: table + register: result + +- assert: + that: + - result is changed + - result.queries == ['ALTER TABLE "my_table" OWNER TO "bob"'] + +- name: postgresql_owner - check that table owner has been changed after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: acme + login_user: '{{ pg_user }}' + query: > + SELECT 1 FROM pg_tables WHERE tablename = 'my_table' + AND tableowner = 'bob' + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_owner - set db owner again + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: my_table + obj_type: table + register: result + +- assert: + that: + - result is not changed + - result.queries == [] + +- name: postgresql_owner - check that table owner is bob + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: acme + login_user: '{{ pg_user }}' + query: > + SELECT 1 FROM pg_tables WHERE tablename = 'my_table' + AND tableowner = 'bob' + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_owner - set sequence owner in check_mode + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: test_seq + obj_type: sequence + check_mode: true + register: result + +- assert: + that: + - result is changed + - result.queries == ['ALTER SEQUENCE "test_seq" OWNER TO "bob"'] + +- name: postgresql_owner - check that nothing changed after the previous step + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + +- assert: + that: + - result.rowcount == 0 + +- name: postgresql_owner - set db owner + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: test_seq + obj_type: sequence + register: result + +- assert: + that: + - result is changed + - result.queries == ['ALTER SEQUENCE "test_seq" OWNER TO "bob"'] + +- name: postgresql_owner - check that table owner has been changed after the previous step + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_owner - set db owner again + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: test_seq + obj_type: sequence + register: result + +- assert: + that: + - result is not changed + - result.queries == [] + +- name: postgresql_owner - check that sequence owner is bob + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_owner - set function owner in check_mode + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: increment + obj_type: function + check_mode: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result is changed + - result.queries == ['ALTER FUNCTION increment OWNER TO "bob"'] + when: postgres_version_resp.stdout is version('10', '>=') + +- name: postgresql_owner - check that nothing changed after the previous step + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.rowcount == 0 + when: postgres_version_resp.stdout is version('10', '>=') + +- name: postgresql_owner - set func owner + become_user: '{{ pg_user }}' + become: true + 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 is changed + - result.queries == ['ALTER FUNCTION increment OWNER TO "bob"'] + when: postgres_version_resp.stdout is version('10', '>=') + +- name: postgresql_owner - check that func owner has been changed after the previous step + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('10', '>=') + +- name: postgresql_owner - set func owner again + become_user: '{{ pg_user }}' + become: true + 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 is not changed + - result.queries == [] + when: postgres_version_resp.stdout is version('10', '>=') + +- name: postgresql_owner - check that function owner is bob + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('10', '>=') + +- name: postgresql_owner - set schema owner in check_mode + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: test_schema + obj_type: schema + check_mode: true + register: result + +- assert: + that: + - result is changed + - result.queries == ['ALTER SCHEMA "test_schema" OWNER TO "bob"'] + +- name: postgresql_owner - check that nothing changed after the previous step + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + +- assert: + that: + - result.rowcount == 0 + +- name: postgresql_owner - set schema owner + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: test_schema + obj_type: schema + register: result + +- assert: + that: + - result is changed + - result.queries == ['ALTER SCHEMA "test_schema" OWNER TO "bob"'] + +- name: postgresql_owner - check that schema owner has been changed after the previous step + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_owner - set schema owner again + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: test_seq + obj_type: sequence + register: result + +- assert: + that: + - result is not changed + - result.queries == [] + +- name: postgresql_owner - check that schema owner is bob + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_owner - set view owner in check_mode + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: test_view + obj_type: view + check_mode: true + register: result + +- assert: + that: + - result is changed + - result.queries == ['ALTER VIEW "test_view" OWNER TO "bob"'] + +- name: postgresql_owner - check that nothing changed after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: acme + login_user: '{{ pg_user }}' + query: SELECT 1 FROM pg_views WHERE viewname = 'test_view' AND viewowner = 'bob' + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +- name: postgresql_owner - set view owner + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: test_view + obj_type: view + register: result + +- assert: + that: + - result is changed + - result.queries == ['ALTER VIEW "test_view" OWNER TO "bob"'] + +- name: postgresql_owner - check that view owner has been changed after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: acme + login_user: '{{ pg_user }}' + query: SELECT 1 FROM pg_views WHERE viewname = 'test_view' AND viewowner = 'bob' + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_owner - set view owner again + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: test_view + obj_type: view + register: result + +- assert: + that: + - result is not changed + - result.queries == [] + +- name: postgresql_owner - check that view owner is bob + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: acme + login_user: '{{ pg_user }}' + query: SELECT 1 FROM pg_views WHERE viewname = 'test_view' AND viewowner = 'bob' + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_owner - set matview owner in check_mode + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: test_mat_view + obj_type: matview + check_mode: true + register: result + when: postgres_version_resp.stdout is version('9.4', '>=') + +- assert: + that: + - result is changed + - result.queries == ['ALTER MATERIALIZED VIEW "test_mat_view" OWNER TO "bob"'] + when: postgres_version_resp.stdout is version('9.4', '>=') + +- name: postgresql_owner - check that nothing changed after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: acme + login_user: '{{ pg_user }}' + query: SELECT 1 FROM pg_matviews WHERE matviewname = 'test_view' AND matviewowner = 'bob' + ignore_errors: true + 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', '>=') + +- name: postgresql_owner - set matview owner + become_user: '{{ pg_user }}' + become: true + 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 is changed + - result.queries == ['ALTER MATERIALIZED VIEW "test_mat_view" OWNER TO "bob"'] + when: postgres_version_resp.stdout is version('9.4', '>=') + +- name: postgresql_owner - check that matview owner has been changed after the previous step + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: acme + login_user: '{{ pg_user }}' + query: SELECT 1 FROM pg_matviews WHERE matviewname = 'test_mat_view' AND matviewowner = 'bob' + ignore_errors: true + 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', '>=') + +- name: postgresql_owner - set matview owner again + become_user: '{{ pg_user }}' + become: true + 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 is not changed + - result.queries == [] + when: postgres_version_resp.stdout is version('9.4', '>=') + +- name: postgresql_owner - check that matview owner is bob + become_user: '{{ pg_user }}' + become: true + postgresql_query: + db: acme + login_user: '{{ pg_user }}' + query: SELECT 1 FROM pg_matviews WHERE matviewname = 'test_mat_view' AND matviewowner = 'bob' + ignore_errors: true + 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', '>=') + +- name: postgresql_owner - set tablespace owner in check_mode + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: acme + obj_type: tablespace + check_mode: true + register: result + +- assert: + that: + - result is changed + - result.queries == ['ALTER TABLESPACE "acme" OWNER TO "bob"'] + +- name: postgresql_owner - check that nothing changed after the previous step + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + +- assert: + that: + - result.rowcount == 0 + +- name: postgresql_owner - set tablespace owner + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: acme + obj_type: tablespace + register: result + +- assert: + that: + - result is changed + - result.queries == ['ALTER TABLESPACE "acme" OWNER TO "bob"'] + +- name: postgresql_owner - check that tablespace owner has been changed after the previous step + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_owner - set tablespace owner again + become_user: '{{ pg_user }}' + become: true + postgresql_owner: + login_user: '{{ pg_user }}' + db: acme + new_owner: bob + obj_name: acme + obj_type: tablespace + register: result + +- assert: + that: + - result is not changed + - result.queries == [] + +- name: postgresql_owner - check that tablespace owner is bob + become_user: '{{ pg_user }}' + become: true + 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: true + register: result + +- assert: + that: + - result.rowcount == 1 + +# +# Crean up +# +- name: postgresql_owner - drop test database + become_user: '{{ pg_user }}' + become: true + postgresql_db: + login_user: '{{ pg_user }}' + db: acme + state: absent + +- name: postgresql_owner - drop test tablespace + become_user: '{{ pg_user }}' + become: true + postgresql_tablespace: + db: postgres + login_user: '{{ pg_user }}' + name: acme + state: absent diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/defaults/main.yml new file mode 100644 index 000000000..94df93f9e --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/defaults/main.yml @@ -0,0 +1,29 @@ +--- +pg_hba_test_ips: +- contype: local + users: 'all,postgres,test' +- source: '0000:ffff::' + netmask: 'ffff:fff0::' +- source: '192.168.0.0/24' + netmask: '' + databases: 'all,replication' +- source: '192.168.1.0/24' + netmask: '' + databases: 'all' + method: reject +- source: '127.0.0.1/32' + netmask: '' +- source: '::1/128' + netmask: '' +- source: '0000:ff00::' + netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00' + method: scram-sha-256 +- source: '172.16.0.0' + netmask: '255.255.0.0' + method: trust +- contype: hostgssenc + users: postgres + source: '2001:db8::1/128' +- contype: hostnogssenc + users: all + source: '2001:db8::1/128' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/tasks/main.yml new file mode 100644 index 000000000..af51cbca9 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/tasks/main.yml @@ -0,0 +1,8 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_pg_hba module +- import_tasks: postgresql_pg_hba_initial.yml +- import_tasks: postgresql_pg_hba_bulk_rules.yml diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/tasks/postgresql_pg_hba_bulk_rules.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/tasks/postgresql_pg_hba_bulk_rules.yml new file mode 100644 index 000000000..4363f6dc9 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/tasks/postgresql_pg_hba_bulk_rules.yml @@ -0,0 +1,136 @@ +- name: set test variables + set_fact: + pghba_defaults: &pghba_defaults + create: true + dest: "/tmp/pg_hba_bulk_test.conf" + test_rule0: &test_rule0 + contype: host + databases: "db0" + users: "user0" + address: "2001:db8::0/128" + method: pam + test_rule1: &test_rule1 + contype: host + databases: "db1" + users: "user1" + address: "2001:db8::1/128" + method: pam + test_rule2: &test_rule2 + contype: host + databases: "db2" + users: "user2" + address: "2001:db8::2/128" + method: pam + +- name: create one rule to clear + community.postgresql.postgresql_pg_hba: + <<: *pghba_defaults + state: "present" + <<: *test_rule0 +- name: overwrite with one normal rule + community.postgresql.postgresql_pg_hba: + <<: *pghba_defaults + overwrite: true + <<: *test_rule1 + register: result +- assert: + that: + - "result.pg_hba|length == 1" + - "result.pg_hba[0].db == test_rule1.databases" + - "result.pg_hba[0].src == test_rule1.address" + - "result.pg_hba[0].usr == test_rule1.users" + - "result.pg_hba[0].type == test_rule1.contype" +- name: 'test the same again (overwrite: true, one normal rule) to ensure nothing changed' + community.postgresql.postgresql_pg_hba: + <<: *pghba_defaults + overwrite: true + <<: *test_rule1 + register: result +- assert: + that: + - "result.changed == false" + +- name: overwrite with one bulk rule + community.postgresql.postgresql_pg_hba: + <<: *pghba_defaults + overwrite: true + rules: + - "{{ test_rule2 }}" + register: result +- assert: + that: + - "result.pg_hba|length == 1" + - "result.pg_hba[0].db == test_rule2.databases" + - "result.pg_hba[0].src == test_rule2.address" + - "result.pg_hba[0].usr == test_rule2.users" + - "result.pg_hba[0].type == test_rule2.contype" +- name: 'test the same again (overwrite: true, one bulk rule) to ensure nothing changes' + community.postgresql.postgresql_pg_hba: + <<: *pghba_defaults + overwrite: true + rules: + - "{{ test_rule2 }}" + register: result +- assert: + that: + - "result.changed == false" + +- name: test rules_behavior conflict + community.postgresql.postgresql_pg_hba: "{{ pghba_defaults|combine(item)|combine({'rules': [test_rule2]}) }}" + loop: + - address: 2001:db8::a/128 + - comment: 'testcomment' + - contype: hostssl + - databases: db_a + - method: cert + - netmask: 255.255.255.0 + # address: 192.0.2.0 + - options: "clientcert=verify-full" + - state: absent + - users: testuser + register: result + ignore_errors: true +- name: get jinja2 version + shell: '/usr/bin/pip --disable-pip-version-check --no-cache-dir show Jinja2 2>/dev/null | grep -oPm 1 "(?<=^Version: )\d+\.\d+"' + register: jinja2_version + ignore_errors: true +- assert: + that: + - result.failed + - not result.changed + - "result.results|selectattr('changed')|length == 0" + - "result.results|rejectattr('failed')|length == 0" + # the 'in' test was added in jinja 2.10 + - "jinja2_version.rc == 0 and jinja2_version.stdout|trim is version('2.10', '<') or result.results|selectattr('msg', 'in', 'conflict')|length == 0" + +- name: test rules with module defaults + community.postgresql.postgresql_pg_hba: + <<: *pghba_defaults + rules: + - contype: hostssl + register: result +- assert: + that: + - result.changed + # assert that module defaults are used + - "{'db': 'all', 'method': 'md5', 'src': 'samehost', 'type': 'hostssl', 'usr': 'all'} in result.pg_hba" + +- name: test rules with custom defaults + community.postgresql.postgresql_pg_hba: + <<: *pghba_defaults + rules_behavior: combine + <<: *test_rule1 + rules: + - {} # complete fallback to custom defaults + - databases: other_db # partial fallback to custom defaults + # no fallback + - <<: *test_rule2 + state: absent + register: result +- assert: + that: + - result.changed + - "{'db': 'all', 'method': 'md5', 'src': 'samehost', 'type': 'hostssl', 'usr': 'all'} in result.pg_hba" # unchanged preexisting from previous task + - "{'db': test_rule1.databases, 'method': test_rule1.method, 'src': test_rule1.address, 'type': test_rule1.contype, 'usr': test_rule1.users} in result.pg_hba" + - "{'db': test_rule2.databases, 'method': test_rule2.method, 'src': test_rule2.address, 'type': test_rule2.contype, 'usr': test_rule2.users} not in result.pg_hba" + - "{'db': 'other_db', 'method': test_rule1.method, 'src': test_rule1.address, 'type': test_rule1.contype, 'usr': test_rule1.users} in result.pg_hba" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/tasks/postgresql_pg_hba_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/tasks/postgresql_pg_hba_initial.yml new file mode 100644 index 000000000..2a9505a5b --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_pg_hba/tasks/postgresql_pg_hba_initial.yml @@ -0,0 +1,264 @@ +- name: Make sure file does not exist + file: + dest: /tmp/pg_hba.conf + state: absent + +- name: check_mode run + postgresql_pg_hba: + dest: /tmp/pg_hba.conf + contype: host + source: '0000:ffff::' + netmask: 'ffff:fff0::' + method: md5 + backup: 'True' + order: sud + state: "{{item}}" + check_mode: true + with_items: + - present + - absent + +- name: check_mode check + stat: + path: /tmp/pg_hba.conf + register: pg_hba_checkmode_check + +- name: Remove several ip addresses for idempotency check + postgresql_pg_hba: + contype: "{{item.contype|default('host')}}" + databases: "{{item.databases|default('all')}}" + dest: /tmp/pg_hba.conf + method: "{{item.method|default('md5')}}" + netmask: "{{item.netmask|default('')}}" + order: sud + source: "{{item.source|default('')}}" + state: absent + users: "{{item.users|default('all')}}" + with_items: "{{pg_hba_test_ips}}" + register: pg_hba_idempotency_check1 + +- name: idempotency not creating file check + stat: + path: /tmp/pg_hba.conf + register: pg_hba_idempotency_file_check + +- name: Add several ip addresses + postgresql_pg_hba: + backup: 'True' + contype: "{{item.contype|default('host')}}" + create: 'True' + databases: "{{item.databases|default('all')}}" + dest: /tmp/pg_hba.conf + method: "{{item.method|default('md5')}}" + netmask: "{{item.netmask|default('')}}" + order: sud + source: "{{item.source|default('')}}" + state: present + users: "{{item.users|default('all')}}" + register: pg_hba_change + with_items: "{{pg_hba_test_ips}}" + +- name: Able to add options on rule without + postgresql_pg_hba: + dest: "/tmp/pg_hba.conf" + users: "+some" + order: "sud" + state: "present" + contype: "local" + method: "cert" + options: "{{ item }}" + address: "" + with_items: + - "" + - "clientcert=1" + +- name: Retain options even if they contain spaces + postgresql_pg_hba: + dest: "/tmp/pg_hba.conf" + users: "+some" + order: "sud" + state: "present" + contype: "{{ item.contype }}" + method: "{{ item.method }}" + options: "{{ item.options }}" + address: "{{ item.address }}" + with_items: + - { address: "", contype: "local", method: "ldap", options: "ldapserver=example.com ldapport=389 ldapprefix=\"cn=\"" } + - { address: "red", contype: "hostssl", method: "cert", options: "clientcert=1 map=mymap" } + - { address: "blue", contype: "hostssl", method: "cert", options: "clientcert=1 map=mymap" } + register: pg_hba_options + +- name: read pg_hba rules + postgresql_pg_hba: + dest: /tmp/pg_hba.conf + register: pg_hba + +- name: Add several ip addresses again for idempotency check + postgresql_pg_hba: + contype: "{{item.contype|default('host')}}" + databases: "{{item.databases|default('all')}}" + dest: /tmp/pg_hba.conf + method: "{{item.method|default('md5')}}" + netmask: "{{item.netmask|default('')}}" + order: sud + source: "{{item.source|default('')}}" + state: present + users: "{{item.users|default('all')}}" + with_items: "{{pg_hba_test_ips}}" + register: pg_hba_idempotency_check2 + +- name: pre-backup stat + stat: + path: /tmp/pg_hba.conf + register: prebackupstat + +- name: Add new ip address for backup check and netmask_sameas_prefix check + postgresql_pg_hba: + backup: 'True' + contype: host + dest: /tmp/pg_hba.conf + method: md5 + netmask: 255.255.255.0 + order: sud + source: '172.21.0.0' + state: present + register: pg_hba_backup_check2 + +- name: Add new ip address for netmask_sameas_prefix check + postgresql_pg_hba: + backup: 'True' + contype: host + dest: /tmp/pg_hba.conf + method: md5 + order: sud + source: '172.21.0.0/24' + state: present + register: netmask_sameas_prefix_check + +- name: post-backup stat + stat: + path: "{{pg_hba_backup_check2.backup_file}}" + register: postbackupstat + +- name: Dont allow netmask for src in [all, samehost, samenet] + postgresql_pg_hba: + contype: host + dest: /tmp/pg_hba.conf + method: md5 + netmask: '255.255.255.255' + order: sud + source: all + state: present + register: pg_hba_fail_src_all_with_netmask + ignore_errors: true + +- debug: + var: pg_hba.pg_hba +- assert: + that: + - 'pg_hba.pg_hba == [ + { "db": "all", "method": "ldap", "type": "local", "usr": "+some", "options": "ldapserver=example.com ldapport=389 ldapprefix=\"cn=\"" }, + { "db": "all", "method": "md5", "type": "local", "usr": "postgres" }, + { "db": "all", "method": "md5", "type": "local", "usr": "test" }, + { "db": "all", "method": "md5", "type": "local", "usr": "all" }, + { "db": "all", "method": "md5", "src": "2001:db8::1/128", "type": "hostgssenc", "usr": "postgres" }, + { "db": "all", "method": "cert", "src": "blue", "type": "hostssl", "usr": "+some", "options": "clientcert=1 map=mymap" }, + { "db": "all", "method": "cert", "src": "red", "type": "hostssl", "usr": "+some", "options": "clientcert=1 map=mymap" }, + { "db": "all", "method": "md5", "src": "127.0.0.1/32", "type": "host", "usr": "all" }, + { "db": "all", "method": "md5", "src": "2001:db8::1/128", "type": "hostnogssenc", "usr": "all" }, + { "db": "all", "method": "md5", "src": "::1/128", "type": "host", "usr": "all" }, + { "db": "all", "method": "scram-sha-256", "src": "0:ff00::/120", "type": "host", "usr": "all" }, + { "db": "replication", "method": "md5", "src": "192.168.0.0/24", "type": "host", "usr": "all" }, + { "db": "all", "method": "md5", "src": "192.168.0.0/24", "type": "host", "usr": "all" }, + { "db": "all", "method": "reject", "src": "192.168.1.0/24", "type": "host", "usr": "all" }, + { "db": "all", "method": "trust", "src": "172.16.0.0/16", "type": "host", "usr": "all" }, + { "db": "all", "method": "md5", "src": "0:fff0::/28", "type": "host", "usr": "all" } + ]' + - 'pg_hba_change is changed' + - 'pg_hba_checkmode_check.stat.exists == false' + - 'not pg_hba_idempotency_check1 is changed' + - 'not pg_hba_idempotency_check2 is changed' + - 'pg_hba_idempotency_file_check.stat.exists == false' + - 'prebackupstat.stat.checksum == postbackupstat.stat.checksum' + - 'pg_hba_fail_src_all_with_netmask is failed' + - 'not netmask_sameas_prefix_check is changed' + - 'pg_hba_options is changed' + +- name: ensure test file is empty + copy: + content: '' + dest: /tmp/pg_hba2.conf + +- name: Create a rule with the comment 'comment1' + postgresql_pg_hba: + contype: host + dest: /tmp/pg_hba2.conf + create: true + method: md5 + address: "2001:db8::1/128" + order: sud + state: present + comment: "comment1" + +- name: Fetch the file + fetch: + src: /tmp/pg_hba2.conf + dest: /tmp/pg_hba2.conf + flat: true +- name: Read pg_hba2.conf + set_fact: + content: "{{ lookup('file', '/tmp/pg_hba2.conf') }}" +- debug: + var: content +- assert: + that: + - '"\nhost\tall\tall\t2001:db8::1/128\tmd5\t#comment1" == content' + +- name: Create a rule with the comment 'comment2' + postgresql_pg_hba: + contype: host + dest: /tmp/pg_hba2.conf + method: md5 + address: "2001:db8::2/128" + order: sud + state: present + comment: "comment2" + +- name: Fetch the file + fetch: + src: /tmp/pg_hba2.conf + dest: /tmp/pg_hba2.conf + flat: true +- name: Read pg_hba2.conf + set_fact: + content: "{{ lookup('file', '/tmp/pg_hba2.conf') }}" +- debug: + var: content +- assert: + that: + - '"#comment1\nhost\tall\tall\t2001:db8::1/128\tmd5\nhost\tall\tall\t2001:db8::2/128\tmd5\t#comment2" == content' + +- name: Create a rule with the comment 'comment3' and keep_comments_at_rules + postgresql_pg_hba: + contype: host + dest: /tmp/pg_hba2.conf + method: md5 + address: "2001:db8::3/128" + order: sud + state: present + comment: "comment3" + keep_comments_at_rules: true + +- name: Fetch the file + fetch: + src: /tmp/pg_hba2.conf + dest: /tmp/pg_hba2.conf + flat: true +- name: Read pg_hba2.conf + set_fact: + content: "{{ lookup('file', '/tmp/pg_hba2.conf') }}" +- debug: + var: content +- assert: + that: + - '"#comment1\nhost\tall\tall\t2001:db8::1/128\tmd5\nhost\tall\tall\t2001:db8::2/128\tmd5\t#comment2\nhost\tall\tall\t2001:db8::3/128\tmd5\t#comment3" == content' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/defaults/main.yml new file mode 100644 index 000000000..73eb55ae2 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/defaults/main.yml @@ -0,0 +1,2 @@ +--- +db_default: postgres diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/tasks/main.yml new file mode 100644 index 000000000..bcb18d2fe --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/tasks/main.yml @@ -0,0 +1,9 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_ping module +- import_tasks: postgresql_ping_initial.yml + vars: + db_name_nonexist: fake_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/tasks/postgresql_ping_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/tasks/postgresql_ping_initial.yml new file mode 100644 index 000000000..218ae9fd7 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_ping/tasks/postgresql_ping_initial.yml @@ -0,0 +1,187 @@ +# Test code for the postgresql_ping module +# 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) + +- name: postgresql_ping - test return values + become_user: "{{ pg_user }}" + become: true + postgresql_ping: + db: "{{ db_default }}" + login_user: "{{ pg_user }}" + register: result + ignore_errors: true + +- assert: + that: + - result.is_available == true + - result.server_version != {} + - result.server_version.raw is search('PostgreSQL') + - result.server_version.major != '' + - result.server_version.minor != '' + - result is not changed + +- assert: + that: + - result.server_version.patch != {} + - result.server_version.full == '{{ result.server_version.major }}.{{ result.server_version.minor }}.{{ result.server_version.patch }}' + when: result.server_version.major == 9 + +- assert: + that: + - result.server_version.full == '{{ result.server_version.major }}.{{ result.server_version.minor }}' + when: result.server_version.major >= 10 + +- name: postgresql_ping - check ping of non-existing database doesn't return anything + become_user: "{{ pg_user }}" + become: true + postgresql_ping: + db: "{{ db_name_nonexist }}" + login_user: "{{ pg_user }}" + register: result + ignore_errors: true + +- assert: + that: + - result.is_available == false + - result.server_version == {} + - result is not changed + +- name: postgresql_ping - check ping of the database on non-existent port does not return anything + become_user: "{{ pg_user }}" + become: true + environment: + PGPORT: 5435 + ignore_errors: true + postgresql_ping: + db: "{{ db_default }}" + login_user: "{{ pg_user }}" + register: result + +- assert: + that: + - result.is_available == false + - result.server_version == {} + - result is not changed + +- name: postgresql_ping - check ping of the database by a non-existent user does not return anything + become_user: "{{ pg_user }}" + become: true + environment: + PGUSER: 'test_user' + ignore_errors: true + postgresql_ping: + db: "{{ db_default }}" + register: result + +- assert: + that: + - result.is_available == false + - result.server_version == {} + - result is not changed + +- name: Creating a "test_user" in postresql + shell: + cmd: psql -U "{{ pg_user }}" -c "CREATE ROLE test_user WITH LOGIN PASSWORD 'TEST_PASSWORD';" + +- name: postgresql_ping - check ping of the database by a existent user + become_user: "{{ pg_user }}" + become: true + environment: + PGUSER: 'test_user' + ignore_errors: true + postgresql_ping: + db: "{{ db_default }}" + login_password: "TEST_PASSWORD" + register: result + +- assert: + that: + - result.is_available == true + - result.server_version != {} + - result.server_version.raw is search('PostgreSQL') + - result.server_version.major != '' + - result.server_version.minor != '' + - result is not changed + +- name: postgresql_ping - ping DB with SSL 1 + become_user: "{{ pg_user }}" + become: true + postgresql_ping: + db: "{{ ssl_db }}" + login_user: "{{ ssl_user }}" + login_password: "{{ ssl_pass }}" + login_host: 127.0.0.1 + login_port: 5432 + ssl_mode: require + ca_cert: '{{ ssl_rootcert }}' + trust_input: true + register: result + when: + - ansible_os_family == 'Debian' + - postgres_version_resp.stdout is version('9.4', '>=') + +- assert: + that: + - result.is_available == true + - result.conn_err_msg == '' + when: + - ansible_os_family == 'Debian' + - postgres_version_resp.stdout is version('9.4', '>=') + +- name: postgresql_ping - ping DB with SSL 2 + become_user: "{{ pg_user }}" + become: true + postgresql_ping: + db: "{{ ssl_db }}" + login_user: "{{ ssl_user }}" + login_password: "{{ ssl_pass }}" + login_host: 127.0.0.1 + login_port: 5432 + ssl_mode: verify-full + ca_cert: '{{ ssl_rootcert }}' + ssl_cert: '{{ ssl_cert }}' + ssl_key: '{{ ssl_key }}' + trust_input: true + register: result + when: + - ansible_os_family == 'Debian' + - postgres_version_resp.stdout is version('9.4', '>=') + +- assert: + that: + - result.is_available == true + - result.conn_err_msg == '' + when: + - ansible_os_family == 'Debian' + - postgres_version_resp.stdout is version('9.4', '>=') + +- name: postgresql_ping - check trust_input + become_user: "{{ pg_user }}" + become: true + postgresql_ping: + db: "{{ db_default }}" + login_user: "{{ pg_user }}" + trust_input: false + session_role: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' + register: result + ignore_errors: true + +- assert: + that: + - result is failed + - result.msg is search('is potentially dangerous') + +# Check conn_err_msg return value +- name: Try to connect to non-existent DB + become_user: "{{ pg_user }}" + become: true + postgresql_ping: + db: blahblah + login_user: "{{ pg_user }}" + register: result + +- name: Check conn_err_msg return value + assert: + that: + - result is succeeded + - result.conn_err_msg is search("database \"blahblah\" does not exist") diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/defaults/main.yml new file mode 100644 index 000000000..0a2766702 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/defaults/main.yml @@ -0,0 +1,14 @@ +db_name: ansible_db +db_user1: ansible_db_user1 +db_user2: ansible_db_user2 +db_user3: ansible_db_user3 +db_user_with_dots1: role.with.dots1 +db_user_with_dots2: role.with.dots2 +db_name_with_hyphens: ansible-db +db_user_with_hyphens: ansible-db-user +db_schema_with_hyphens: ansible-db-schema +db_schema_with_dot: test.schema +db_schema_with_quote: 'TEST_schema"' +db_session_role1: session_role1 +db_session_role2: session_role2 +dangerous_name: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/main.yml new file mode 100644 index 000000000..cf7b63524 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/main.yml @@ -0,0 +1,19 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- include_tasks: postgresql_privs_session_role.yml + when: postgres_version_resp.stdout is version('9.4', '>=') + +# Initial CI tests of postgresql_privs module: +- include_tasks: postgresql_privs_initial.yml + when: postgres_version_resp.stdout is version('9.4', '>=') + +# General tests: +- include_tasks: postgresql_privs_general.yml + when: postgres_version_resp.stdout is version('9.4', '>=') + +# Tests default_privs with target_role: +- include_tasks: test_target_role.yml + when: postgres_version_resp.stdout is version('9.4', '>=') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/pg_authid_not_readable.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/pg_authid_not_readable.yml new file mode 100644 index 000000000..3f810d473 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/pg_authid_not_readable.yml @@ -0,0 +1,50 @@ +- name: "Admin user is allowed to access pg_authid relation: password comparison will succeed, password won't be updated" + become_user: "{{ pg_user }}" + become: true + postgresql_user: + name: "{{ db_user1 }}" + encrypted: 'true' + password: "md5{{ (db_password ~ db_user1) | hash('md5')}}" + db: "{{ db_name }}" + priv: 'test_table1:INSERT,SELECT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER/test_table2:INSERT/CREATE,CONNECT,TEMP' + login_user: "{{ pg_user }}" + register: redo_as_admin + +- name: "Check that task succeeded without any change" + assert: + that: + - 'redo_as_admin is not failed' + - 'redo_as_admin is not changed' + - 'redo_as_admin is successful' + +- name: "Check that normal user isn't allowed to access pg_authid" + shell: 'psql -c "select * from pg_authid;" {{ db_name }} {{ db_user1 }}' + environment: + PGPASSWORD: '{{ db_password }}' + ignore_errors: true + register: pg_authid + +- assert: + that: + - 'pg_authid is failed' + - pg_authid.stderr is search('permission denied for (relation|table) pg_authid') + +- name: "Normal user isn't allowed to access pg_authid relation: password comparison will fail, password will be updated" + become_user: "{{ pg_user }}" + become: true + postgresql_user: + name: "{{ db_user1 }}" + encrypted: 'true' + password: "md5{{ (db_password ~ db_user1) | hash('md5')}}" + db: "{{ db_name }}" + priv: 'test_table1:INSERT,SELECT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER/test_table2:INSERT/CREATE,CONNECT,TEMP' + login_user: "{{ db_user1 }}" + login_password: "{{ db_password }}" + register: redo_as_normal_user + +- name: "Check that task succeeded and that result is changed" + assert: + that: + - 'redo_as_normal_user is not failed' + - 'redo_as_normal_user is changed' + - 'redo_as_normal_user is successful' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/postgresql_privs_general.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/postgresql_privs_general.yml new file mode 100644 index 000000000..4b4621010 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/postgresql_privs_general.yml @@ -0,0 +1,1767 @@ +# Setup +- name: Create DB + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: present + name: "{{ db_name }}" + login_user: "{{ pg_user }}" + +- name: Create a user to be owner of objects + postgresql_user: + name: "{{ db_user3 }}" + state: present + encrypted: true + password: password + role_attr_flags: CREATEDB,LOGIN + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + +- name: Create a user to be given permissions and other tests + postgresql_user: + name: "{{ db_user2 }}" + state: present + encrypted: true + password: password + role_attr_flags: LOGIN + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + +############################# +# Test of solving bug 656 # +############################# +- name: Create DB with hyphen in the name + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: present + name: "{{ db_name_with_hyphens }}" + login_user: "{{ pg_user }}" + register: result + +- assert: + that: + - result is changed + +- name: Create a user with hyphen in the name + postgresql_user: + name: "{{ db_user_with_hyphens }}" + state: present + encrypted: true + password: password + role_attr_flags: CREATEDB,LOGIN + db: "{{ db_name_with_hyphens }}" + login_user: "{{ pg_user }}" + register: result + +- assert: + that: + - result is changed + +- name: Create schema with hyphen in the name + postgresql_schema: + login_user: "{{ pg_user }}" + login_password: password + db: "{{ db_name_with_hyphens }}" + name: "{{ db_schema_with_hyphens }}" + state: present + register: result + +- assert: + that: + - result is changed + +# Grant rights to the public schema, since in PostgreSQL 15 +# the rights to this schema are taken away from all users except the owner +- name: GRANT ALL PRIVILEGES ON SCHEMA public TO ansible_db_user1,2,3 + community.postgresql.postgresql_privs: + db: "{{ db_name }}" + privs: ALL + type: schema + objs: public + role: "{{ item }}" + loop: + - "{{ db_user2 }}" + - "{{ db_user3 }}" + +# Also covers https://github.com/ansible-collections/community.general/issues/884 +- name: Set table default privs on the schema with hyphen in the name + postgresql_privs: + login_user: "{{ pg_user }}" + login_password: password + db: "{{ db_name_with_hyphens }}" + schema: "{{ db_schema_with_hyphens }}" + role: "{{ db_user_with_hyphens }}" + type: default_privs + obj: TABLES + privs: all + state: present + usage_on_types: true + register: result + check_mode: true + +- assert: + that: + - result is changed + - result.queries is search('ON TYPES') + +# Also covers https://github.com/ansible-collections/community.general/issues/884 +- name: Set table default privs on the schema with hyphen in the name + postgresql_privs: + login_user: "{{ pg_user }}" + login_password: password + db: "{{ db_name_with_hyphens }}" + schema: "{{ db_schema_with_hyphens }}" + role: "{{ db_user_with_hyphens }}" + type: default_privs + obj: TABLES + privs: all + state: present + usage_on_types: false + register: result + +- assert: + that: + - result is changed + - result.queries is not search('ON TYPES') + +- name: Delete table default privs on the schema with hyphen in the name + postgresql_privs: + login_user: "{{ pg_user }}" + login_password: password + db: "{{ db_name_with_hyphens }}" + schema: "{{ db_schema_with_hyphens }}" + role: "{{ db_user_with_hyphens }}" + type: default_privs + obj: TABLES + privs: all + state: absent + register: result + +- assert: + that: + - result is changed + +- name: Delete schema with hyphen in the name + postgresql_schema: + login_user: "{{ pg_user }}" + login_password: password + db: "{{ db_name_with_hyphens }}" + name: "{{ db_schema_with_hyphens }}" + state: absent + register: result + +- assert: + that: + - result is changed + +- name: Delete a user with hyphen in the name + postgresql_user: + name: "{{ db_user_with_hyphens }}" + state: absent + encrypted: true + login_password: password + role_attr_flags: CREATEDB,LOGIN + db: "{{ db_name_with_hyphens }}" + login_user: "{{ pg_user }}" + register: result + +- assert: + that: + - result is changed + +- name: Delete DB with hyphen in the name + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: absent + name: "{{ db_name_with_hyphens }}" + login_user: "{{ pg_user }}" + register: result + +- assert: + that: + - result is changed + +############################# +# Test of solving bug 27327 # +############################# + +# Create the test table and view: +- name: Create table + become: true + become_user: "{{ pg_user }}" + postgresql_table: + login_user: "{{ pg_user }}" + db: postgres + name: test_table1 + columns: + - id int + +- name: Create view + become: true + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "CREATE VIEW test_view AS SELECT id FROM test_table1" + +# Test check_mode: +- name: Grant SELECT on test_view, check_mode + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + db: postgres + state: present + privs: SELECT + type: table + objs: test_view + roles: "{{ db_user2 }}" + trust_input: false + check_mode: true + register: result + +- assert: + that: + - result is changed + +# Check: +- name: Check that nothing was changed after the prev step + become: true + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "SELECT grantee FROM information_schema.role_table_grants WHERE table_name='test_view' AND grantee = '{{ db_user2 }}'" + register: result + +- assert: + that: + - result.rowcount == 0 + +# Test true mode: +- name: Grant SELECT on test_view + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + db: postgres + state: present + privs: SELECT + type: table + objs: test_view + roles: "{{ db_user2 }}" + trust_input: false + register: result + +- assert: + that: + - result is changed + +# Check: +- name: Check that nothing was changed after the prev step + become: true + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "SELECT grantee FROM information_schema.role_table_grants WHERE table_name='test_view' AND grantee = '{{ db_user2 }}'" + register: result + +- assert: + that: + - result.rowcount == 1 + +# Test true mode: +- name: Try to grant SELECT again + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + db: postgres + state: present + privs: SELECT + type: table + objs: test_view + roles: "{{ db_user2 }}" + trust_input: false + register: result + +- assert: + that: + - result is not changed + +# Cleanup: +- name: Drop test view + become: true + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + db: postgres + query: "DROP VIEW test_view" + +- name: Drop test table + become: true + become_user: "{{ pg_user }}" + postgresql_table: + login_user: "{{ pg_user }}" + db: postgres + name: test_table1 + state: absent + +###################################################### +# Test foreign data wrapper and foreign server privs # +###################################################### + +# Foreign data wrapper setup +- name: Create foreign data wrapper extension + become: true + become_user: "{{ pg_user }}" + shell: echo "CREATE EXTENSION postgres_fdw" | psql -d "{{ db_name }}" + +- name: Create dummy foreign data wrapper + become: true + become_user: "{{ pg_user }}" + shell: echo "CREATE FOREIGN DATA WRAPPER dummy" | psql -d "{{ db_name }}" + +- name: Create foreign server + become: true + become_user: "{{ pg_user }}" + shell: echo "CREATE SERVER dummy_server FOREIGN DATA WRAPPER dummy" | psql -d "{{ db_name }}" + +# Test +- name: Grant foreign data wrapper privileges + postgresql_privs: + state: present + type: foreign_data_wrapper + roles: "{{ db_user2 }}" + privs: ALL + objs: dummy + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + ignore_errors: true + +# Checks +- assert: + that: + - result is changed + +- name: Get foreign data wrapper privileges + become: true + become_user: "{{ pg_user }}" + shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}" + vars: + fdw_query: > + SELECT fdwacl FROM pg_catalog.pg_foreign_data_wrapper + WHERE fdwname = ANY (ARRAY['dummy']) ORDER BY fdwname + register: fdw_result + +- assert: + that: + - "fdw_result.stdout_lines[-1] == '(1 row)'" + - "'{{ db_user2 }}' in fdw_result.stdout_lines[-2]" + +# Test +- name: Grant foreign data wrapper privileges second time + postgresql_privs: + state: present + type: foreign_data_wrapper + roles: "{{ db_user2 }}" + privs: ALL + objs: dummy + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + ignore_errors: true + +# Checks +- assert: + that: + - result is not changed + +# Test +- name: Revoke foreign data wrapper privileges + postgresql_privs: + state: absent + type: foreign_data_wrapper + roles: "{{ db_user2 }}" + privs: ALL + objs: dummy + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + ignore_errors: true + +# Checks +- assert: + that: + - result is changed + +- name: Get foreign data wrapper privileges + become: true + become_user: "{{ pg_user }}" + shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}" + vars: + fdw_query: > + SELECT fdwacl FROM pg_catalog.pg_foreign_data_wrapper + WHERE fdwname = ANY (ARRAY['dummy']) ORDER BY fdwname + register: fdw_result + +- assert: + that: + - "fdw_result.stdout_lines[-1] == '(1 row)'" + - "'{{ db_user2 }}' not in fdw_result.stdout_lines[-2]" + +# Test +- name: Revoke foreign data wrapper privileges for second time + postgresql_privs: + state: absent + type: foreign_data_wrapper + roles: "{{ db_user2 }}" + privs: ALL + objs: dummy + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + ignore_errors: true + +# Checks +- assert: + that: + - result is not changed + +# Test +- name: Grant foreign server privileges + postgresql_privs: + state: present + type: foreign_server + roles: "{{ db_user2 }}" + privs: ALL + objs: dummy_server + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + ignore_errors: true + +# Checks +- assert: + that: + - result is changed + +- name: Get foreign server privileges + become: true + become_user: "{{ pg_user }}" + shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}" + vars: + fdw_query: > + SELECT srvacl FROM pg_catalog.pg_foreign_server + WHERE srvname = ANY (ARRAY['dummy_server']) ORDER BY srvname + register: fs_result + +- assert: + that: + - "fs_result.stdout_lines[-1] == '(1 row)'" + - "'{{ db_user2 }}' in fs_result.stdout_lines[-2]" + +# Test +- name: Grant foreign server privileges for second time + postgresql_privs: + state: present + type: foreign_server + roles: "{{ db_user2 }}" + privs: ALL + objs: dummy_server + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + ignore_errors: true + +# Checks +- assert: + that: + - result is not changed + +# Test +- name: Revoke foreign server privileges + postgresql_privs: + state: absent + type: foreign_server + roles: "{{ db_user2 }}" + privs: ALL + objs: dummy_server + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + ignore_errors: true + +# Checks +- assert: + that: + - result is changed + +- name: Get foreign server privileges + become: true + become_user: "{{ pg_user }}" + shell: echo "{{ fdw_query }}" | psql -d "{{ db_name }}" + vars: + fdw_query: > + SELECT srvacl FROM pg_catalog.pg_foreign_server + WHERE srvname = ANY (ARRAY['dummy_server']) ORDER BY srvname + register: fs_result + +- assert: + that: + - "fs_result.stdout_lines[-1] == '(1 row)'" + - "'{{ db_user2 }}' not in fs_result.stdout_lines[-2]" + +# Test +- name: Revoke foreign server privileges for second time + postgresql_privs: + state: absent + type: foreign_server + roles: "{{ db_user2 }}" + privs: ALL + objs: dummy_server + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + ignore_errors: true + +# Checks +- assert: + that: + - result is not changed + +# Foreign data wrapper cleanup +- name: Drop foreign server + become: true + become_user: "{{ pg_user }}" + shell: echo "DROP SERVER dummy_server" | psql -d "{{ db_name }}" + +- name: Drop dummy foreign data wrapper + become: true + become_user: "{{ pg_user }}" + shell: echo "DROP FOREIGN DATA WRAPPER dummy" | psql -d "{{ db_name }}" + +- name: Drop foreign data wrapper extension + become: true + become_user: "{{ pg_user }}" + shell: echo "DROP EXTENSION postgres_fdw" | psql -d "{{ db_name }}" + +########################################## +# Test ALL_IN_SCHEMA for 'function' type # +########################################## + +# Function ALL_IN_SCHEMA Setup +- name: Create function for test + postgresql_query: + query: CREATE FUNCTION public.a() RETURNS integer LANGUAGE SQL AS 'SELECT 2'; + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + +# Test +- name: Grant execute to all functions + postgresql_privs: + type: function + state: present + privs: EXECUTE + roles: "{{ db_user2 }}" + objs: ALL_IN_SCHEMA + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + trust_input: false + register: result + ignore_errors: true + +# Checks +- assert: + that: result is changed + +- name: Check that all functions have execute privileges + become: true + become_user: "{{ pg_user }}" + shell: psql {{ db_name }} -c "SELECT proacl FROM pg_proc WHERE proname = 'a'" -t + register: result + +- assert: + that: "'{{ db_user2 }}=X/{{ db_user3 }}' in '{{ result.stdout_lines[0] }}'" + +# Test +- name: Grant execute to all functions again + postgresql_privs: + type: function + state: present + privs: EXECUTE + roles: "{{ db_user2 }}" + objs: ALL_IN_SCHEMA + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + trust_input: false + register: result + ignore_errors: true + +# Checks +- assert: + that: result is not changed + +# Test +- name: Revoke execute to all functions + postgresql_privs: + type: function + state: absent + privs: EXECUTE + roles: "{{ db_user2 }}" + objs: ALL_IN_SCHEMA + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + trust_input: false + register: result + ignore_errors: true + +# Checks +- assert: + that: result is changed + +# Test +- name: Revoke execute to all functions again + postgresql_privs: + type: function + state: absent + privs: EXECUTE + roles: "{{ db_user2 }}" + objs: ALL_IN_SCHEMA + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + trust_input: false + register: result + ignore_errors: true + +- assert: + that: result is not changed + +# Function ALL_IN_SCHEMA cleanup +- name: Remove function for test + postgresql_query: + query: DROP FUNCTION public.a(); + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + +# Issue https://github.com/ansible-collections/community.general/issues/994 +- name: Create a procedure for tests + postgresql_query: + query: "CREATE PROCEDURE mock_procedure() LANGUAGE SQL AS $$ SELECT 1; $$;" + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + when: postgres_version_resp.stdout is version('11', '>=') + +# Issue https://github.com/ansible-collections/community.general/issues/994 +- name: Try to run module against a procedure, not function + postgresql_privs: + type: function + state: present + privs: ALL + roles: "{{ db_user2 }}" + objs: ALL_IN_SCHEMA + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + register: result + when: postgres_version_resp.stdout is version('11', '>=') + +- assert: + that: + - result is not changed + when: postgres_version_resp.stdout is version('11', '>=') + +########################### +# Test for procedure type # +########################### +- name: Create another procedure for tests + postgresql_query: + query: "CREATE PROCEDURE mock_procedure1(int, int) LANGUAGE SQL AS $$ SELECT 1; $$;" + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + when: postgres_version_resp.stdout is version('11', '>=') + +- name: Grant privs on procedure + postgresql_privs: + type: procedure + state: present + privs: EXECUTE + roles: "{{ db_user2 }}" + objs: 'mock_procedure1(int:int)' + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + register: result + when: postgres_version_resp.stdout is version('11', '>=') + +- assert: + that: + - result is changed + when: postgres_version_resp.stdout is version('11', '>=') + +- name: Grant privs on procedure again + postgresql_privs: + type: procedure + state: present + privs: EXECUTE + roles: "{{ db_user2 }}" + objs: 'mock_procedure1(int:int)' + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + register: result + when: postgres_version_resp.stdout is version('11', '>=') + +- assert: + that: + - result is not changed + when: postgres_version_resp.stdout is version('11', '>=') + +- name: Revoke procedure privs + postgresql_privs: + type: procedure + state: absent + privs: EXECUTE + roles: "{{ db_user2 }}" + objs: 'mock_procedure1(int:int)' + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + register: result + when: postgres_version_resp.stdout is version('11', '>=') + +- assert: + that: + - result is changed + when: postgres_version_resp.stdout is version('11', '>=') + +- name: Revoke procedure privs again + postgresql_privs: + type: procedure + state: absent + privs: EXECUTE + roles: "{{ db_user2 }}" + objs: 'mock_procedure1(int:int)' + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + register: result + when: postgres_version_resp.stdout is version('11', '>=') + +- assert: + that: + - result is not changed + when: postgres_version_resp.stdout is version('11', '>=') + +- name: Grant procedure privs for all object in schema + postgresql_privs: + type: procedure + state: present + privs: ALL + roles: "{{ db_user2 }}" + objs: ALL_IN_SCHEMA + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + register: result + when: postgres_version_resp.stdout is version('11', '>=') + +- assert: + that: + - result is changed + when: postgres_version_resp.stdout is version('11', '>=') + +- name: Grant procedure privs for all object in schema again + postgresql_privs: + type: procedure + state: present + privs: ALL + roles: "{{ db_user2 }}" + objs: ALL_IN_SCHEMA + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + register: result + when: postgres_version_resp.stdout is version('11', '>=') + +- assert: + that: + - result is not changed + when: postgres_version_resp.stdout is version('11', '>=') + +- name: Revoke procedure privs for all object in schema + postgresql_privs: + type: procedure + state: absent + privs: ALL + roles: "{{ db_user2 }}" + objs: ALL_IN_SCHEMA + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + register: result + when: postgres_version_resp.stdout is version('11', '>=') + +- assert: + that: + - result is changed + when: postgres_version_resp.stdout is version('11', '>=') + +################################################# +# Test ALL_IN_SCHEMA for 'partioned tables type # +################################################# + +# Partitioning tables is a feature introduced in Postgresql 10. +# (see https://www.postgresql.org/docs/10/ddl-partitioning.html ) +# The test below check for this version + +# Function ALL_IN_SCHEMA Setup +- name: Create partioned table for test purpose + postgresql_query: + query: CREATE TABLE public.testpt (id int not null, logdate date not null) PARTITION BY RANGE (logdate); + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + when: postgres_version_resp.stdout is version('10', '>=') + +# Test +- name: Grant execute to all tables in check mode + postgresql_privs: + type: table + state: present + privs: SELECT + roles: "{{ db_user2 }}" + objs: ALL_IN_SCHEMA + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + trust_input: false + register: result + ignore_errors: true + when: postgres_version_resp.stdout is version('10', '>=') + check_mode: true + +# Checks +- name: Check that all partitioned tables don't have select privileges after the check mode task + postgresql_query: + query: SELECT grantee, privilege_type FROM information_schema.role_table_grants WHERE table_name='testpt' and privilege_type='SELECT' and grantee = %(grantuser)s + db: "{{ db_name }}" + login_user: '{{ db_user2 }}' + login_password: password + named_args: + grantuser: '{{ db_user2 }}' + become: true + become_user: "{{ pg_user }}" + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.rowcount == 0 + when: postgres_version_resp.stdout is version('10', '>=') + +# Test +- name: Grant execute with grant option on pg_create_restore_point function + postgresql_privs: + privs: EXECUTE + type: function + schema: pg_catalog + obj: pg_create_restore_point(text) + db: "{{ db_name }}" + roles: "{{ db_user2 }}" + login_user: "{{ pg_user }}" + grant_option: true + state: present + become: true + become_user: "{{ pg_user }}" + register: result + +# Checks +- assert: + that: result is changed + +- name: Check that user has GRANT privilege on the function + postgresql_query: + query: SELECT proacl FROM pg_proc WHERE proname='pg_create_restore_point' + db: "{{ db_name }}" + login_user: "{{ db_user2 }}" + login_password: password + become: true + become_user: "{{ pg_user }}" + register: result + +- assert: + that: "'{{ db_user2 }}=X*/{{ pg_user }}' in result.query_result[0].proacl" + +# Test +- name: Grant execute without specifying grant_option to check idempotence + postgresql_privs: + privs: EXECUTE + type: function + schema: pg_catalog + obj: pg_create_restore_point(text) + db: "{{ db_name }}" + roles: "{{ db_user2 }}" + login_user: "{{ pg_user }}" + state: present + become: true + become_user: "{{ pg_user }}" + register: result + +# Checks +- assert: + that: result is not changed + +- name: Check that user has GRANT privilege on the function + postgresql_query: + query: SELECT proacl FROM pg_proc WHERE proname='pg_create_restore_point' + db: "{{ db_name }}" + login_user: "{{ db_user2 }}" + login_password: password + become: true + become_user: "{{ pg_user }}" + register: result + +- assert: + that: "'{{ db_user2 }}=X*/{{ pg_user }}' in result.query_result[0].proacl" + +# Test +- name: Revoke grant option on pg_create_restore_point function + postgresql_privs: + privs: EXECUTE + type: function + schema: pg_catalog + obj: pg_create_restore_point(text) + db: "{{ db_name }}" + roles: "{{ db_user2 }}" + login_user: "{{ pg_user }}" + grant_option: false + state: present + become: true + become_user: "{{ pg_user }}" + register: result + +# Checks +- assert: + that: result is changed + +- name: Check that user does not have GRANT privilege on the function + postgresql_query: + query: SELECT proacl FROM pg_proc WHERE proname='pg_create_restore_point' + db: "{{ db_name }}" + login_user: "{{ db_user2 }}" + login_password: password + become: true + become_user: "{{ pg_user }}" + register: result + +- assert: + that: "'{{ db_user2 }}=X/{{ pg_user }}' in result.query_result[0].proacl" + +# Test +- name: Revoke execute on pg_create_restore_point function + postgresql_privs: + privs: EXECUTE + type: function + schema: pg_catalog + obj: pg_create_restore_point(text) + db: "{{ db_name }}" + roles: "{{ db_user2 }}" + login_user: "{{ pg_user }}" + state: absent + become: true + become_user: "{{ pg_user }}" + register: result + +# Checks +- assert: + that: result is changed + +- name: Check that user does not have EXECUTE privilege on the function + postgresql_query: + query: SELECT proacl FROM pg_proc WHERE proname='pg_create_restore_point' + db: "{{ db_name }}" + login_user: "{{ db_user2 }}" + login_password: password + become: true + become_user: "{{ pg_user }}" + register: result + +- assert: + that: "'{{ db_user2 }}' not in result.query_result[0].proacl" + +# Test +- name: Grant execute to all tables + postgresql_privs: + type: table + state: present + privs: SELECT + roles: "{{ db_user2 }}" + objs: ALL_IN_SCHEMA + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + trust_input: false + register: result + ignore_errors: true + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: result is changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Check that all partitioned tables have select privileges + postgresql_query: + query: SELECT grantee, privilege_type FROM information_schema.role_table_grants WHERE table_name='testpt' and privilege_type='SELECT' and grantee = %(grantuser)s + db: "{{ db_name }}" + login_user: '{{ db_user2 }}' + login_password: password + named_args: + grantuser: '{{ db_user2 }}' + become: true + become_user: "{{ pg_user }}" + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('10', '>=') + +# Test +- name: Grant execute to all tables again to see no changes are reported + postgresql_privs: + type: table + state: present + privs: SELECT + roles: "{{ db_user2 }}" + objs: ALL_IN_SCHEMA + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + trust_input: false + register: result + ignore_errors: true + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: result is not changed + when: postgres_version_resp.stdout is version('10', '>=') + +# Test +- name: Revoke SELECT to all tables + postgresql_privs: + type: table + state: absent + privs: SELECT + roles: "{{ db_user2 }}" + objs: ALL_IN_SCHEMA + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + trust_input: false + register: result + ignore_errors: true + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: result is changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Check that all partitioned tables don't have select privileges + postgresql_query: + query: SELECT grantee, privilege_type FROM information_schema.role_table_grants WHERE table_name='testpt' and privilege_type='SELECT' and grantee = %(grantuser)s + db: "{{ db_name }}" + login_user: '{{ db_user2 }}' + login_password: password + named_args: + grantuser: '{{ db_user2 }}' + become: true + become_user: "{{ pg_user }}" + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.rowcount == 0 + when: postgres_version_resp.stdout is version('10', '>=') + +# Test +- name: Revoke SELECT to all tables and no changes are reported + postgresql_privs: + type: table + state: absent + privs: SELECT + roles: "{{ db_user2 }}" + objs: ALL_IN_SCHEMA + schema: public + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + trust_input: false + register: result + ignore_errors: true + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: result is not changed + when: postgres_version_resp.stdout is version('10', '>=') + +# Table ALL_IN_SCHEMA cleanup +- name: Remove table for test + postgresql_query: + query: DROP TABLE public.testpt; + db: "{{ db_name }}" + login_user: "{{ db_user3 }}" + login_password: password + trust_input: false + ignore_errors: true + when: postgres_version_resp.stdout is version('10', '>=') + +########################################### +# Test for 'type' value of type parameter # +########################################### + +# Test +- name: Grant type privileges + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + state: present + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: numeric + schema: pg_catalog + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: + - result is changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Get type privileges + become: true + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: SELECT typacl FROM pg_catalog.pg_type WHERE typname = 'numeric'; + register: typ_result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - "'{{ db_user2 }}' in typ_result.query_result[0].typacl" + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Grant type privileges again using check_mode + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + state: present + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: numeric + schema: pg_catalog + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + check_mode: true + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: + - result is not changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Get type privileges + become: true + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: SELECT typacl FROM pg_catalog.pg_type WHERE typname = 'numeric'; + register: typ_result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - "'{{ db_user2 }}' in typ_result.query_result[0].typacl" + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Grant type privileges again + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + state: present + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: numeric + schema: pg_catalog + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: + - result is not changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Get type privileges + become: true + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: SELECT typacl FROM pg_catalog.pg_type WHERE typname = 'numeric'; + register: typ_result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - "'{{ db_user2 }}' in typ_result.query_result[0].typacl" + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Revoke type privileges in check_mode + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + state: absent + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: numeric + schema: pg_catalog + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + check_mode: true + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: + - result is changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Get type privileges + become: true + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: SELECT typacl FROM pg_catalog.pg_type WHERE typname = 'numeric'; + register: typ_result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - "'{{ db_user2 }}' in typ_result.query_result[0].typacl" + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Revoke type privileges + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + state: absent + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: numeric + schema: pg_catalog + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: + - result is changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Get type privileges + become: true + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: SELECT typacl FROM pg_catalog.pg_type WHERE typname = 'numeric'; + register: typ_result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - "'{{ db_user2 }}' not in typ_result.query_result[0].typacl" + when: postgres_version_resp.stdout is version('10', '>=') + +# type with default schema (public): +- name: Create custom type in schema public + become: true + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: "CREATE TYPE compfoo AS (f1 int, f2 text)" + when: postgres_version_resp.stdout is version('10', '>=') + +# Test +- name: Grant type privileges with default schema + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + state: present + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: compfoo + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +# Checks +- assert: + that: + - result is changed + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Get type privileges + become: true + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + query: > + SELECT t.typacl FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n + ON n.oid = t.typnamespace WHERE t.typname = 'compfoo' AND n.nspname = 'public'; + register: typ_result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - "'{{ db_user2 }}' in typ_result.query_result[0].typacl" + when: postgres_version_resp.stdout is version('10', '>=') + +###################################################################### +# https://github.com/ansible-collections/community.general/issues/1058 +- name: Create user for test + become: true + become_user: "{{ pg_user }}" + postgresql_user: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + name: "test-role" + role_attr_flags: "NOLOGIN,NOSUPERUSER,INHERIT,NOCREATEDB,NOCREATEROLE,NOREPLICATION" + +- name: Test community.general/issue/1058 GRANT with hyphen + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + roles: "test-role" + objs: "{{ pg_user }}" + type: "group" + register: result + +- assert: + that: + - result is changed + - result.queries == ["GRANT \"{{ pg_user }}\" TO \"test-role\";"] + +- name: Test community.general/issue/1058 REVOKE + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + roles: "test-role" + objs: "{{ pg_user }}" + type: "group" + state: absent + register: result + +- assert: + that: + - result is changed + - result.queries == ["REVOKE \"{{ pg_user }}\" FROM \"test-role\";"] + +- name: Test community.general/issue/1058 GRANT without hyphen + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + roles: "{{ db_user3 }}" + objs: "{{ pg_user }}" + type: "group" + register: result + +- assert: + that: + - result is changed + - result.queries == ["GRANT \"{{ pg_user }}\" TO \"{{ db_user3 }}\";"] + +- name: Test community.general/issue/1058 GRANT with hyphen as an object + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + roles: "{{ db_user3 }}" + objs: "test-role,{{ db_user2 }}" + type: "group" + register: result + +- assert: + that: + - result is changed + - result.queries == ["GRANT \"test-role\",\"{{ db_user2 }}\" TO \"{{ db_user3 }}\";"] + +- name: Test community.general/issue/1058 GRANT with hyphen as an object + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + roles: "{{ db_user3 }}" + objs: "test-role" + type: "group" + register: result + +- assert: + that: + - result is not changed + +############## +# Issue https://github.com/ansible-collections/community.postgresql/issues/381 +- name: create schemas with special names + become: true + become_user: "{{ pg_user }}" + postgresql_schema: + login_user: "{{ pg_user }}" + login_password: password + db: "{{ db_name }}" + name: '"{{ item }}"' + state: present + loop: + - "{{ db_schema_with_dot|replace('\"', '\"\"') }}" + - "{{ db_schema_with_quote|replace('\"', '\"\"') }}" + register: result +- assert: + that: + - result is changed +- name: create tables in schemas with special names + become: true + become_user: "{{ pg_user }}" + postgresql_table: + login_user: "{{ pg_user }}" + login_password: password + db: "{{ db_name }}" + name: '"{{ item }}"."test.table.name"' + columns: [] + loop: + - "{{ db_schema_with_dot|replace('\"', '\"\"') }}" + - "{{ db_schema_with_quote|replace('\"', '\"\"') }}" + register: result +- assert: + that: + - result is changed +- name: grant privileges on all tables in schemas with special names + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + roles: PUBLIC + objs: ALL_IN_SCHEMA + type: table + privs: SELECT + schema: "{{ item }}" + loop: + - "{{ db_schema_with_dot }}" + - "{{ db_schema_with_quote }}" + register: result +- assert: + that: + - result is changed +- name: grant privileges on some table in schemas with special names + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + roles: PUBLIC + objs: 'test.table.name' + type: table + privs: SELECT + schema: "{{ item }}" + loop: + - "{{ db_schema_with_dot }}" + - "{{ db_schema_with_quote }}" + register: result +- assert: + that: + - result is changed +- name: check permissions on tables in schemas with special names + become: true + become_user: "{{ pg_user }}" + postgresql_query: + login_user: "{{ pg_user }}" + db: "{{ db_name }}" + query: | + select true as granted from information_schema.role_table_grants + where table_schema=%s and table_name='test.table.name' and privilege_type='SELECT' and grantee='PUBLIC' + positional_args: + - "{{ item }}" + loop: + - "{{ db_schema_with_dot }}" + - "{{ db_schema_with_quote }}" + register: result +- assert: + that: + - 'result.results|length == 2' + - 'result.results[0].rowcount == 1' + - 'not result.results[0].failed' + - 'result.results[1].rowcount == 1' + - 'not result.results[1].failed' +- name: cleanup test schemas with special names + become: true + become_user: "{{ pg_user }}" + postgresql_schema: + login_user: "{{ pg_user }}" + login_password: password + db: "{{ db_name }}" + name: '"{{ item }}"' + state: absent + cascade_drop: true + loop: + - "{{ db_schema_with_dot|replace('\"', '\"\"') }}" + - "{{ db_schema_with_quote|replace('\"', '\"\"') }}" + register: result + + +############## +# Issue https://github.com/ansible-collections/community.postgresql/issues/332 +- name: Test community.postgresql issue 332 grant usage + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + roles: "{{ db_user3 }}" + objs: schemas + type: default_privs + privs: usage + register: result + +- assert: + that: + - result is changed + +- name: Test community.postgresql issue 332 grant usage, run again + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + roles: "{{ db_user3 }}" + objs: schemas + type: default_privs + privs: usage + register: result + +- assert: + that: + - result is not changed + +- name: Test community.postgresql issue 333 grant usage + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + roles: "{{ db_user3 }}" + objs: tables + type: default_privs + schema: not-specified + privs: select + register: result + +- assert: + that: + - result is changed + +- name: Test community.postgresql issue 333 grant usage, run again + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + roles: "{{ db_user3 }}" + objs: tables + type: default_privs + schema: not-specified + privs: select + register: result + +- assert: + that: + - result is not changed + +- name: Test community.postgresql issue 373 + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + roles: "{{ db_user3 }}" + objs: ALL_DEFAULT + type: default_privs + privs: ALL + register: result + +- assert: + that: + - result is changed + +- name: Test community.postgresql issue 379 + become: yes + become_user: "{{ pg_user }}" + postgresql_privs: + login_user: "{{ pg_user }}" + login_db: "{{ db_name }}" + roles: "{{ db_user3 }}" + objs: ALL_IN_SCHEMA + type: default_privs + privs: SELECT,INSERT,UPDATE,DELETE,EXECUTE + schema: public + register: result + ignore_errors: yes + +- assert: + that: + - result is failed + - result.msg is search('ALL_IN_SCHEMA can be used only for type') + +# Cleanup +- name: Remove privs + become: true + become_user: "{{ pg_user }}" + postgresql_privs: + state: absent + type: type + roles: "{{ db_user2 }}" + privs: ALL + objs: compfoo + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + when: postgres_version_resp.stdout is version('10', '>=') + +- name: Reassign ownership + become_user: "{{ pg_user }}" + become: true + postgresql_owner: + login_user: "{{ pg_user }}" + db: "{{ db_name }}" + new_owner: "{{ pg_user }}" + reassign_owned_by: "{{ item }}" + loop: + - "{{ db_user2 }}" + - "{{ db_user3 }}" + +- name: Drop a role for which the default privileges have been altered + become: yes + become_user: "{{ pg_user }}" + postgresql_query: + login_db: "{{ db_name }}" + query: "DROP OWNED BY {{ item }};" + loop: + - "{{ db_user2 }}" + - "{{ db_user3 }}" + +- name: Remove user given permissions + become: true + become_user: "{{ pg_user }}" + postgresql_user: + name: "{{ db_user2 }}" + state: absent + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + +- name: Remove user owner of objects + become: true + become_user: "{{ pg_user }}" + postgresql_user: + name: "{{ item }}" + state: absent + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + loop: + - '{{ db_user3 }}' + - 'test-role' + +- name: Destroy DB + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: absent + name: "{{ db_name }}" + login_user: "{{ pg_user }}" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/postgresql_privs_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/postgresql_privs_initial.yml new file mode 100644 index 000000000..814bc348d --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/postgresql_privs_initial.yml @@ -0,0 +1,407 @@ +# The tests below were added initially and moved here +# from the shared target called ``postgresql`` by @Andersson007 <aaklychkov@mail.ru>. +# You can see modern examples of CI tests in postgresql_publication directory, for example. + +# +# Test settings privileges +# +- name: Create db + become_user: "{{ pg_user }}" + become: true + postgresql_db: + name: "{{ db_name }}" + state: "present" + login_user: "{{ pg_user }}" + +- name: Create some tables on the db + become_user: "{{ pg_user }}" + become: true + shell: echo "create table test_table1 (field text);" | psql {{ db_name }} + +- become_user: "{{ pg_user }}" + become: true + shell: echo "create table test_table2 (field text);" | psql {{ db_name }} + +- vars: + db_password: 'secretù' # use UTF-8 + block: + - name: Create a user with some permissions on the db + become_user: "{{ pg_user }}" + become: true + postgresql_user: + name: "{{ db_user1 }}" + encrypted: 'true' + password: "md5{{ (db_password ~ db_user1) | hash('md5')}}" + db: "{{ db_name }}" + priv: 'test_table1:INSERT,SELECT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER/test_table2:INSERT/CREATE,CONNECT,TEMP' + login_user: "{{ pg_user }}" + + - include_tasks: pg_authid_not_readable.yml + +- name: Check that the user has the requested permissions (table1) + become_user: "{{ pg_user }}" + become: true + shell: echo "select privilege_type from information_schema.role_table_grants where grantee='{{ db_user1 }}' and table_name='test_table1';" | psql {{ db_name }} + register: result_table1 + +- name: Check that the user has the requested permissions (table2) + become_user: "{{ pg_user }}" + become: true + shell: echo "select privilege_type from information_schema.role_table_grants where grantee='{{ db_user1 }}' and table_name='test_table2';" | psql {{ db_name }} + register: result_table2 + +- name: Check that the user has the requested permissions (database) + become_user: "{{ pg_user }}" + become: true + shell: echo "select datacl from pg_database where datname='{{ db_name }}';" | psql {{ db_name }} + register: result_database + +- assert: + that: + - "result_table1.stdout_lines[-1] == '(7 rows)'" + - "'INSERT' in result_table1.stdout" + - "'SELECT' in result_table1.stdout" + - "'UPDATE' in result_table1.stdout" + - "'DELETE' in result_table1.stdout" + - "'TRUNCATE' in result_table1.stdout" + - "'REFERENCES' in result_table1.stdout" + - "'TRIGGER' in result_table1.stdout" + - "result_table2.stdout_lines[-1] == '(1 row)'" + - "'INSERT' == '{{ result_table2.stdout_lines[-2] | trim }}'" + - "result_database.stdout_lines[-1] == '(1 row)'" + - "'{{ db_user1 }}=CTc/{{ pg_user }}' in result_database.stdout_lines[-2]" + +- name: Add another permission for the user + become_user: "{{ pg_user }}" + become: true + postgresql_user: + name: "{{ db_user1 }}" + encrypted: 'true' + password: "md55c8ccfd9d6711fc69a7eae647fc54f51" + db: "{{ db_name }}" + priv: 'test_table2:select' + login_user: "{{ pg_user }}" + register: result + +- name: Check that ansible reports it changed the user + assert: + that: + - result is changed + +- name: Check that the user has the requested permissions (table2) + become_user: "{{ pg_user }}" + become: true + shell: echo "select privilege_type from information_schema.role_table_grants where grantee='{{ db_user1 }}' and table_name='test_table2';" | psql {{ db_name }} + register: result_table2 + +- assert: + that: + - "result_table2.stdout_lines[-1] == '(2 rows)'" + - "'INSERT' in result_table2.stdout" + - "'SELECT' in result_table2.stdout" + +# +# Test priv setting via postgresql_privs module +# (Depends on state from previous _user privs tests) +# + +- name: Revoke a privilege + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + type: "table" + state: "absent" + roles: "{{ db_user1 }}" + privs: "INSERT" + objs: "test_table2" + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + +- name: Check that ansible reports it changed the user + assert: + that: + - result is changed + +- name: Check that the user has the requested permissions (table2) + become_user: "{{ pg_user }}" + become: true + shell: echo "select privilege_type from information_schema.role_table_grants where grantee='{{ db_user1 }}' and table_name='test_table2';" | psql {{ db_name }} + register: result_table2 + +- assert: + that: + - "result_table2.stdout_lines[-1] == '(1 row)'" + - "'SELECT' == '{{ result_table2.stdout_lines[-2] | trim }}'" + +- name: Revoke many privileges on multiple tables + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + state: "absent" + roles: "{{ db_user1 }}" + privs: "INSERT,select,UPDATE,TRUNCATE,REFERENCES,TRIGGER,delete" + objs: "test_table2,test_table1" + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + +- name: Check that ansible reports it changed the user + assert: + that: + - result is changed + +- name: Check that permissions were revoked (table1) + become_user: "{{ pg_user }}" + become: true + shell: echo "select privilege_type from information_schema.role_table_grants where grantee='{{ db_user1 }}' and table_name='test_table1';" | psql {{ db_name }} + register: result_table1 + +- name: Check that permissions were revoked (table2) + become_user: "{{ pg_user }}" + become: true + shell: echo "select privilege_type from information_schema.role_table_grants where grantee='{{ db_user1 }}' and table_name='test_table2';" | psql {{ db_name }} + register: result_table2 + +- assert: + that: + - "result_table1.stdout_lines[-1] == '(0 rows)'" + - "result_table2.stdout_lines[-1] == '(0 rows)'" + +- name: Revoke database privileges + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + type: "database" + state: "absent" + roles: "{{ db_user1 }}" + privs: "Create,connect,TEMP" + objs: "{{ db_name }}" + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + +- name: Check that the user has the requested permissions (database) + become_user: "{{ pg_user }}" + become: true + shell: echo "select datacl from pg_database where datname='{{ db_name }}';" | psql {{ db_name }} + register: result_database + +- assert: + that: + - "result_database.stdout_lines[-1] == '(1 row)'" + - "'{{ db_user1 }}' not in result_database.stdout" + +- name: Grant database privileges + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + type: "database" + state: "present" + roles: "{{ db_user1 }}" + privs: "CREATE,connect" + objs: "{{ db_name }}" + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + register: result + +- name: Check that ansible reports it changed the user + assert: + that: + - result is changed + +- name: Check that the user has the requested permissions (database) + become_user: "{{ pg_user }}" + become: true + shell: echo "select datacl from pg_database where datname='{{ db_name }}';" | psql {{ db_name }} + register: result_database + +- assert: + that: + - "result_database.stdout_lines[-1] == '(1 row)'" + - "'{{ db_user1 }}=Cc' in result_database.stdout" + +- name: Grant a single privilege on a table + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + state: "present" + roles: "{{ db_user1 }}" + privs: "INSERT" + objs: "test_table1" + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + +- name: Check that permissions were added (table1) + become_user: "{{ pg_user }}" + become: true + shell: echo "select privilege_type from information_schema.role_table_grants where grantee='{{ db_user1 }}' and table_name='test_table1';" | psql {{ db_name }} + register: result_table1 + +- assert: + that: + - "result_table1.stdout_lines[-1] == '(1 row)'" + - "'{{ result_table1.stdout_lines[-2] | trim }}' == 'INSERT'" + +- name: Grant many privileges on multiple tables + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + state: "present" + roles: "{{ db_user1 }}" + privs: 'INSERT,SELECT,UPDATE,DELETE,TRUNCATE,REFERENCES,trigger' + objs: "test_table2,test_table1" + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + +- name: Check that permissions were added (table1) + become_user: "{{ pg_user }}" + become: true + shell: echo "select privilege_type from information_schema.role_table_grants where grantee='{{ db_user1 }}' and table_name='test_table1';" | psql {{ db_name }} + register: result_table1 + +- name: Check that permissions were added (table2) + become_user: "{{ pg_user }}" + become: true + shell: echo "select privilege_type from information_schema.role_table_grants where grantee='{{ db_user1 }}' and table_name='test_table2';" | psql {{ db_name }} + register: result_table2 + +- assert: + that: + - "result_table1.stdout_lines[-1] == '(7 rows)'" + - "'INSERT' in result_table1.stdout" + - "'SELECT' in result_table1.stdout" + - "'UPDATE' in result_table1.stdout" + - "'DELETE' in result_table1.stdout" + - "'TRUNCATE' in result_table1.stdout" + - "'REFERENCES' in result_table1.stdout" + - "'TRIGGER' in result_table1.stdout" + - "result_table2.stdout_lines[-1] == '(7 rows)'" + - "'INSERT' in result_table2.stdout" + - "'SELECT' in result_table2.stdout" + - "'UPDATE' in result_table2.stdout" + - "'DELETE' in result_table2.stdout" + - "'TRUNCATE' in result_table2.stdout" + - "'REFERENCES' in result_table2.stdout" + - "'TRIGGER' in result_table2.stdout" + +# Check passing roles with dots +# https://github.com/ansible/ansible/issues/63204 +- name: Create roles for further tests + become_user: "{{ pg_user }}" + become: true + postgresql_user: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: "{{ item }}" + loop: + - "{{ db_user_with_dots1 }}" + - "{{ db_user_with_dots2 }}" + +- name: Pass role with dots in its name to roles parameter + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + state: "present" + roles: "{{ db_user_with_dots1 }}" + privs: "INSERT" + objs: "test_table1" + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + trust_input: false + +- name: Check that permissions were added (table1) + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "select privilege_type from information_schema.role_table_grants where grantee='{{ db_user_with_dots1 }}' and table_name='test_table1'" + register: result + +- assert: + that: + - result.rowcount == 1 + +# We don't need to check anything here, only that nothing failed +- name: Pass role with dots in its name to target_roles parameter + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + state: "present" + roles: "{{ db_user_with_dots1 }}" + privs: "INSERT" + objs: TABLES + type: default_privs + target_roles: "{{ db_user_with_dots2 }}" + trust_input: false + +# Bugfix for https://github.com/ansible-collections/community.general/issues/857 +- name: Test passing lowercase PUBLIC role + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + type: 'database' + privs: 'connect' + role: 'public' + register: result + +- assert: + that: + - result is changed + - result.queries == ["GRANT CONNECT ON database \"{{ db_name }}\" TO PUBLIC;"] + +# +# Cleanup +# +- name: Cleanup db + become_user: "{{ pg_user }}" + become: true + postgresql_db: + name: "{{ db_name }}" + state: "absent" + login_user: "{{ pg_user }}" + +- name: Check that database was destroyed + become_user: "{{ pg_user }}" + become: true + shell: echo "select datname from pg_database where datname = '{{ db_name }}';" | psql -d postgres + register: result + +- assert: + that: + - "result.stdout_lines[-1] == '(0 rows)'" + +- name: Cleanup test user + become_user: "{{ pg_user }}" + become: true + postgresql_user: + name: "{{ item }}" + state: 'absent' + login_user: "{{ pg_user }}" + db: postgres + loop: + - "{{ db_user1 }}" + - "{{ db_user2 }}" + - "{{ db_user3 }}" + - "{{ db_user_with_dots1 }}" + - "{{ db_user_with_dots2 }}" + +- name: Check that they were removed + become_user: "{{ pg_user }}" + become: true + shell: echo "select * from pg_user where usename='{{ db_user1 }}';" | psql -d postgres + register: result + +- assert: + that: + - "result.stdout_lines[-1] == '(0 rows)'" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/postgresql_privs_session_role.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/postgresql_privs_session_role.yml new file mode 100644 index 000000000..e0c083e90 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/postgresql_privs_session_role.yml @@ -0,0 +1,102 @@ +- name: Create a high privileged user + become: true + become_user: "{{ pg_user }}" + postgresql_user: + name: "{{ db_session_role1 }}" + state: "present" + password: "password" + role_attr_flags: "CREATEDB,LOGIN,CREATEROLE" + login_user: "{{ pg_user }}" + db: postgres + +- name: Create a low privileged user using the newly created user + become: true + become_user: "{{ pg_user }}" + postgresql_user: + name: "{{ db_session_role2 }}" + state: "present" + password: "password" + role_attr_flags: "LOGIN" + login_user: "{{ pg_user }}" + session_role: "{{ db_session_role1 }}" + db: postgres + +- name: Create DB as session_role + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: present + name: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + session_role: "{{ db_session_role1 }}" + register: result + +- name: Create table to be able to grant privileges + become_user: "{{ pg_user }}" + become: true + shell: echo "CREATE TABLE test(i int); CREATE TABLE test2(i int);" | psql -AtXq "{{ db_session_role1 }}" + +- name: Grant all privileges on test1 table to low privileged user + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + db: "{{ db_session_role1 }}" + type: table + objs: test + roles: "{{ db_session_role2 }}" + login_user: "{{ pg_user }}" + privs: select + admin_option: true + +- name: Verify admin option was successful for grants + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + db: "{{ db_session_role1 }}" + type: table + objs: test + roles: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + privs: select + session_role: "{{ db_session_role2 }}" + +- name: Verify no grants can be granted for test2 table + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + db: "{{ db_session_role1 }}" + type: table + objs: test2 + roles: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + privs: update + session_role: "{{ db_session_role2 }}" + ignore_errors: true + register: result + +- assert: + that: + - result is failed + +######################## +# Test trust_input param + +- name: Verify trust_input parameter + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + db: "{{ db_session_role1 }}" + type: table + objs: test2 + roles: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + privs: update + session_role: "{{ dangerous_name }}" + trust_input: false + ignore_errors: true + register: result + +- assert: + that: + - result is failed + - result.msg == 'Passed input \'{{ dangerous_name }}\' is potentially dangerous' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/test_target_role.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/test_target_role.yml new file mode 100644 index 000000000..42ece0bad --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_privs/tasks/test_target_role.yml @@ -0,0 +1,120 @@ +# Setup +- name: Create a test user + become_user: "{{ pg_user }}" + become: true + postgresql_user: + name: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + db: postgres + +- name: Create DB + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: present + name: "{{ db_name }}" + owner: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + +- name: Create a user to be given permissions and other tests + postgresql_user: + name: "{{ db_user2 }}" + state: present + encrypted: true + password: password + role_attr_flags: LOGIN + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + +####################################### +# Test default_privs with target_role # +####################################### + +# Test +- name: Grant default privileges for new table objects + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + db: "{{ db_name }}" + objs: TABLES + privs: SELECT + type: default_privs + role: "{{ db_user2 }}" + target_roles: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + register: result + +# Checks +- assert: + that: result is changed + +- name: Check that default privileges are set + become: true + become_user: "{{ pg_user }}" + shell: psql {{ db_name }} -c "SELECT defaclrole, defaclobjtype, defaclacl FROM pg_default_acl a JOIN pg_roles b ON a.defaclrole=b.oid;" -t + register: result + +- assert: + that: "'{{ db_user2 }}=r/{{ db_user1 }}' in '{{ result.stdout_lines[0] }}'" + +# Test +- name: Revoke default privileges for new table objects + become_user: "{{ pg_user }}" + become: true + postgresql_privs: + db: "{{ db_name }}" + state: absent + objs: TABLES + privs: SELECT + type: default_privs + role: "{{ db_user2 }}" + target_roles: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + register: result + +# Checks +- assert: + that: result is changed + +# Cleanup +- name: Remove user given permissions + become_user: "{{ pg_user }}" + become: true + postgresql_user: + name: "{{ db_user2 }}" + state: absent + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + +- name: Remove user owner of objects + become_user: "{{ pg_user }}" + become: true + postgresql_user: + name: "{{ db_user3 }}" + state: absent + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + +- name: Destroy DBs + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: absent + name: "{{ item }}" + login_user: "{{ pg_user }}" + loop: + - "{{ db_name }}" + - "{{ db_session_role1 }}" + +- name: Remove test users + become_user: "{{ pg_user }}" + become: true + postgresql_user: + name: "{{ item }}" + state: absent + db: postgres + login_user: "{{ pg_user }}" + loop: + - "{{ db_user1 }}" + - "{{ db_session_role1 }}" + - "{{ db_session_role2 }}" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_publication/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_publication/aliases new file mode 100644 index 000000000..142e8aa07 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_publication/aliases @@ -0,0 +1,3 @@ +destructive +shippable/posix/group1 +skip/freebsd diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_publication/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_publication/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_publication/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_publication/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_publication/tasks/main.yml new file mode 100644 index 000000000..507c1e234 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_publication/tasks/main.yml @@ -0,0 +1,8 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_publication module +- import_tasks: postgresql_publication_initial.yml + when: postgres_version_resp.stdout is version('10', '>=') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_publication/tasks/postgresql_publication_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_publication/tasks/postgresql_publication_initial.yml new file mode 100644 index 000000000..584a4848b --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_publication/tasks/postgresql_publication_initial.yml @@ -0,0 +1,436 @@ +# 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) +# The file for testing postgresql_copy module. + +- vars: + test_table1: acme1 + test_table2: acme2 + test_table3: acme3 + test_pub: acme_publ + test_role: alice + dangerous_name: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' + test_schema: acme_schema + test_db: acme_db + task_parameters: &task_parameters + become_user: '{{ pg_user }}' + become: true + register: result + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: '{{ test_db }}' + + block: + ################################################# + # Test preparation, create database test objects: + - name: postgresql_publication - create test db + <<: *task_parameters + postgresql_db: + login_user: '{{ pg_user }}' + maintenance_db: postgres + name: '{{ test_db }}' + + - name: postgresql_publication - create test schema + <<: *task_parameters + postgresql_schema: + <<: *pg_parameters + name: '{{ test_schema }}' + + - name: postgresql_publication - create test role + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_role }}' + role_attr_flags: SUPERUSER + + - name: postgresql_publication - create test tables + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: '{{ item }}' + columns: + - id int + loop: + - '{{ test_table1 }}' + - '{{ test_schema }}.{{ test_table2 }}' + - '{{ test_table3 }}' + + + ################ + # Do main tests: + + # Test + - name: postgresql_publication - create publication, check_mode + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + name: '{{ test_pub }}' + trust_input: false + check_mode: true + + - assert: + that: + - result is changed + - result.exists == false + - result.queries == ["CREATE PUBLICATION \"{{ test_pub }}\" FOR ALL TABLES"] + + # Check + - name: postgresql_publication - check that nothing has been changed + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM pg_publication WHERE pubname = '{{ test_pub }}' + + - assert: + that: + - result.rowcount == 0 + + # Test + - name: postgresql_publication - create publication + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + name: '{{ test_pub }}' + trust_input: false + + - assert: + that: + - result is changed + - result.exists == true + - result.queries == ["CREATE PUBLICATION \"{{ test_pub }}\" FOR ALL TABLES"] + - result.owner == '{{ pg_user }}' + - result.alltables == true + - result.tables == [] + - result.parameters.publish != {} + + # Check + - name: postgresql_publication - check that nothing has been changed + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT * FROM pg_publication WHERE pubname = '{{ test_pub }}' + AND pubowner = '10' AND puballtables = 't' + + - assert: + that: + - result.rowcount == 1 + + # Test + - name: postgresql_publication - drop publication, check_mode + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + name: '{{ test_pub }}' + state: absent + trust_input: false + check_mode: true + + - assert: + that: + - result is changed + - result.exists == true + - result.queries == ["DROP PUBLICATION \"{{ test_pub }}\""] + - result.owner == '{{ pg_user }}' + - result.alltables == true + - result.tables == [] + - result.parameters.publish != {} + + # Check + - name: postgresql_publication - check that nothing has been changed + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM pg_publication WHERE pubname = '{{ test_pub }}' + + - assert: + that: + - result.rowcount == 1 + + # Test + - name: postgresql_publication - drop publication + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + name: '{{ test_pub }}' + state: absent + cascade: true + + - assert: + that: + - result is changed + - result.exists == false + - result.queries == ["DROP PUBLICATION \"{{ test_pub }}\" CASCADE"] + + # Check + - name: postgresql_publication - check that publication does not exist + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM pg_publication WHERE pubname = '{{ test_pub }}' + + - assert: + that: + - result.rowcount == 0 + + # Test + - name: postgresql_publication - create publication with tables, owner, params + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + name: '{{ test_pub }}' + owner: '{{ test_role }}' + tables: + - '{{ test_table1 }}' + - '{{ test_schema }}.{{ test_table2 }}' + parameters: + publish: 'insert' + trust_input: false + + - assert: + that: + - result is changed + - result.queries == ["CREATE PUBLICATION \"{{ test_pub }}\" FOR TABLE \"public\".\"{{ test_table1 }}\", \"{{ test_schema }}\".\"{{ test_table2 }}\" WITH (publish = 'insert')", "ALTER PUBLICATION \"{{ test_pub }}\" OWNER TO \"{{ test_role }}\""] + - result.owner == '{{ test_role }}' + - result.tables == ["\"public\".\"{{ test_table1 }}\"", "\"{{ test_schema }}\".\"{{ test_table2 }}\""] + - result.parameters.publish.insert == true + - result.parameters.publish.delete == false + + # Check 1 + - name: postgresql_publication - check that test publication exists + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT * FROM pg_publication WHERE pubname = '{{ test_pub }}' + AND pubowner != '10' AND puballtables = 'f' AND pubinsert = 't' AND pubdelete = 'f' + + - assert: + that: + - result.rowcount == 1 + + # Check 2 + - name: postgresql_publication - check that test_table1 from schema public is in publication + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM pg_publication_tables WHERE pubname = '{{ test_pub }}' AND schemaname = 'public' + + - assert: + that: + - result.rowcount == 1 + + # Check 3 + - name: postgresql_publication - check that test_table2 from test schema is in publication + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM pg_publication_tables WHERE pubname = '{{ test_pub }}' AND schemaname = '{{ test_schema }}' + + - assert: + that: + - result.rowcount == 1 + + # Test + - name: postgresql_publication - test trust_input parameter + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + name: '{{ test_pub }}' + session_role: '{{ dangerous_name }}' + owner: '{{ dangerous_name }}' + trust_input: false + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg is search('is potentially dangerous') + + # Test + - name: postgresql_publication - add table to publication, change owner, check_mode + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + name: '{{ test_pub }}' + owner: '{{ pg_user }}' + tables: + - '{{ test_table1 }}' + - '{{ test_schema }}.{{ test_table2 }}' + - '{{ test_table3 }}' + trust_input: false + check_mode: true + + - assert: + that: + - result is changed + - result.queries == ["ALTER PUBLICATION \"{{ test_pub }}\" ADD TABLE \"public\".\"{{ test_table3 }}\"", "ALTER PUBLICATION \"{{ test_pub }}\" OWNER TO \"{{ pg_user }}\""] + - result.tables == ["\"public\".\"{{ test_table1 }}\"", "\"{{ test_schema }}\".\"{{ test_table2 }}\""] + + # Check + - name: postgresql_publication - check that nothing changes after the previous step + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT * FROM pg_publication WHERE pubname = '{{ test_pub }}' + AND pubowner != '10' AND puballtables = 'f' AND pubinsert = 't' AND pubupdate = 't' + + - assert: + that: + - result.rowcount == 0 + + # Check + - name: postgresql_publication - check that 2 tables are in publication + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM pg_publication_tables WHERE pubname = '{{ test_pub }}' + + - assert: + that: + - result.rowcount == 2 + + # Test + - name: postgresql_publication - add table to publication, change owner + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + name: '{{ test_pub }}' + owner: '{{ pg_user }}' + tables: + - '{{ test_table1 }}' + - '{{ test_schema }}.{{ test_table2 }}' + - '{{ test_table3 }}' + trust_input: false + + - assert: + that: + - result is changed + - result.queries == ["ALTER PUBLICATION \"{{ test_pub }}\" ADD TABLE \"public\".\"{{ test_table3 }}\"", "ALTER PUBLICATION \"{{ test_pub }}\" OWNER TO \"{{ pg_user }}\""] + - result.tables == ["\"public\".\"{{ test_table1 }}\"", "\"{{ test_schema }}\".\"{{ test_table2 }}\"", "\"public\".\"{{ test_table3 }}\""] + + # Check 1 + - name: postgresql_publication - check owner has been changed + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT * FROM pg_publication WHERE pubname = '{{ test_pub }}' AND pubowner = '10' + + - assert: + that: + - result.rowcount == 1 + + # Check 2 + - name: postgresql_publication - check that 3 tables are in publication + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM pg_publication_tables WHERE pubname = '{{ test_pub }}' + + - assert: + that: + - result.rowcount == 3 + + # Test + - name: postgresql_publication - remove table from publication, check_mode + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + name: '{{ test_pub }}' + tables: + - '{{ test_table1 }}' + - '{{ test_schema }}.{{ test_table2 }}' + parameters: + publish: 'insert' + trust_input: false + check_mode: true + + - assert: + that: + - result is changed + - result.queries == ["ALTER PUBLICATION \"{{ test_pub }}\" DROP TABLE \"public\".\"{{ test_table3 }}\""] + - result.tables == ["\"public\".\"{{ test_table1 }}\"", "\"{{ test_schema }}\".\"{{ test_table2 }}\"", "\"public\".\"{{ test_table3 }}\""] + + # Check 1 + - name: postgresql_publication - check that 3 tables are in publication + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM pg_publication_tables WHERE pubname = '{{ test_pub }}' + + - assert: + that: + - result.rowcount == 3 + + # Check 2 + - name: postgresql_publication - check no parameters have been changed + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM pg_publication WHERE pubname = '{{ test_pub }}' AND pubinsert = 't' + + - assert: + that: + - result.rowcount == 1 + + # Test + - name: postgresql_publication - remove table from publication + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + name: '{{ test_pub }}' + tables: + - '{{ test_table1 }}' + - '{{ test_schema }}.{{ test_table2 }}' + parameters: + publish: 'delete' + trust_input: false + + - assert: + that: + - result is changed + - result.queries == ["ALTER PUBLICATION \"{{ test_pub }}\" DROP TABLE \"public\".\"{{ test_table3 }}\"", "ALTER PUBLICATION \"{{ test_pub }}\" SET (publish = 'delete')"] + - result.tables == ["\"public\".\"{{ test_table1 }}\"", "\"{{ test_schema }}\".\"{{ test_table2 }}\""] + + # Check 1 + - name: postgresql_publication - check that 2 tables are in publication + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM pg_publication_tables WHERE pubname = '{{ test_pub }}' + + - assert: + that: + - result.rowcount == 2 + + # Check 2 + - name: postgresql_publication - check parameter has been changed + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM pg_publication WHERE pubname = '{{ test_pub }}' AND pubinsert = 'f' + + - assert: + that: + - result.rowcount == 1 + + always: + ########### + # Clean up: + + - name: postgresql_publication - remove test db + <<: *task_parameters + postgresql_db: + login_user: '{{ pg_user }}' + maintenance_db: postgres + name: '{{ test_db }}' + state: absent + + - name: postgresql_publication - remove test role + <<: *task_parameters + postgresql_user: + login_user: '{{ pg_user }}' + login_db: postgres + name: '{{ test_role }}' + state: absent diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/files/test0.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/files/test0.sql new file mode 100644 index 000000000..e8a5ca03d --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/files/test0.sql @@ -0,0 +1,6 @@ +SELECT version(); + +SELECT story FROM test_table + WHERE id = %s OR story = 'Данные'; + + diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/files/test1.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/files/test1.sql new file mode 100644 index 000000000..028c192d7 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/files/test1.sql @@ -0,0 +1,10 @@ +CREATE FUNCTION add(integer, integer) RETURNS integer + AS 'select $1 + $2;' + LANGUAGE SQL + IMMUTABLE + RETURNS NULL ON NULL INPUT; + +SELECT story FROM test_table + WHERE id = %s OR story = 'Данные'; + +SELECT version(); diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/tasks/main.yml new file mode 100644 index 000000000..7b24dbf92 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/tasks/main.yml @@ -0,0 +1,7 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_query module +- import_tasks: postgresql_query_initial.yml diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/tasks/postgresql_query_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/tasks/postgresql_query_initial.yml new file mode 100644 index 000000000..5d447d608 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_query/tasks/postgresql_query_initial.yml @@ -0,0 +1,604 @@ +- vars: + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: postgres + connect_params: + connect_timeout: 30 + + block: + + - name: postgresql_query - drop test table if exists + become_user: '{{ pg_user }}' + become: true + shell: psql postgres -U "{{ pg_user }}" -t -c "DROP TABLE IF EXISTS test_table;" + ignore_errors: true + + - name: postgresql_query - create test table called test_table + become_user: '{{ pg_user }}' + become: true + shell: psql postgres -U "{{ pg_user }}" -t -c "CREATE TABLE test_table (id int, story text);" + ignore_errors: true + + - name: postgresql_query - insert some data into test_table + become_user: '{{ pg_user }}' + become: true + shell: psql postgres -U "{{ pg_user }}" -t -c "INSERT INTO test_table (id, story) VALUES (1, 'first'), (2, 'second'), (3, 'third');" + ignore_errors: true + + - name: Copy script files + become: true + copy: + src: '{{ item }}' + dest: '~{{ pg_user }}/{{ item }}' + owner: '{{ pg_user }}' + force: true + loop: + - test0.sql + - test1.sql + register: sql_file_created + ignore_errors: true + + - name: postgresql_query - analyze test_table + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: ANALYZE test_table + register: result + ignore_errors: true + + - assert: + that: + - result is changed + - result.query == 'ANALYZE test_table' + - result.query_list == ['ANALYZE test_table'] + - result.rowcount == 0 + - result.statusmessage == 'ANALYZE' + - result.query_result == {} + - result.query_all_results == [{}] + + - name: postgresql_query - run queries from SQL script + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + path_to_script: ~{{ pg_user }}/test0.sql + positional_args: + - 1 + encoding: UTF-8 + as_single_query: false + register: result + ignore_errors: true + when: sql_file_created + + - assert: + that: + - result is not changed + - result.query == "\n\nSELECT story FROM test_table\n WHERE id = 1 OR story = 'Данные'" + - result.query_result[0].story == 'first' + - result.rowcount == 2 + - result.statusmessage == 'SELECT 1' or result.statusmessage == 'SELECT' + when: sql_file_created + + - name: postgresql_query - simple select query to test_table + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM test_table + register: result + ignore_errors: true + + - assert: + that: + - result is not changed + - result.query == 'SELECT * FROM test_table' + - result.rowcount == 3 + - result.statusmessage == 'SELECT 3' or result.statusmessage == 'SELECT' + - result.query_result[0].id == 1 + - result.query_result[1].id == 2 + - result.query_result[2].id == 3 + - result.query_result[0].story == 'first' + - result.query_result[1].story == 'second' + - result.query_result[2].story == 'third' + + - name: postgresql_query - select query with named args + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: SELECT id FROM test_table WHERE id = %(id_val)s AND story = %(story_val)s + named_args: + id_val: 1 + story_val: first + register: result + ignore_errors: true + + - assert: + that: + - result is not changed + - result.query == "SELECT id FROM test_table WHERE id = 1 AND story = 'first'" or result.query == "SELECT id FROM test_table WHERE id = 1 AND story = E'first'" + - result.rowcount == 1 + - result.statusmessage == 'SELECT 1' or result.statusmessage == 'SELECT' + - result.query_result[0].id == 1 + + - name: postgresql_query - select query with positional arguments + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: SELECT story FROM test_table WHERE id = %s AND story = %s + positional_args: + - 2 + - second + register: result + ignore_errors: true + + - assert: + that: + - result is not changed + - result.query == "SELECT story FROM test_table WHERE id = 2 AND story = 'second'" or result.query == "SELECT story FROM test_table WHERE id = 2 AND story = E'second'" + - result.rowcount == 1 + - result.statusmessage == 'SELECT 1' or result.statusmessage == 'SELECT' + - result.query_result[0].story == 'second' + + - name: postgresql_query - simple update query + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: UPDATE test_table SET story = 'new' WHERE id = 3 + register: result + ignore_errors: true + + - assert: + that: + - result is changed + - result.query == "UPDATE test_table SET story = 'new' WHERE id = 3" + - result.rowcount == 1 + - result.statusmessage == 'UPDATE 1' + - result.query_result == {} + + - name: check the previous update + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM test_table WHERE story = 'new' AND id = 3 + register: result + + - assert: + that: + - result.rowcount == 1 + + - name: postgresql_query - simple update query in check_mode + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: UPDATE test_table SET story = 'CHECK_MODE' WHERE id = 3 + register: result + check_mode: true + + - assert: + that: + - result is changed + - result.query == "UPDATE test_table SET story = 'CHECK_MODE' WHERE id = 3" + - result.rowcount == 1 + - result.statusmessage == 'UPDATE 1' + - result.query_result == {} + + - name: check the previous update that nothing has been changed + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM test_table WHERE story = 'CHECK_MODE' AND id = 3 + register: result + + - assert: + that: + - result.rowcount == 0 + + - name: postgresql_query - try to update not existing row + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: UPDATE test_table SET story = 'new' WHERE id = 100 + register: result + ignore_errors: true + + - assert: + that: + - result is not changed + - result.query == "UPDATE test_table SET story = 'new' WHERE id = 100" + - result.rowcount == 0 + - result.statusmessage == 'UPDATE 0' + - result.query_result == {} + + - name: postgresql_query - insert query + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: INSERT INTO test_table (id, story) VALUES (%s, %s) + positional_args: + - 4 + - fourth + register: result + ignore_errors: true + + - assert: + that: + - result is changed + - result.query == "INSERT INTO test_table (id, story) VALUES (4, 'fourth')" or result.query == "INSERT INTO test_table (id, story) VALUES (4, E'fourth')" + - result.rowcount == 1 + - result.statusmessage == 'INSERT 0 1' + - result.query_result == {} + + - name: postgresql_query - truncate test_table + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: TRUNCATE test_table + register: result + ignore_errors: true + + - assert: + that: + - result is changed + - result.query == "TRUNCATE test_table" + - result.rowcount == 0 + - result.statusmessage == 'TRUNCATE TABLE' + - result.query_result == {} + + - name: postgresql_query - alter test_table + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: ALTER TABLE test_table ADD COLUMN foo int + register: result + ignore_errors: true + + - assert: + that: + - result is changed + - result.query == "ALTER TABLE test_table ADD COLUMN foo int" + - result.rowcount == 0 + - result.statusmessage == 'ALTER TABLE' + + - name: postgresql_query - vacuum without autocommit must fail + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: VACUUM + register: result + ignore_errors: true + + - assert: + that: + - result.failed == true + + - name: postgresql_query - autocommit in check_mode must fail + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: VACUUM + autocommit: true + check_mode: true + register: result + ignore_errors: true + + - assert: + that: + - result.failed == true + - result.msg == "Using autocommit is mutually exclusive with check_mode" + + - name: postgresql_query - vacuum with autocommit + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: VACUUM + autocommit: true + register: result + + - assert: + that: + - result is changed + - result.query == "VACUUM" + - result.rowcount == 0 + - result.statusmessage == 'VACUUM' + - result.query_result == {} + + - name: postgresql_query - create test table for issue 59955 + become_user: '{{ pg_user }}' + become: true + postgresql_table: + login_user: '{{ pg_user }}' + login_db: postgres + name: test_array_table + columns: + - arr_col int[] + when: postgres_version_resp.stdout is version('9.4', '>=') + + - set_fact: + my_list: + - 1 + - 2 + - 3 + my_arr: '{1, 2, 3}' + when: postgres_version_resp.stdout is version('9.4', '>=') + + - name: postgresql_query - insert array into test table by positional args + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: INSERT INTO test_array_table (arr_col) VALUES (%s) + positional_args: + - '{{ my_list }}' + register: result + when: postgres_version_resp.stdout is version('9.4', '>=') + + - assert: + that: + - result is changed + - result.query == "INSERT INTO test_array_table (arr_col) VALUES ('{1, 2, 3}')" + when: postgres_version_resp.stdout is version('9.4', '>=') + + - name: postgresql_query - select array from test table by passing positional_args + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM test_array_table WHERE arr_col = %s + positional_args: + - '{{ my_list }}' + register: result + when: postgres_version_resp.stdout is version('9.4', '>=') + + - assert: + that: + - result is not changed + - result.query == "SELECT * FROM test_array_table WHERE arr_col = '{1, 2, 3}'" + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('9.4', '>=') + + - name: postgresql_query - select array from test table by passing named_args + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM test_array_table WHERE arr_col = %(arr_val)s + named_args: + arr_val: + - '{{ my_list }}' + register: result + when: postgres_version_resp.stdout is version('9.4', '>=') + + - assert: + that: + - result is not changed + - result.query == "SELECT * FROM test_array_table WHERE arr_col = '{1, 2, 3}'" + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('9.4', '>=') + + - name: postgresql_query - select array from test table by passing positional_args as a string + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM test_array_table WHERE arr_col = %s + positional_args: + - '{{ my_arr|string }}' + trust_input: true + register: result + when: postgres_version_resp.stdout is version('9.4', '>=') + + - assert: + that: + - result is not changed + - result.query == "SELECT * FROM test_array_table WHERE arr_col = '{1, 2, 3}'" + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('9.4', '>=') + + - name: postgresql_query - test trust_input parameter + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + session_role: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' + query: SELECT version() + trust_input: false + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - result.msg is search('is potentially dangerous') + + - name: postgresql_query - clean up + become_user: '{{ pg_user }}' + become: true + postgresql_table: + login_user: '{{ pg_user }}' + login_db: postgres + name: test_array_table + state: absent + when: postgres_version_resp.stdout is version('9.4', '>=') + + ############################# + # Check search_path parameter + + - name: postgresql_set - create test schemas + become_user: '{{ pg_user }}' + become: true + postgresql_schema: + login_user: '{{ pg_user }}' + login_db: postgres + name: '{{ item }}' + loop: + - query_test1 + - query_test2 + + - name: postgresql_set - create test tables + become_user: '{{ pg_user }}' + become: true + postgresql_table: + login_user: '{{ pg_user }}' + login_db: postgres + name: '{{ item }}' + columns: + - id int + loop: + - 'query_test1.test1' + - 'query_test2.test2' + + - name: postgresql_query - insert data + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: 'INSERT INTO {{ item }} (id) VALUES (1)' + search_path: + - query_test1 + - query_test2 + loop: + - test1 + - test2 + + - name: postgresql_query - get data + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: 'SELECT id FROM test1' + search_path: + - query_test1 + - query_test2 + register: result + + - assert: + that: + - result.rowcount == 1 + + - name: postgresql_query - get data, must fail + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: 'SELECT id FROM test1' + register: result + ignore_errors: true + + - assert: + that: + - result is failed + + # Tests for the as_single_query option + - name: Run queries from SQL script as a single query + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + path_to_script: ~{{ pg_user }}/test1.sql + positional_args: + - 1 + encoding: UTF-8 + as_single_query: true + register: result + + - name: > + Must pass. Not changed because we can only + check statusmessage of the last query + assert: + that: + - result is not changed + - result.statusmessage == 'SELECT 1' or result.statusmessage == 'SELECT' + - result.query_list[0] == "CREATE FUNCTION add(integer, integer) RETURNS integer\n AS 'select $1 + $2;'\n LANGUAGE SQL\n IMMUTABLE\n RETURNS NULL ON NULL INPUT;\n\nSELECT story FROM test_table\n WHERE id = %s OR story = 'Данные';\n\nSELECT version();\n" + + ############################################################################# + # Issue https://github.com/ansible-collections/community.postgresql/issues/45 + - name: Create table containing a decimal value + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: CREATE TABLE blabla (id int, num decimal) + + - name: Insert data + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: INSERT INTO blabla (id, num) VALUES (1, 1::decimal) + + - name: Get data + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: SELECT * FROM blabla + register: result + + - assert: + that: + - result.rowcount == 1 + + ############################################################################# + # Issue https://github.com/ansible-collections/community.postgresql/issues/47 + - name: Get datetime.timedelta value + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: "SELECT EXTRACT(epoch from make_interval(secs => 3)) AS extract" + register: result + when: postgres_version_resp.stdout is version('10', '>=') + + - assert: + that: + - result.rowcount == 1 + - result.query_result[0]["extract"] == 3 or result.query_result[0]["extract"] == 3.0 + when: postgres_version_resp.stdout is version('10', '>=') + + - name: Get interval value + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: "SELECT make_interval(secs => 3)" + register: result + when: postgres_version_resp.stdout is version('10', '>=') + + - assert: + that: + - result.rowcount == 1 + - result.query_result[0]["make_interval"] == "0:00:03" + when: postgres_version_resp.stdout is version('10', '>=') + + ############################################################################## + # Issue https://github.com/ansible-collections/community.postgresql/issues/312 + - name: Run several queries + become_user: '{{ pg_user }}' + become: true + postgresql_query: + <<: *pg_parameters + query: + - SELECT 1 + - SELECT 1 + - SELECT 1 + register: result + + - assert: + that: + - result.rowcount == 3 + - result.query_result == [{"?column?": 1}] + - 'result.query_all_results == [[{"?column?": 1}], [{"?column?": 1}], [{"?column?": 1}]]' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/defaults/main.yml new file mode 100644 index 000000000..ff6dd5cb9 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/defaults/main.yml @@ -0,0 +1,7 @@ +--- +db_name: 'ansible_db' +db_user1: 'ansible_db_user1' +db_user2: 'ansible_db_user2' +dangerous_name: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' +db_session_role1: 'session_role1' +db_session_role2: 'session_role2' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/tasks/main.yml new file mode 100644 index 000000000..d894dd040 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/tasks/main.yml @@ -0,0 +1,9 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- import_tasks: postgresql_schema_session_role.yml + +# Initial CI tests of postgresql_schema module +- import_tasks: postgresql_schema_initial.yml diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/tasks/postgresql_schema_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/tasks/postgresql_schema_initial.yml new file mode 100644 index 000000000..58832f049 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/tasks/postgresql_schema_initial.yml @@ -0,0 +1,331 @@ +--- + +# Setup +- name: Create test roles + postgresql_user: + name: "{{ item }}" + state: present + encrypted: true + password: password + role_attr_flags: LOGIN + db: postgres + login_user: "{{ pg_user }}" + loop: + - "{{ db_user1 }}" + - "{{ db_user2 }}" + +- name: Create DB + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: present + name: "{{ db_name }}" + owner: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + +# Test: CREATE SCHEMA in checkmode +- name: Create a new schema with name "acme" in check_mode + become_user: "{{ pg_user }}" + become: true + check_mode: true + postgresql_schema: + database: "{{ db_name }}" + name: acme + login_user: "{{ pg_user }}" + register: result + +# Checks +- assert: + that: + - result is changed + - result.schema == 'acme' + +- name: Check that the new schema "acme" not exists + become: true + become_user: "{{ pg_user }}" + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'acme'" + register: result + +- assert: + that: + - result.rowcount == 0 + +# Test: CREATE SCHEMA +- name: Create a new schema with name "acme" + become_user: "{{ pg_user }}" + become: true + postgresql_schema: + database: "{{ db_name }}" + name: acme + login_user: "{{ pg_user }}" + trust_input: true + register: result + +# Checks +- assert: + that: + - result is changed + - result.schema == 'acme' + - result.queries == [ 'CREATE SCHEMA "acme"' ] + +- name: Check that the new schema "acme" exists + become: true + become_user: "{{ pg_user }}" + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'acme'" + register: result + +- assert: + that: + - result.rowcount == 1 + +# Test: DROP SCHEMA in checkmode +- name: Drop schema "acme" in check_mode + become_user: "{{ pg_user }}" + become: true + check_mode: true + postgresql_schema: + database: "{{ db_name }}" + name: acme + state: absent + login_user: "{{ pg_user }}" + register: result + +# Checks +- assert: + that: + - result is not changed + +- name: Check that the new schema "acme" still exists + become: true + become_user: "{{ pg_user }}" + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'acme'" + register: result + +- assert: + that: + - result.rowcount == 1 + +# Test: DROP SCHEMA +- name: Drop schema "acme" + become_user: "{{ pg_user }}" + become: true + postgresql_schema: + database: "{{ db_name }}" + name: acme + state: absent + login_user: "{{ pg_user }}" + register: result + +# Checks +- assert: + that: + - result is changed + - result.queries == [ 'DROP SCHEMA "acme"' ] + +- name: Check that no schema "acme" exists + become: true + become_user: "{{ pg_user }}" + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'acme'" + register: result + ignore_errors: true + +- assert: + that: + - result.rowcount == 0 + +# Test: trust_input parameter +- name: Create a new schema with potentially dangerous owner name + become_user: "{{ pg_user }}" + become: true + postgresql_schema: + database: "{{ db_name }}" + name: acme + login_user: "{{ pg_user }}" + owner: "{{ dangerous_name }}" + trust_input: false + register: result + ignore_errors: true + +# Checks +- assert: + that: + - result is failed + - result.msg == 'Passed input \'{{ dangerous_name }}\' is potentially dangerous' + +# Test: CREATE SCHEMA; WITH TABLE for DROP CASCADE test +- name: Create a new schema "acme" + become_user: "{{ pg_user }}" + become: true + postgresql_schema: + database: "{{ db_name }}" + name: acme + login_user: "{{ pg_user }}" + register: result + +- name: Create table in schema for DROP CASCADE check + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "CREATE TABLE acme.table1()" + register: result2 + +# Checks +- assert: + that: + - result is changed + - result.schema == 'acme' + - result.queries == [ 'CREATE SCHEMA "acme"' ] + - result2.changed == true + - result2.statusmessage == 'CREATE TABLE' + +- name: Check that the new schema "acme" exists + become: true + become_user: "{{ pg_user }}" + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT schema_name,schema_owner FROM information_schema.schemata WHERE schema_name = 'acme'" + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: Check that the new table "table1" in schema 'acme' exists + become: true + become_user: "{{ pg_user }}" + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'acme' AND tablename = 'table1')" + register: result + +- assert: + that: + - result.rowcount == 1 + +# Test: DROP SCHEMA ... CASCADE; +- name: Drop schema "acme" with cascade + become_user: "{{ pg_user }}" + become: true + postgresql_schema: + database: "{{ db_name }}" + name: acme + state: absent + cascade_drop: true + login_user: "{{ pg_user }}" + register: result + +# Checks +- assert: + that: + - result is changed + - result.queries == [ 'DROP SCHEMA "acme" CASCADE' ] + +- name: Check that no schema "acme" exists + become: true + become_user: "{{ pg_user }}" + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'acme'" + register: result + ignore_errors: true + +- assert: + that: + - result.rowcount == 0 + +# Test: CREATE SCHEMA WITH OWNER ...; +- name: Create a new schema "acme" with a user "{{ db_user2 }}" who will own it + become_user: "{{ pg_user }}" + become: true + postgresql_schema: + database: "{{ db_name }}" + name: acme + owner: "{{ db_user2 }}" + login_user: "{{ pg_user }}" + register: result + +# Checks +- assert: + that: + - result is changed + - result.schema == 'acme' + - result.queries == [ 'CREATE SCHEMA "acme" AUTHORIZATION "{{ db_user2 }}"' ] + +- name: Check that the new schema "acme" exists and "{{ db_user2 }}" own it + become: true + become_user: "{{ pg_user }}" + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT schema_name,schema_owner FROM information_schema.schemata WHERE schema_name = 'acme' AND schema_owner = '{{ db_user2 }}'" + register: result + ignore_errors: true + +- assert: + that: + - result.rowcount == 1 + +# Test: DROP SCHEMA +- name: Drop schema "acme" + become_user: "{{ pg_user }}" + become: true + postgresql_schema: + database: "{{ db_name }}" + name: acme + state: absent + login_user: "{{ pg_user }}" + register: result + +# Checks +- assert: + that: + - result is changed + - result.queries == [ 'DROP SCHEMA "acme"' ] + +- name: Check that no schema "acme" exists + become: true + become_user: "{{ pg_user }}" + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'acme'" + register: result + ignore_errors: true + +- assert: + that: + - result.rowcount == 0 + + +# Cleanup +- name: Remove user + postgresql_user: + name: "{{ db_user2 }}" + state: absent + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + +- name: Destroy DB + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: absent + name: "{{ db_name }}" + login_user: "{{ pg_user }}" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/tasks/postgresql_schema_session_role.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/tasks/postgresql_schema_session_role.yml new file mode 100644 index 000000000..4b8af75ff --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_schema/tasks/postgresql_schema_session_role.yml @@ -0,0 +1,78 @@ +- name: Create a high privileged user + become: true + become_user: "{{ pg_user }}" + postgresql_user: + name: "{{ db_session_role1 }}" + state: "present" + password: "password" + role_attr_flags: "CREATEDB,LOGIN,CREATEROLE" + login_user: "{{ pg_user }}" + db: postgres + +- name: Create DB as session_role + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: present + name: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + session_role: "{{ db_session_role1 }}" + register: result + +- name: Create schema in own database + become_user: "{{ pg_user }}" + become: true + postgresql_schema: + database: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + name: "{{ db_session_role1 }}" + session_role: "{{ db_session_role1 }}" + +- name: Create schema in own database, should be owned by session_role + become_user: "{{ pg_user }}" + become: true + postgresql_schema: + database: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + name: "{{ db_session_role1 }}" + owner: "{{ db_session_role1 }}" + register: result + +- assert: + that: + - result is not changed + +- name: Fail when creating schema in postgres database as a regular user + become_user: "{{ pg_user }}" + become: true + postgresql_schema: + database: postgres + login_user: "{{ pg_user }}" + name: "{{ db_session_role1 }}" + session_role: "{{ db_session_role1 }}" + ignore_errors: true + register: result + +- assert: + that: + - result is failed + +- name: Drop test db + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: absent + name: "{{ db_session_role1 }}" + login_user: "{{ pg_user }}" + +- name: Drop test users + become: true + become_user: "{{ pg_user }}" + postgresql_user: + name: "{{ item }}" + state: absent + login_user: "{{ pg_user }}" + db: postgres + with_items: + - "{{ db_session_role1 }}" + - "{{ db_session_role2 }}" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/defaults/main.yml new file mode 100644 index 000000000..35e711215 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/defaults/main.yml @@ -0,0 +1 @@ +db_default: postgres diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test0.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test0.sql new file mode 100644 index 000000000..fb9ce5162 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test0.sql @@ -0,0 +1,4 @@ +SELECT version(); + +SELECT story FROM test_table + WHERE id = %s OR story = 'Данные'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test1.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test1.sql new file mode 100644 index 000000000..028c192d7 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test1.sql @@ -0,0 +1,10 @@ +CREATE FUNCTION add(integer, integer) RETURNS integer + AS 'select $1 + $2;' + LANGUAGE SQL + IMMUTABLE + RETURNS NULL ON NULL INPUT; + +SELECT story FROM test_table + WHERE id = %s OR story = 'Данные'; + +SELECT version(); diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test10.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test10.sql new file mode 100644 index 000000000..cbfdc457a --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test10.sql @@ -0,0 +1 @@ +SELECT EXTRACT(epoch from make_interval(secs => 3)) AS extract diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test11.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test11.sql new file mode 100644 index 000000000..6c1596f96 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test11.sql @@ -0,0 +1 @@ +SELECT make_interval(secs => 3) diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test12.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test12.sql new file mode 100644 index 000000000..33bd94eba --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test12.sql @@ -0,0 +1 @@ +INSERT INTO test2 (id) VALUES (1) diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test2.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test2.sql new file mode 100644 index 000000000..cba55b34a --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test2.sql @@ -0,0 +1 @@ +ANALYZE test_table diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test3.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test3.sql new file mode 100644 index 000000000..a2ac43509 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test3.sql @@ -0,0 +1,4 @@ +SELECT version(); + +SELECT story FROM test_table + WHERE id = %(item)s OR story = 'Данные'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test4.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test4.sql new file mode 100644 index 000000000..5ea74a292 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test4.sql @@ -0,0 +1 @@ +INSERT INTO test_array_table (arr_col) VALUES (%s) diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test5.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test5.sql new file mode 100644 index 000000000..e631dfb5a --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test5.sql @@ -0,0 +1 @@ +SELECT * FROM test_array_table WHERE arr_col = %s diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test6.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test6.sql new file mode 100644 index 000000000..48d210d4e --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test6.sql @@ -0,0 +1 @@ +SELECT * FROM test_array_table WHERE arr_col = %(arr_val)s; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test7.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test7.sql new file mode 100644 index 000000000..96dfaebdc --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test7.sql @@ -0,0 +1 @@ +INSERT INTO test1 (id) VALUES (1) diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test8.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test8.sql new file mode 100644 index 000000000..8ca7c86dc --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test8.sql @@ -0,0 +1 @@ +SELECT id FROM test1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test9.sql b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test9.sql new file mode 100644 index 000000000..e52a59a3d --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/files/test9.sql @@ -0,0 +1 @@ +SELECT * FROM blabla diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/tasks/main.yml new file mode 100644 index 000000000..ae5922853 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/tasks/main.yml @@ -0,0 +1,7 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_query module +- import_tasks: postgresql_script_initial.yml diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/tasks/postgresql_script_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/tasks/postgresql_script_initial.yml new file mode 100644 index 000000000..729f1670a --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_script/tasks/postgresql_script_initial.yml @@ -0,0 +1,311 @@ +- vars: + task_parameters: &task_parameters + become_user: '{{ pg_user }}' + become: true + register: result + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: '{{ db_default }}' + block: + - name: Drop test table if exists + <<: *task_parameters + shell: psql postgres -U "{{ pg_user }}" -t -c "DROP TABLE IF EXISTS test_table;" + ignore_errors: true + + - name: Create test table called test_table + <<: *task_parameters + shell: psql postgres -U "{{ pg_user }}" -t -c "CREATE TABLE test_table (id int, story text);" + ignore_errors: true + + - name: Insert some data into test_table + <<: *task_parameters + shell: psql postgres -U "{{ pg_user }}" -t -c "INSERT INTO test_table (id, story) VALUES (1, 'first'), (2, 'second'), (3, 'third');" + ignore_errors: true + + - name: Copy script files + become: true + copy: + src: '{{ item }}' + dest: '~{{ pg_user }}/{{ item }}' + owner: '{{ pg_user }}' + force: true + loop: + - test0.sql + - test1.sql + - test2.sql + - test3.sql + - test4.sql + - test5.sql + - test6.sql + - test7.sql + - test8.sql + - test9.sql + - test10.sql + - test11.sql + - test12.sql + + - name: Analyze test_table + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + path: '~{{ pg_user }}/test2.sql' + + - assert: + that: + - result is changed + - result.query == 'ANALYZE test_table\n' + - result.rowcount != 0 + - result.statusmessage == 'ANALYZE' + - result.query_result == {} + + - name: Run queries from SQL script using positional_args + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + path: ~{{ pg_user }}/test0.sql + positional_args: + - 1 + encoding: UTF-8 + + - assert: + that: + - result is changed + - result.query == "SELECT version();\n\nSELECT story FROM test_table\n WHERE id = 1 OR story = 'Данные';\n" + - result.query_result[0].story == 'first' + - result.rowcount == 1 + - result.statusmessage == 'SELECT 1' or result.statusmessage == 'SELECT' + + - name: Run queries from SQL script using named_args + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + path: ~{{ pg_user }}/test3.sql + named_args: + item: 1 + encoding: UTF-8 + + - assert: + that: + - result is changed + - result.query == "SELECT version();\n\nSELECT story FROM test_table\n WHERE id = 1 OR story = 'Данные';\n" + - result.query_result[0].story == 'first' + - result.rowcount == 1 + - result.statusmessage == 'SELECT 1' or result.statusmessage == 'SELECT' + + - name: Create test table for issue 59955 + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: test_array_table + columns: + - arr_col int[] + when: postgres_version_resp.stdout is version('9.4', '>=') + + - set_fact: + my_list: + - 1 + - 2 + - 3 + my_arr: '{1, 2, 3}' + when: postgres_version_resp.stdout is version('9.4', '>=') + + - name: Insert array into test table by positional args + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + path: ~{{ pg_user }}/test4.sql + positional_args: + - '{{ my_list }}' + when: postgres_version_resp.stdout is version('9.4', '>=') + + - assert: + that: + - result is changed + - result.query == "INSERT INTO test_array_table (arr_col) VALUES ('{1, 2, 3}')\n" + when: postgres_version_resp.stdout is version('9.4', '>=') + + - name: Select array from test table by passing positional_args + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + path: ~{{ pg_user }}/test5.sql + positional_args: + - '{{ my_list }}' + when: postgres_version_resp.stdout is version('9.4', '>=') + + - assert: + that: + - result is changed + - result.query == "SELECT * FROM test_array_table WHERE arr_col = '{1, 2, 3}'\n" + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('9.4', '>=') + + - name: Select array from test table by passing named_args + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + path: ~{{ pg_user }}/test6.sql + named_args: + arr_val: + - '{{ my_list }}' + when: postgres_version_resp.stdout is version('9.4', '>=') + + - assert: + that: + - result is changed + - result.query == "SELECT * FROM test_array_table WHERE arr_col = '{1, 2, 3}';\n" + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('9.4', '>=') + + - name: Select array from test table by passing positional_args as a string + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + path: ~{{ pg_user }}/test5.sql + positional_args: + - '{{ my_arr|string }}' + trust_input: true + when: postgres_version_resp.stdout is version('9.4', '>=') + + - assert: + that: + - result is changed + - result.query == "SELECT * FROM test_array_table WHERE arr_col = '{1, 2, 3}'\n" + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('9.4', '>=') + + - name: Test trust_input parameter + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + session_role: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' + path: ~{{ pg_user }}/test5.sql + trust_input: false + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg is search('is potentially dangerous') + + - name: Clean up + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: test_array_table + state: absent + when: postgres_version_resp.stdout is version('9.4', '>=') + + ############################# + # Check search_path parameter + + - name: Create test schemas + <<: *task_parameters + postgresql_schema: + <<: *pg_parameters + name: '{{ item }}' + loop: + - query_test1 + - query_test2 + + - name: Create test tables + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: '{{ item }}' + columns: + - id int + loop: + - 'query_test1.test1' + - 'query_test2.test2' + + - name: Insert data + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + path: ~{{ pg_user }}/{{ item }}.sql + search_path: + - query_test1 + - query_test2 + loop: + - test7 + - test12 + + - name: Get data + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + path: ~{{ pg_user }}/test8.sql + search_path: + - query_test1 + - query_test2 + + - assert: + that: + - result.rowcount == 1 + + - name: Get data, must fail as we don't specify search_path + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + path: ~{{ pg_user }}/test8.sql + ignore_errors: true + + - assert: + that: + - result is failed + + ############################################################################# + # Issue https://github.com/ansible-collections/community.postgresql/issues/45 + - name: Create table containing a decimal value + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: blabla + columns: + - id int + - num decimal + + - name: Insert data + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: INSERT INTO blabla (id, num) VALUES (1, 1::decimal) + + - name: Get data + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + path: ~{{ pg_user }}/test9.sql + + - assert: + that: + - result.rowcount == 1 + + ############################################################################# + # Issue https://github.com/ansible-collections/community.postgresql/issues/47 + - name: Get datetime.timedelta value + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + path: ~{{ pg_user }}/test10.sql + when: postgres_version_resp.stdout is version('10', '>=') + + - assert: + that: + - result.rowcount == 1 + - result.query_result[0]["extract"] == 3 or result.query_result[0]["extract"] == 3.0 + when: postgres_version_resp.stdout is version('10', '>=') + + - name: Get interval value + <<: *task_parameters + postgresql_script: + <<: *pg_parameters + path: ~{{ pg_user }}/test11.sql + when: postgres_version_resp.stdout is version('10', '>=') + + - assert: + that: + - result.rowcount == 1 + - result.query_result[0]["make_interval"] == "0:00:03" + when: postgres_version_resp.stdout is version('10', '>=') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/defaults/main.yml new file mode 100644 index 000000000..049b5531f --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/defaults/main.yml @@ -0,0 +1,5 @@ +--- +db_name: 'ansible_db' +db_user1: 'ansible_db_user1' +db_user2: 'ansible_db_user2' +db_default: 'postgres' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/tasks/main.yml new file mode 100644 index 000000000..b53069005 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/tasks/main.yml @@ -0,0 +1,8 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_sequence module +- import_tasks: postgresql_sequence_initial.yml + when: postgres_version_resp.stdout is version('9.4', '>=') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/tasks/postgresql_sequence_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/tasks/postgresql_sequence_initial.yml new file mode 100644 index 000000000..c498033bd --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_sequence/tasks/postgresql_sequence_initial.yml @@ -0,0 +1,730 @@ +--- +# Copyright: (c) 2019, Tobias Birkefeld (@tcraxs) <t@craxs.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Preparation for tests. +- name: postgresql_sequence - create a user to be owner of a database + become_user: "{{ pg_user }}" + become: true + postgresql_user: + name: "{{ db_user1 }}" + state: present + encrypted: true + password: password + role_attr_flags: LOGIN + db: "{{ db_default }}" + login_user: "{{ pg_user }}" + +- name: postgresql_sequence - create DB + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: present + name: "{{ db_name }}" + owner: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + +- name: Create a user to be owner of a sequence + become_user: "{{ pg_user }}" + become: true + postgresql_user: + name: "{{ db_user2 }}" + state: present + encrypted: true + password: password + role_attr_flags: LOGIN + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + +- name: postgresql_sequence - create a schema + become_user: "{{ pg_user }}" + become: true + postgresql_schema: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_schema + +#################### +# Test: create sequence in checkmode +- name: postgresql_sequence - create a new sequence with name "foobar" in check_mode + become_user: "{{ pg_user }}" + become: true + check_mode: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'foobar' + - result.queries == ["CREATE SEQUENCE \"public\".\"foobar\""] + +# Real SQL check +- name: postgresql_sequence - check that the new sequence "foobar" not exists + become: true + become_user: "{{ pg_user }}" + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 0 + - result.statusmessage == 'SELECT 0' + +#################### +# Test: create sequence +- name: postgresql_sequence - create a new sequence with name "foobar" + become_user: "{{ pg_user }}" + become: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'foobar' + - result.queries == ["CREATE SEQUENCE \"public\".\"foobar\""] + +# Real SQL check +- name: postgresql_sequence - check that the new sequence "foobar" exists + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: drop sequence in checkmode +- name: postgresql_sequence - drop a sequence called foobar + become_user: "{{ pg_user }}" + become: true + check_mode: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar + state: absent + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'foobar' + - result.queries == ["DROP SEQUENCE \"public\".\"foobar\""] + +# Real SQL check +- name: postgresql_sequence - check that the sequence "foobar" still exists + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: drop sequence +- name: postgresql_sequence - drop a sequence called foobar + become_user: "{{ pg_user }}" + become: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar + state: absent + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'foobar' + - result.queries == ["DROP SEQUENCE \"public\".\"foobar\""] + +# Real SQL check +- name: postgresql_sequence - check that the sequence "foobar" not exists + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 0 + +#################### +# Test: drop nonexistent sequence +- name: postgresql_sequence - drop a sequence called foobar which does not exists + become_user: "{{ pg_user }}" + become: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar + state: absent + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is not changed + - result.sequence == 'foobar' + - result.queries == [] + +# Real SQL check +- name: postgresql_sequence - check that the sequence "foobar" not exists + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 0 + +#################### +# Test: create sequence with options +- name: postgresql_sequence - create an descending sequence called foobar_desc, starting at 101 and which cycle between 1 to 1000 + become_user: "{{ pg_user }}" + become: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_desc + increment: -1 + start: 101 + minvalue: 1 + maxvalue: 1000 + cycle: true + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'foobar_desc' + - result.increment == '-1' + - result.minvalue == '1' + - result.maxvalue == '1000' + - result.cycle == 'YES' + - result.queries == ["CREATE SEQUENCE \"public\".\"foobar_desc\" INCREMENT BY -1 MINVALUE 1 MAXVALUE 1000 START WITH 101 CYCLE"] + +# Real SQL check +- name: postgresql_sequence - check that the new sequence "foobar_desc" exists + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar_desc'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: rename a sequence in checkmode +- name: postgresql_sequence - rename an existing sequence named foobar_desc to foobar_with_options + become_user: "{{ pg_user }}" + become: true + check_mode: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_desc + rename_to: foobar_with_options + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'foobar_desc' + - result.newname == 'foobar_with_options' + - result.queries == ["ALTER SEQUENCE \"public\".\"foobar_desc\" RENAME TO \"foobar_with_options\""] + +# Real SQL check +- name: postgresql_sequence - check that the sequence "foobar_desc" still exists and is not renamed + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar_desc'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: rename a sequence +- name: postgresql_sequence - rename an existing sequence named foobar_desc to foobar_with_options + become_user: "{{ pg_user }}" + become: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_desc + rename_to: foobar_with_options + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'foobar_desc' + - result.newname == 'foobar_with_options' + - result.queries == ["ALTER SEQUENCE \"public\".\"foobar_desc\" RENAME TO \"foobar_with_options\""] + +# Real SQL check +- name: postgresql_sequence - check that the renamed sequence "foobar_with_options" exists + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar_with_options'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: change schema of a sequence in checkmode +- name: postgresql_sequence - change schema of an existing sequence from public to foobar_schema + become_user: "{{ pg_user }}" + become: true + check_mode: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_with_options + newschema: foobar_schema + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'foobar_with_options' + - result.schema == 'public' + - result.newschema == 'foobar_schema' + - result.queries == ["ALTER SEQUENCE \"public\".\"foobar_with_options\" SET SCHEMA \"foobar_schema\""] + +# Real SQL check +- name: postgresql_sequence - check that the sequence "foobar_with_options" still exists in the old schema + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name,sequence_schema FROM information_schema.sequences WHERE sequence_name = 'foobar_with_options' AND sequence_schema = 'public'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: change schema of a sequence +- name: postgresql_sequence - change schema of an existing sequence from public to foobar_schema + become_user: "{{ pg_user }}" + become: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_with_options + newschema: foobar_schema + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'foobar_with_options' + - result.schema == 'public' + - result.newschema == 'foobar_schema' + - result.queries == ["ALTER SEQUENCE \"public\".\"foobar_with_options\" SET SCHEMA \"foobar_schema\""] + +# Real SQL check +- name: postgresql_sequence - check that the sequence "foobar_with_options" exists in new schema + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name,sequence_schema FROM information_schema.sequences WHERE sequence_name = 'foobar_with_options' AND sequence_schema = 'foobar_schema'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: change owner of a sequence in checkmode +- name: postgresql_sequence - change owner of an existing sequence from "{{ pg_user }}" to "{{ db_user1 }}" + become_user: "{{ pg_user }}" + become: true + check_mode: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_with_options + schema: foobar_schema + owner: "{{ db_user1 }}" + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'foobar_with_options' + - result.owner == "{{ pg_user }}" + - result.queries == ["ALTER SEQUENCE \"foobar_schema\".\"foobar_with_options\" OWNER TO \"{{ db_user1 }}\""] + +# Real SQL check +- name: postgresql_sequence - check that the sequence "foobar_with_options" has still the old owner + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT c.relname,a.rolname,n.nspname + FROM pg_class as c + JOIN pg_authid as a on (c.relowner = a.oid) + JOIN pg_namespace as n on (c.relnamespace = n.oid) + WHERE c.relkind = 'S' and + c.relname = 'foobar_with_options' and + n.nspname = 'foobar_schema' and + a.rolname = '{{ pg_user }}'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: change owner of a sequence +- name: postgresql_sequence - change owner of an existing sequence from "{{ pg_user }}" to "{{ db_user1 }}" + become_user: "{{ pg_user }}" + become: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_with_options + schema: foobar_schema + owner: "{{ db_user1 }}" + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'foobar_with_options' + - result.owner == "{{ pg_user }}" + - result.queries == ["ALTER SEQUENCE \"foobar_schema\".\"foobar_with_options\" OWNER TO \"{{ db_user1 }}\""] + +# Real SQL check +- name: postgresql_sequence - check that the sequence "foobar_with_options" has a new owner + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT c.relname,a.rolname,n.nspname + FROM pg_class as c + JOIN pg_authid as a on (c.relowner = a.oid) + JOIN pg_namespace as n on (c.relnamespace = n.oid) + WHERE c.relkind = 'S' and + c.relname = 'foobar_with_options' and + n.nspname = 'foobar_schema' and + a.rolname = '{{ db_user1 }}'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: drop sequence with cascade + +# CREATE SEQUENCE seq1; +# CREATE TABLE t1 (f1 INT NOT NULL DEFAULT nextval('seq1')); +# DROP SEQUENCE seq1 CASCADE; +- name: postgresql_sequence - create sequence for drop cascade test + become_user: "{{ pg_user }}" + become: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: seq1 + +- name: postgresql_sequence - create table which use sequence for drop cascade test + become_user: "{{ pg_user }}" + become: true + postgresql_table: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: t1 + columns: + - f1 INT NOT NULL DEFAULT nextval('seq1') + +#################### +# Test: drop sequence with cascade in checkmode +- name: postgresql_sequence - drop with cascade a sequence called seq1 + become_user: "{{ pg_user }}" + become: true + check_mode: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: seq1 + state: absent + cascade: true + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'seq1' + - result.queries == ["DROP SEQUENCE \"public\".\"seq1\" CASCADE"] + +# Real SQL check +- name: postgresql_sequence - check that the sequence "seq1" still exists + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'seq1'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: drop sequence with cascade +- name: postgresql_sequence - drop with cascade a sequence called seq1 + become_user: "{{ pg_user }}" + become: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: seq1 + state: absent + cascade: true + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'seq1' + - result.queries == ["DROP SEQUENCE \"public\".\"seq1\" CASCADE"] + +# Real SQL check +- name: postgresql_sequence - check that the sequence "seq1" not exists + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'seq1'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 0 + +#################### +# Test: create sequence with owner in checkmode +- name: postgresql_sequence - create a new sequence with name "foobar2" with owner "{{ db_user2 }}" + become_user: "{{ pg_user }}" + become: true + check_mode: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar2 + owner: "{{ db_user2 }}" + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'foobar2' + - result.queries == ["CREATE SEQUENCE \"public\".\"foobar2\"", "ALTER SEQUENCE \"public\".\"foobar2\" OWNER TO \"ansible_db_user2\""] + +# Real SQL check +- name: postgresql_sequence - check that the new sequence "foobar2" does not exists + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar2'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 0 + +#################### +# Test: create sequence with owner +- name: postgresql_sequence - create a new sequence with name "foobar2" with owner "{{ db_user2 }}" + become_user: "{{ pg_user }}" + become: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar2 + owner: "{{ db_user2 }}" + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is changed + - result.sequence == 'foobar2' + - result.queries == ["CREATE SEQUENCE \"public\".\"foobar2\"", "ALTER SEQUENCE \"public\".\"foobar2\" OWNER TO \"ansible_db_user2\""] + +# Real SQL check +- name: postgresql_sequence - check that the new sequence "foobar2" exists + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar2'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +- name: postgresql_sequence - check that the sequence "foobar2" has owner "{{ db_user2 }}" + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT c.relname,a.rolname,n.nspname + FROM pg_class as c + JOIN pg_authid as a on (c.relowner = a.oid) + JOIN pg_namespace as n on (c.relnamespace = n.oid) + WHERE c.relkind = 'S' and + c.relname = 'foobar2' and + n.nspname = 'public' and + a.rolname = '{{ db_user2 }}'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: create sequence with trust_input +- name: postgresql_sequence - check that trust_input works as expected + become_user: "{{ pg_user }}" + become: true + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: 'just_a_name"; SELECT * FROM information_schema.tables; --' + trust_input: false + owner: "{{ db_user2 }}" + ignore_errors: true + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result is failed + - result.msg is search('is potentially dangerous') + +# Cleanup +- name: postgresql_sequence - destroy DB + become_user: "{{ pg_user }}" + become: true + postgresql_db: + state: absent + name: "{{ db_name }}" + login_user: "{{ pg_user }}" + +- name: remove test roles + become_user: "{{ pg_user }}" + become: true + postgresql_user: + state: absent + login_db: "{{ db_default }}" + login_user: "{{ pg_user }}" + name: "{{ item }}" + loop: + - "{{ db_user1 }}" + - "{{ db_user2 }}" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/tasks/main.yml new file mode 100644 index 000000000..3f16eb0d6 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/tasks/main.yml @@ -0,0 +1,11 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_initial module +- include_tasks: postgresql_set_initial.yml + when: postgres_version_resp.stdout is version('9.6', '>=') + +- include_tasks: options_coverage.yml + when: postgres_version_resp.stdout is version('9.6', '>=') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/tasks/options_coverage.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/tasks/options_coverage.yml new file mode 100644 index 000000000..c4598d2a9 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/tasks/options_coverage.yml @@ -0,0 +1,71 @@ +# Test code for the postgresql_set module +# Copyright: (c) 2021, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- vars: + task_parameters: &task_parameters + become_user: '{{ pg_user }}' + become: true + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: postgres + + block: + - name: Define a test setting map + set_fact: + setting_map: + allow_system_table_mods: on + archive_command: /bin/true + archive_timeout: 10min + autovacuum_work_mem: '-1' + backend_flush_after: 0 + autovacuum_vacuum_scale_factor: 0.5 + client_encoding: UTF-8 + bgwriter_delay: 400 + maintenance_work_mem: 32mb + effective_cache_size: 1024kB + shared_buffers: 1GB + wal_level: replica + log_statement: mod + track_functions: none + shared_preload_libraries: 'pg_stat_statements, pgaudit' + log_line_prefix: 'db=%d,user=%u,app=%a,client=%h ' + + # Check mode: + - name: Set settings in check mode + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: '{{ item.key }}' + value: '{{ item.value }}' + check_mode: true + with_dict: '{{ setting_map }}' + + # Actual mode: + - name: Set settings in actual mode + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: '{{ item.key }}' + value: '{{ item.value }}' + with_dict: '{{ setting_map }}' + + # https://github.com/ansible-collections/community.postgresql/issues/78 + - name: Test param with comma containing values + <<: *task_parameters + shell: "grep shared_preload_libraries {{ pg_auto_conf }}" + register: result + + - assert: + that: + - result.stdout == "shared_preload_libraries = 'pg_stat_statements, pgaudit'" + + # Test for single-value params with commas and spaces in value + - name: Test single-value param with commas and spaces in value + <<: *task_parameters + shell: "grep log_line_prefix {{ pg_auto_conf }}" + register: result + + - assert: + that: + - result.stdout == "log_line_prefix = 'db=%d,user=%u,app=%a,client=%h '" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/tasks/postgresql_set_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/tasks/postgresql_set_initial.yml new file mode 100644 index 000000000..ddff916aa --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_set/tasks/postgresql_set_initial.yml @@ -0,0 +1,442 @@ +# Test code for the postgresql_set module +# 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) +# +# Notice: assertions are different for Ubuntu 16.04 and FreeBSD because they don't work +# correctly for these tests. There are some stranges exactly in Shippable CI. +# However I checked it manually for all points (including Ubuntu 16.05 and FreeBSD) +# and it worked as expected. + +- vars: + task_parameters: &task_parameters + become_user: '{{ pg_user }}' + become: true + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: postgres + + block: + - name: postgresql_set - preparation to the next step + <<: *task_parameters + become_user: "{{ pg_user }}" + become: true + postgresql_set: + <<: *pg_parameters + name: work_mem + reset: true + + ##################### + # Testing check_mode: + - name: postgresql_set - get work_mem initial value + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SHOW work_mem + register: before + + - name: postgresql_set - set work_mem (restart is not required), check_mode + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: work_mem + value: 12MB + register: set_wm + check_mode: true + + - assert: + that: + - set_wm.name == 'work_mem' + - set_wm.changed == true + - set_wm.prev_val_pretty == before.query_result[0].work_mem + - set_wm.value_pretty == '12MB' + - set_wm.restart_required == false + + - name: postgresql_set - get work_mem value to check, must be the same as initial + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SHOW work_mem + register: after + + - assert: + that: + - before.query_result[0].work_mem == after.query_result[0].work_mem + ###### + # + + - name: postgresql_set - set work_mem (restart is not required) + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: work_mem + value: 12mb + register: set_wm + + - assert: + that: + - set_wm.name == 'work_mem' + - set_wm.changed == true + - set_wm.value_pretty == '12MB' + - set_wm.value_pretty != set_wm.prev_val_pretty + - set_wm.restart_required == false + - set_wm.value.value == 12582912 + - set_wm.value.unit == 'b' + when: + - ansible_distribution != "Ubuntu" + - ansible_distribution_major_version != '16' + - ansible_distribution != "FreeBSD" + + - assert: + that: + - set_wm.name == 'work_mem' + - set_wm.changed == true + - set_wm.restart_required == false + when: + - ansible_distribution == "Ubuntu" + - ansible_distribution_major_version == '16' + + - name: postgresql_set - reset work_mem (restart is not required) + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: work_mem + reset: true + register: reset_wm + + - assert: + that: + - reset_wm.name == 'work_mem' + - reset_wm.changed == true + - reset_wm.value_pretty != reset_wm.prev_val_pretty + - reset_wm.restart_required == false + - reset_wm.value.value != '12582912' + when: + - ansible_distribution != "Ubuntu" + - ansible_distribution_major_version != '16' + - ansible_distribution != "FreeBSD" + + - assert: + that: + - reset_wm.name == 'work_mem' + - reset_wm.changed == true + - reset_wm.restart_required == false + when: + - ansible_distribution == "Ubuntu" + - ansible_distribution_major_version == '16' + + - name: postgresql_set - reset work_mem again to check that nothing changed (restart is not required) + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: work_mem + reset: true + register: reset_wm2 + + - assert: + that: + - reset_wm2.name == 'work_mem' + - reset_wm2.changed == false + - reset_wm2.value_pretty == reset_wm2.prev_val_pretty + - reset_wm2.restart_required == false + when: + - ansible_distribution != "Ubuntu" + - ansible_distribution_major_version != '16' + + - assert: + that: + - reset_wm2.name == 'work_mem' + - reset_wm2.changed == false + - reset_wm2.restart_required == false + when: + - ansible_distribution == "Ubuntu" + - ansible_distribution_major_version == '16' + + - name: postgresql_set - preparation to the next step + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: work_mem + value: 14MB + + - name: postgresql_set - set work_mem to initial state (restart is not required) + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: work_mem + value: default + register: def_wm + + - assert: + that: + - def_wm.name == 'work_mem' + - def_wm.changed == true + - def_wm.value_pretty != def_wm.prev_val_pretty + - def_wm.restart_required == false + - def_wm.value.value != '14680064' + when: + - ansible_distribution != "Ubuntu" + - ansible_distribution_major_version != '16' + - ansible_distribution != 'FreeBSD' + + - assert: + that: + - def_wm.name == 'work_mem' + - def_wm.changed == true + - def_wm.restart_required == false + when: + - ansible_distribution == "Ubuntu" + - ansible_distribution_major_version == '16' + - ansible_distribution != 'FreeBSD' + + - name: postgresql_set - set shared_buffers (restart is required) + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: shared_buffers + value: 111MB + register: set_shb + + - assert: + that: + - set_shb.name == 'shared_buffers' + - set_shb.changed == true + - set_shb.restart_required == true + + # We don't check value.unit because it is none + - name: postgresql_set - set autovacuum (enabled by default, restart is not required) + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: autovacuum + value: off + register: set_aut + + - assert: + that: + - set_aut.name == 'autovacuum' + - set_aut.changed == true + - set_aut.restart_required == false + - set_aut.value.value == 'off' + + # Test check_mode, step 1. At the previous test we set autovacuum = 'off' + - name: postgresql - try to change autovacuum again in check_mode + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: autovacuum + value: on + register: set_aut + check_mode: true + + - assert: + that: + - set_aut.name == 'autovacuum' + - set_aut.changed == true + - set_aut.restart_required == false + - set_aut.value.value == 'off' + + # Test check_mode, step 2 + - name: postgresql - check that autovacuum wasn't actually changed after change in check_mode + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: autovacuum + value: off + register: set_aut + check_mode: true + + - assert: + that: + - set_aut.name == 'autovacuum' + - set_aut.changed == false + - set_aut.restart_required == false + - set_aut.value.value == 'off' + + # Additional check by SQL query: + - name: postgresql_set - get autovacuum value to check, must be off + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: SHOW autovacuum + register: result + + - assert: + that: + - result.query_result[0].autovacuum == 'off' + + # Test check_mode, step 3. It is different from + # the prev test - it runs without check_mode: true. + # Before the check_mode tests autovacuum was off + - name: postgresql - check that autovacuum wasn't actually changed after change in check_mode + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: autovacuum + value: off + register: set_aut + + - assert: + that: + - set_aut.name == 'autovacuum' + - set_aut.changed == false + - set_aut.restart_required == false + - set_aut.value.value == 'off' + + ################# + # Bugfix of 67377 + - name: archive command with mb + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + trust_input: true + name: archive_command + value: 'test ! -f /mnt/postgres/mb/%f && cp %p /mnt/postgres/mb/%f' + + # Check: + - name: check value + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: select reset_val from pg_settings where name = 'archive_command' + register: result + + - assert: + that: + - result.query_result.0.reset_val == "test ! -f /mnt/postgres/mb/%f && cp %p /mnt/postgres/mb/%f" + + ############################# + # Check trust_input parameter + - name: postgresql_set - check trust_input + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: shared_buffers + value: 111MB + trust_input: false + session_role: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' + register: result + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg is search('is potentially dangerous') + + ############################################################################### + # Bugfix of https://github.com/ansible-collections/community.general/issues/775 + - name: postgresql_set - turn on archive mode + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: archive_mode + value: 'on' + + - name: Restart PostgreSQL + become: true + service: + name: "{{ postgresql_service }}" + state: restarted + + - name: postgresql_set - set empty string as value + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: archive_command + value: '' + register: result + + - assert: + that: + - result is changed + + - name: postgresql_set - set empty string as value again + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: archive_command + value: '' + register: result + + - assert: + that: + - result is not changed + + - name: postgresql_set - set empty string as value again in check mode + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: archive_command + value: '' + register: result + check_mode: true + + - assert: + that: + - result is not changed + + - name: Pass non-existent parameter + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: Timezone + value: utc + register: result + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg is search('No such parameter') + + ####################################################################### + # https://github.com/ansible-collections/community.postgresql/issues/48 + - name: Pass a parameter containing b in check_mode + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: archive_command + value: '/usr/bin/touch %f' + register: result + check_mode: true + + - assert: + that: + - result is changed + + - name: Pass a parameter containing b + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: archive_command + value: '/usr/bin/touch %f' + register: result + + - assert: + that: + - result is changed + + - name: Pass another parameter containing B in check_mode + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: track_activity_query_size + value: '4096B' + register: result + check_mode: true + + - assert: + that: + - result is changed + + - name: Pass another parameter containing b in check_mode + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: track_activity_query_size + value: '2048b' + register: result + check_mode: true + + - assert: + that: + - result is changed diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_slot/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_slot/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_slot/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_slot/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_slot/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_slot/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_slot/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_slot/tasks/main.yml new file mode 100644 index 000000000..d44aab9de --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_slot/tasks/main.yml @@ -0,0 +1,9 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_slot module +# Physical replication slots are available since PostgreSQL 9.4 +- import_tasks: postgresql_slot_initial.yml + when: postgres_version_resp.stdout is version('9.4', '>=') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_slot/tasks/postgresql_slot_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_slot/tasks/postgresql_slot_initial.yml new file mode 100644 index 000000000..23a1cfb0e --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_slot/tasks/postgresql_slot_initial.yml @@ -0,0 +1,735 @@ +--- +# 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) + +- name: postgresql_slot - set max_replication_slots + become_user: "{{ pg_user }}" + become: true + postgresql_set: + login_user: "{{ pg_user }}" + db: postgres + name: max_replication_slots + value: '10' + +- name: postgresql_slot - set wal_level to logical + become_user: "{{ pg_user }}" + become: true + postgresql_set: + login_user: "{{ pg_user }}" + db: postgres + name: wal_level + value: logical + +# To avoid CI timeouts +- name: Kill all postgres processes + shell: 'pkill -u {{ pg_user }}' + become: true + when: ansible_facts.distribution == 'CentOS' and ansible_facts.distribution_major_version == '8' + ignore_errors: true + +- name: postgresql_slot - stop PostgreSQL + become: true + service: + name: "{{ postgresql_service }}" + state: stopped + when: (ansible_facts.distribution_major_version != '8' and ansible_facts.distribution == 'CentOS') or ansible_facts.distribution != 'CentOS' + +- name: postgresql_slot - pause between stop and start PostgreSQL + ansible.builtin.pause: + seconds: 5 + +- name: postgresql_slot - start PostgreSQL + become: true + service: + name: "{{ postgresql_service }}" + state: started + +# +# state=present +# + +# check_mode +- name: postgresql_slot - create slot in check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot0 + check_mode: true + register: result + +- assert: + that: + - result is changed + - result.queries == [] + +# Check, rowcount must be 0 +- name: postgresql_slot - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot0'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +# true mode +- name: postgresql_slot - create physical slot + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot0 + register: result + +- assert: + that: + - result is changed + - result.queries == ["SELECT pg_create_physical_replication_slot('slot0', false)"] + when: postgres_version_resp.stdout is version('9.6', '>=') + +- assert: + that: + - result is changed + - result.queries == ["SELECT pg_create_physical_replication_slot('slot0')"] + when: postgres_version_resp.stdout is version('9.6', '<') + +# Check, rowcount must be 1 +- name: postgresql_slot - check that the slot exists after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot0' and slot_type = 'physical'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +# check mode +- name: postgresql_slot - try create physical slot again in check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot0 + check_mode: true + register: result + +- assert: + that: + - result is not changed + - result.queries == [] + +# Check, rowcount must be 1 +- name: postgresql_slot - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot0' and slot_type = 'physical'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +# true mode +- name: postgresql_slot - try create physical slot again + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot0 + slot_type: physical + register: result + +- assert: + that: + - result is not changed + - result.queries == [] + +# Check, rowcount must be 1 +- name: postgresql_slot - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot0' and slot_type = 'physical'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +# +# immediately_reserve +# + +- name: postgresql_slot - create physical slot with immediately_reserve + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot1 + immediately_reserve: true + register: result + when: postgres_version_resp.stdout is version('9.6', '>=') + +- assert: + that: + - result is changed + - result.queries == ["SELECT pg_create_physical_replication_slot('slot1', true)"] + when: postgres_version_resp.stdout is version('9.6', '>=') + +# Check, rowcount must be 1 +- name: postgresql_slot - check that the slot exists after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot1' and slot_type = 'physical' and restart_lsn is not NULL" + ignore_errors: true + 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', '>=') + +# +# slot_type: logical +# +# available from postgresql 10 +# +# on RedHat family tests failed: +# ERROR: could not access file "test_decoding": No such file or directory +# "Your distrib did not compile the test decoder." +# So the tests are restricted by Ubuntu because of the module functionality +# depends on PostgreSQL server version only. + +# check_mode +- name: postgresql_slot - create slot in check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot2 + slot_type: logical + check_mode: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result is changed + - result.queries == [] + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# Check, rowcount must be 0 +- name: postgresql_slot - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot2'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result.rowcount == 0 + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# true mode +- name: postgresql_slot - create logical slot + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot2 + slot_type: logical + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result is changed + - result.queries == ["SELECT pg_create_logical_replication_slot('slot2', 'test_decoding')"] + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# Check, rowcount must be 1 +- name: postgresql_slot - check that the slot exists after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot2' and slot_type = 'logical'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# check mode +- name: postgresql_slot - try create logical slot again in check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot2 + slot_type: logical + check_mode: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result is not changed + - result.queries == [] + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# Check, rowcount must be 1 +- name: postgresql_slot - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot2' and slot_type = 'logical'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# true mode +- name: postgresql_slot - try create logical slot again + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot2 + slot_type: logical + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result is not changed + - result.queries == [] + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# Check, rowcount must be 1 +- name: postgresql_slot - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot2' and slot_type = 'logical'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# +# output_plugin: test_decoding +# + +- name: postgresql_slot - create logical slot with output_plugin + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot3 + slot_type: logical + output_plugin: test_decoding + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result is changed + - result.queries == ["SELECT pg_create_logical_replication_slot('slot3', 'test_decoding')"] + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# Check, rowcount must be 1 +- name: postgresql_slot - check that the slot exists after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot3' and slot_type = 'logical' and plugin = 'test_decoding'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# +# state: absent for logical slots +# + +# check_mode +- name: postgresql_slot - drop logical slot in check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot2 + state: absent + check_mode: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result is changed + - result.queries == [] + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# Check, rowcount must be 1 +- name: postgresql_slot - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot2'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# true mode +- name: postgresql_slot - drop logical slot + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot2 + state: absent + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result is changed + - result.queries == ["SELECT pg_drop_replication_slot('slot2')"] + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# Check, rowcount must be 0 +- name: postgresql_slot - check that the slot does not exist after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot2' and slot_type = 'logical'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result.rowcount == 0 + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# check mode +- name: postgresql_slot - try drop logical slot again in check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot2 + state: absent + check_mode: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result is not changed + - result.queries == [] + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# Check, rowcount must be 0 +- name: postgresql_slot - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot2' and slot_type = 'logical'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') + +- assert: + that: + - result.rowcount == 0 + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# true mode +- name: postgresql_slot - try drop logical slot again + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot2 + state: absent + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result is not changed + - result.queries == [] + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# Check, rowcount must be 0 +- name: postgresql_slot - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot2' and slot_type = 'logical'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +- assert: + that: + - result.rowcount == 0 + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + +# +# state=absent for physical slots +# + +# check_mode +- name: postgresql_slot - drop physical slot in check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot1 + state: absent + check_mode: true + register: result + +- assert: + that: + - result is changed + - result.queries == [] + when: postgres_version_resp.stdout is version('9.6', '>=') + +# Check, rowcount must be 1 +- name: postgresql_slot - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot1'" + ignore_errors: true + 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', '>=') + +# true mode +- name: postgresql_slot - drop physical slot + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot1 + state: absent + register: result + when: postgres_version_resp.stdout is version('9.6', '>=') + +- assert: + that: + - result is changed + - result.queries == ["SELECT pg_drop_replication_slot('slot1')"] + when: postgres_version_resp.stdout is version('9.6', '>=') + +# Check, rowcount must be 0 +- name: postgresql_slot - check that the slot does not exist after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot1' and slot_type = 'physical'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('9.6', '>=') + +- assert: + that: + - result.rowcount == 0 + when: postgres_version_resp.stdout is version('9.6', '>=') + +# check mode +- name: postgresql_slot - try drop physical slot again in check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot1 + state: absent + check_mode: true + register: result + when: postgres_version_resp.stdout is version('9.6', '>=') + +- assert: + that: + - result is not changed + - result.queries == [] + when: postgres_version_resp.stdout is version('9.6', '>=') + +# Check, rowcount must be 0 +- name: postgresql_slot - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot1' and slot_type = 'physical'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('9.6', '>=') + +- assert: + that: + - result.rowcount == 0 + when: postgres_version_resp.stdout is version('9.6', '>=') + +# true mode +- name: postgresql_slot - try drop physical slot again + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: slot1 + state: absent + register: result + when: postgres_version_resp.stdout is version('9.6', '>=') + +- assert: + that: + - result is not changed + - result.queries == [] + when: postgres_version_resp.stdout is version('9.6', '>=') + +# Check, rowcount must be 0 +- name: postgresql_slot - check that nothing changed after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_replication_slots WHERE slot_name = 'slot1' and slot_type = 'physical'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('9.6', '>=') + +- assert: + that: + - result.rowcount == 0 + when: postgres_version_resp.stdout is version('9.6', '>=') + +# Check trust input +- name: postgresql_slot - try using a bad name + postgresql_slot: + session_role: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' + db: postgres + name: slot1 + trust_input: false + register: result + ignore_errors: true + when: postgres_version_resp.stdout is version('9.6', '>=') + +- name: postgresql_slot - check that using a dangerous name fails + assert: + that: + - result is failed + - result.msg is search('is potentially dangerous') + when: postgres_version_resp.stdout is version('9.6', '>=') + +# +# clean up +# +- name: postgresql_slot - clean up + become_user: "{{ pg_user }}" + become: true + postgresql_slot: + login_user: "{{ pg_user }}" + db: postgres + name: "{{ item }}" + state: absent + ignore_errors: true + when: postgres_version_resp.stdout is version('10', '>=') and ansible_distribution == 'Ubuntu' + with_items: + - slot0 + - slot3 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/aliases new file mode 100644 index 000000000..786e05315 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/aliases @@ -0,0 +1,4 @@ +destructive +shippable/posix/group1 +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/defaults/main.yml new file mode 100644 index 000000000..8709694ba --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/defaults/main.yml @@ -0,0 +1,13 @@ +pg_user: postgres +db_default: postgres + +test_table1: acme1 +test_pub: first_publication +test_pub2: second_publication +replication_role: logical_replication +replication_pass: alsdjfKJKDf1# +test_db: acme_db +test_subscription: test +test_role1: alice +test_role2: bob +conn_timeout: 100 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/meta/main.yml new file mode 100644 index 000000000..d72e4d23c --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_replication diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/tasks/main.yml new file mode 100644 index 000000000..e440e8c80 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/tasks/main.yml @@ -0,0 +1,12 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial tests of postgresql_subscription module: + +- import_tasks: setup_publication.yml + when: ansible_distribution == 'Ubuntu' and ansible_distribution_major_version >= '18' + +- import_tasks: postgresql_subscription_initial.yml + when: ansible_distribution == 'Ubuntu' and ansible_distribution_major_version >= '18' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/tasks/postgresql_subscription_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/tasks/postgresql_subscription_initial.yml new file mode 100644 index 000000000..b464c3dbe --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/tasks/postgresql_subscription_initial.yml @@ -0,0 +1,672 @@ +# 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) + +- vars: + dangerous_name: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' + task_parameters: &task_parameters + become_user: '{{ pg_user }}' + become: true + register: result + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: '{{ test_db }}' + + block: + + - name: Create roles to test owner parameter + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ item }}' + role_attr_flags: SUPERUSER,LOGIN + loop: + - '{{ test_role1 }}' + - '{{ test_role2 }}' + + #################### + # Test mode: present + #################### + - name: Create subscription + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: present + publications: '{{ test_pub }}' + connparams: + host: 127.0.0.1 + port: '{{ primary_port }}' + user: '{{ replication_role }}' + password: '{{ replication_pass }}' + dbname: '{{ test_db }}' + trust_input: false + + - assert: + that: + - result is changed + - result.name == '{{ test_subscription }}' + - result.queries == ["CREATE SUBSCRIPTION test CONNECTION 'host=127.0.0.1 port={{ primary_port }} user={{ replication_role }} password={{ replication_pass }} dbname={{ test_db }}' PUBLICATION {{ test_pub }}"] + - result.exists == true + - result.initial_state == {} + - result.final_state.owner == '{{ pg_user }}' + - result.final_state.enabled == true + - result.final_state.publications == ["{{ test_pub }}"] + - result.final_state.synccommit == true + - result.final_state.slotname == '{{ test_subscription }}' + - result.final_state.conninfo.dbname == '{{ test_db }}' + - result.final_state.conninfo.host == '127.0.0.1' + - result.final_state.conninfo.port == {{ primary_port }} + - result.final_state.conninfo.user == '{{ replication_role }}' + - result.final_state.conninfo.password == '{{ replication_pass }}' + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: "SELECT subname FROM pg_subscription WHERE subname = '{{ test_subscription }}'" + + - assert: + that: + - result.rowcount == 1 + + ################### + # Test mode: absent + ################### + + - name: Drop subscription in check mode + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: absent + trust_input: false + check_mode: true + + - assert: + that: + - result is changed + - result.queries == ["DROP SUBSCRIPTION {{ test_subscription }}"] + - result.final_state == result.initial_state + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: "SELECT subname FROM pg_subscription WHERE subname = '{{ test_subscription }}'" + + - assert: + that: + - result.rowcount == 1 + + - name: Drop subscription + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: absent + + - assert: + that: + - result is changed + - result.queries == ["DROP SUBSCRIPTION {{ test_subscription }}"] + - result.final_state != result.initial_state + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: "SELECT subname FROM pg_subscription WHERE subname = '{{ test_subscription }}'" + + - assert: + that: + - result.rowcount == 0 + + ################## + # Test owner param + ################## + + - name: Create with owner + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: present + publications: '{{ test_pub }}' + owner: '{{ test_role1 }}' + connparams: + host: 127.0.0.1 + port: '{{ primary_port }}' + user: '{{ replication_role }}' + password: '{{ replication_pass }}' + dbname: '{{ test_db }}' + trust_input: false + + - assert: + that: + - result.final_state.owner == '{{ test_role1 }}' + - result.queries[1] == 'ALTER SUBSCRIPTION {{ test_subscription }} OWNER TO "{{ test_role1 }}"' + + - name: Try to set this owner again + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: present + publications: '{{ test_pub }}' + owner: '{{ test_role1 }}' + trust_input: false + + - assert: + that: + - result is not changed + - result.initial_state == result.final_state + - result.final_state.owner == '{{ test_role1 }}' + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: > + SELECT subname FROM pg_subscription AS s + JOIN pg_catalog.pg_roles AS r ON s.subowner = r.oid + WHERE subname = '{{ test_subscription }}' and r.rolname = '{{ test_role1 }}' + + - assert: + that: + - result.rowcount == 1 + + - name: Set another owner in check mode + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: present + publications: '{{ test_pub }}' + owner: '{{ test_role2 }}' + trust_input: false + check_mode: true + + - assert: + that: + - result is changed + - result.initial_state == result.final_state + - result.final_state.owner == '{{ test_role1 }}' + - result.queries == ['ALTER SUBSCRIPTION {{ test_subscription }} OWNER TO "{{ test_role2 }}"'] + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: > + SELECT subname FROM pg_subscription AS s + JOIN pg_catalog.pg_roles AS r ON s.subowner = r.oid + WHERE subname = '{{ test_subscription }}' and r.rolname = '{{ test_role1 }}' + + - assert: + that: + - result.rowcount == 1 + + - name: Set another owner + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: present + publications: '{{ test_pub }}' + owner: '{{ test_role2 }}' + trust_input: false + + - assert: + that: + - result is changed + - result.initial_state != result.final_state + - result.final_state.owner == '{{ test_role2 }}' + - result.queries == ['ALTER SUBSCRIPTION {{ test_subscription }} OWNER TO "{{ test_role2 }}"'] + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: > + SELECT subname FROM pg_subscription AS s + JOIN pg_catalog.pg_roles AS r ON s.subowner = r.oid + WHERE subname = '{{ test_subscription }}' and r.rolname = '{{ test_role2 }}' + + - assert: + that: + - result.rowcount == 1 + + ########################## + # Test trust_input param # + ########################## + + - name: Test trust_input parameter + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: present + publications: '{{ test_pub }}' + session_role: '{{ dangerous_name }}' + owner: '{{ test_role1 }}' + trust_input: false + connparams: + host: 127.0.0.1 + port: '{{ primary_port }}' + user: '{{ replication_role }}' + password: '{{ replication_pass }}' + dbname: '{{ test_db }}' + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg is search('is potentially dangerous') + + ############## + # Test cascade + ############## + + - name: Drop subscription cascade in check mode + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: absent + cascade: true + trust_input: false + check_mode: true + + - assert: + that: + - result is changed + - result.queries == ["DROP SUBSCRIPTION {{ test_subscription }} CASCADE"] + - result.final_state == result.initial_state + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: "SELECT subname FROM pg_subscription WHERE subname = '{{ test_subscription }}'" + + - assert: + that: + - result.rowcount == 1 + + - name: Drop subscription cascade + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: absent + cascade: true + + - assert: + that: + - result is changed + - result.queries == ["DROP SUBSCRIPTION {{ test_subscription }} CASCADE"] + - result.final_state != result.initial_state + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: "SELECT subname FROM pg_subscription WHERE subname = '{{ test_subscription }}'" + + - assert: + that: + - result.rowcount == 0 + + ########################### + # Test subsparams parameter + ########################### + + - name: Create subscription with subsparams + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: present + publications: '{{ test_pub }}' + connparams: + host: 127.0.0.1 + port: '{{ primary_port }}' + user: '{{ replication_role }}' + password: '{{ replication_pass }}' + dbname: '{{ test_db }}' + subsparams: + enabled: false + synchronous_commit: false + trust_input: false + + - assert: + that: + - result is changed + - result.name == '{{ test_subscription }}' + - result.queries == ["CREATE SUBSCRIPTION test CONNECTION 'host=127.0.0.1 port={{ primary_port }} user={{ replication_role }} password={{ replication_pass }} dbname={{ test_db }}' PUBLICATION {{ test_pub }} WITH (enabled = false, synchronous_commit = false)"] + - result.exists == true + - result.final_state.enabled == false + - result.final_state.synccommit == false + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: > + SELECT subname FROM pg_subscription WHERE subname = '{{ test_subscription }}' + AND subenabled = 'f' AND subsynccommit = 'false' + + - assert: + that: + - result.rowcount == 1 + + - name: Enable changed params + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + publications: '{{ test_pub }}' + subsparams: + enabled: true + synchronous_commit: true + trust_input: false + + - assert: + that: + - result is changed + - result.name == '{{ test_subscription }}' + - result.queries == ["ALTER SUBSCRIPTION {{ test_subscription }} ENABLE", "ALTER SUBSCRIPTION {{ test_subscription }} SET (synchronous_commit = true)"] + - result.exists == true + - result.final_state.enabled == true + - result.final_state.synccommit == true + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: > + SELECT subname FROM pg_subscription WHERE subname = '{{ test_subscription }}' + AND subenabled = 't' AND subsynccommit = 'true' + + - assert: + that: + - result.rowcount == 1 + + - name: Enable the same params again + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + publications: '{{ test_pub }}' + subsparams: + enabled: true + synchronous_commit: true + trust_input: false + + - assert: + that: + - result is not changed + - result.name == '{{ test_subscription }}' + - result.queries == [] + - result.exists == true + - result.final_state == result.initial_state + - result.final_state.enabled == true + - result.final_state.synccommit == true + + ########################## + # Test change publications + ########################## + + - name: Change publications in check mode + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: present + publications: + - '{{ test_pub }}' + - '{{ test_pub2 }}' + trust_input: false + check_mode: true + + - assert: + that: + - result is changed + - result.name == '{{ test_subscription }}' + - result.final_state.publications == result.initial_state.publications + - result.final_state.publications == ['{{ test_pub }}'] + - result.queries == ['ALTER SUBSCRIPTION {{ test_subscription }} SET PUBLICATION {{ test_pub }}, {{ test_pub2 }}'] + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: > + SELECT subname FROM pg_subscription WHERE subname = '{{ test_subscription }}' + AND subpublications = '{"{{ test_pub }}"}' + + - assert: + that: + - result.rowcount == 1 + + - name: Change publications + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: present + publications: + - '{{ test_pub }}' + - '{{ test_pub2 }}' + trust_input: false + + - assert: + that: + - result is changed + - result.name == '{{ test_subscription }}' + - result.final_state.publications != result.initial_state.publications + - result.final_state.publications == ['{{ test_pub }}', '{{ test_pub2 }}'] + - result.queries == ['ALTER SUBSCRIPTION {{ test_subscription }} SET PUBLICATION {{ test_pub }}, {{ test_pub2 }}'] + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: > + SELECT subname FROM pg_subscription WHERE subname = '{{ test_subscription }}' + AND subpublications = '{"{{ test_pub }}", "{{ test_pub2 }}"}' + + - assert: + that: + - result.rowcount == 1 + + - name: Change publications with the same values again + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: present + publications: + - '{{ test_pub }}' + - '{{ test_pub2 }}' + trust_input: false + + - assert: + that: + - result is not changed + - result.name == '{{ test_subscription }}' + - result.final_state.publications == result.initial_state.publications + - result.final_state.publications == ['{{ test_pub }}', '{{ test_pub2 }}'] + - result.queries == [] + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: > + SELECT subname FROM pg_subscription WHERE subname = '{{ test_subscription }}' + AND subpublications = '{"{{ test_pub }}", "{{ test_pub2 }}"}' + + - assert: + that: + - result.rowcount == 1 + + ###################### + # Test update conninfo + ###################### + + - name: Change conninfo in check mode + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: present + connparams: + host: 127.0.0.1 + port: '{{ primary_port }}' + user: '{{ replication_role }}' + password: '{{ replication_pass }}' + dbname: '{{ test_db }}' + connect_timeout: '{{ conn_timeout }}' + trust_input: false + check_mode: true + + - assert: + that: + - result is changed + - result.name == '{{ test_subscription }}' + - result.queries == ["ALTER SUBSCRIPTION {{ test_subscription }} CONNECTION 'host=127.0.0.1 port={{ primary_port }} user={{ replication_role }} password={{ replication_pass }} dbname={{ test_db }} connect_timeout={{ conn_timeout }}'"] + - result.initial_state.conninfo == result.final_state.conninfo + + - name: Change conninfo + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: present + connparams: + host: 127.0.0.1 + port: '{{ primary_port }}' + user: '{{ replication_role }}' + password: '{{ replication_pass }}' + dbname: '{{ test_db }}' + connect_timeout: '{{ conn_timeout }}' + trust_input: false + + - assert: + that: + - result is changed + - result.name == '{{ test_subscription }}' + - result.queries == ["ALTER SUBSCRIPTION {{ test_subscription }} CONNECTION 'host=127.0.0.1 port={{ primary_port }} user={{ replication_role }} password={{ replication_pass }} dbname={{ test_db }} connect_timeout={{ conn_timeout }}'"] + - result.initial_state.conninfo != result.final_state.conninfo + + - name: Check + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ replica_port }}' + query: "SELECT * FROM pg_subscription WHERE subname = '{{ test_subscription }}'" + + - assert: + that: + - result.query_result[0].subconninfo == "host=127.0.0.1 port={{ primary_port }} user={{ replication_role }} password={{ replication_pass }} dbname={{ test_db }} connect_timeout={{ conn_timeout }}" + + - name: Try to change conninfo again with the same values + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: present + connparams: + host: 127.0.0.1 + port: '{{ primary_port }}' + user: '{{ replication_role }}' + password: '{{ replication_pass }}' + dbname: '{{ test_db }}' + connect_timeout: '{{ conn_timeout }}' + trust_input: false + + - assert: + that: + - result is not changed + - result.name == '{{ test_subscription }}' + - result.queries == [] + - result.initial_state.conninfo == result.final_state.conninfo + - result.final_state.conninfo.connect_timeout == {{ conn_timeout }} + + #################### + # Test state refresh + #################### + + - name: Refresh in check mode + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: refresh + check_mode: true + + - assert: + that: + - result is changed + - result.name == '{{ test_subscription }}' + - result.queries == ["ALTER SUBSCRIPTION {{ test_subscription }} REFRESH PUBLICATION"] + + - name: Refresh + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: refresh + + - assert: + that: + - result is changed + - result.name == '{{ test_subscription }}' + - result.queries == ["ALTER SUBSCRIPTION {{ test_subscription }} REFRESH PUBLICATION"] + + ########## + # Clean up + ########## + - name: Drop subscription + <<: *task_parameters + postgresql_subscription: + <<: *pg_parameters + login_port: '{{ replica_port }}' + name: '{{ test_subscription }}' + state: absent diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/tasks/setup_publication.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/tasks/setup_publication.yml new file mode 100644 index 000000000..712b3701d --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_subscription/tasks/setup_publication.yml @@ -0,0 +1,85 @@ +# 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) +# Preparation for further tests of postgresql_subscription module. + +- vars: + task_parameters: &task_parameters + become_user: '{{ pg_user }}' + become: true + register: result + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: '{{ test_db }}' + + block: + - name: postgresql_publication - create test db + <<: *task_parameters + postgresql_db: + login_user: '{{ pg_user }}' + login_port: '{{ item }}' + maintenance_db: '{{ db_default }}' + name: '{{ test_db }}' + loop: + - '{{ primary_port }}' + - '{{ replica_port }}' + + - name: postgresql_publication - create test role + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + login_port: '{{ item }}' + name: '{{ replication_role }}' + password: '{{ replication_pass }}' + role_attr_flags: LOGIN,REPLICATION + loop: + - '{{ primary_port }}' + - '{{ replica_port }}' + + - name: postgresql_publication - create test table + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + login_port: '{{ item }}' + name: '{{ test_table1 }}' + columns: + - id int + loop: + - '{{ primary_port }}' + - '{{ replica_port }}' + + - name: postgresql_publication - create publication + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + login_port: '{{ primary_port }}' + name: '{{ test_pub }}' + + - assert: + that: + - result is changed + - result.exists == true + - result.queries == ["CREATE PUBLICATION \"{{ test_pub }}\" FOR ALL TABLES"] + - result.owner == '{{ pg_user }}' + - result.alltables == true + - result.tables == [] + - result.parameters.publish != {} + + - name: postgresql_publication - create one more publication + <<: *task_parameters + postgresql_publication: + <<: *pg_parameters + login_port: '{{ primary_port }}' + name: '{{ test_pub2 }}' + + - name: postgresql_publication - check the publication was created + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + login_port: '{{ primary_port }}' + query: > + SELECT * FROM pg_publication WHERE pubname = '{{ test_pub }}' + AND pubowner = '10' AND puballtables = 't' + + - assert: + that: + - result.rowcount == 1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_table/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_table/aliases new file mode 100644 index 000000000..0d91b7de0 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_table/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group5 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_table/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_table/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_table/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_table/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_table/tasks/main.yml new file mode 100644 index 000000000..3534c73b0 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_table/tasks/main.yml @@ -0,0 +1,7 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_table module +- import_tasks: postgresql_table_initial.yml diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_table/tasks/postgresql_table_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_table/tasks/postgresql_table_initial.yml new file mode 100644 index 000000000..db0f2732e --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_table/tasks/postgresql_table_initial.yml @@ -0,0 +1,899 @@ +# Test code for the postgresql_set module + +# 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 a role for tests: +- name: postgresql_table - create a role for tests + become_user: "{{ pg_user }}" + become: true + postgresql_user: + db: postgres + login_user: "{{ pg_user }}" + name: alice + +- name: postgresql_table - create test schema + become_user: "{{ pg_user }}" + become: true + postgresql_schema: + database: postgres + login_user: "{{ pg_user }}" + name: acme + +# +# Check table creation +# + +# Create a simple table in check_mode: +- name: postgresql_table - create table in check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_table: + login_db: postgres + login_port: 5432 + login_user: "{{ pg_user }}" + name: test1 + owner: alice + columns: id int + register: result + ignore_errors: true + check_mode: true + +- assert: + that: + - result is changed + - result.table == 'test1' + - result.queries == ['CREATE TABLE "test1" (id int)', 'ALTER TABLE "test1" OWNER TO "alice"'] + - result.state == 'absent' + +# Check that the table doesn't exist after the previous step, rowcount must be 0 +- name: postgresql_table - check that table doesn't exist after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test1'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +# Create a simple table: +- name: postgresql_table - create table + become_user: "{{ pg_user }}" + become: true + postgresql_table: + login_db: postgres + login_port: 5432 + login_user: "{{ pg_user }}" + name: test1 + owner: alice + columns: id int + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.table == 'test1' + - result.queries == ['CREATE TABLE "test1" (id int)', 'ALTER TABLE "test1" OWNER TO "alice"'] + - result.state == 'present' + - result.storage_params == [] + - result.tablespace == "" + - result.owner == "alice" + +# Check that the table exists after the previous step, rowcount must be 1 +- name: postgresql_table - check that table exists after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test1'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +# Check that the tableowner is alice +- name: postgresql_table - check that table owner is alice + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_tables WHERE tablename = 'test1' AND tableowner = 'alice'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +# +# Check create table like another table +# + +# Create a table LIKE another table without any additional parameters in check_mode: +- name: postgresql_table - create table like in check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test2 + like: test1 + register: result + ignore_errors: true + check_mode: true + +- assert: + that: + - result is changed + - result.table == 'test2' + - result.queries == ['CREATE TABLE "test2" (LIKE "test1")'] + - result.state == 'absent' + +# Check that the table doesn't exist after the previous step, rowcount must be 0 +- name: postgresql_table - check that table doesn't exist after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test2'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +# Create a table LIKE another table without any additional parameters: +- name: postgresql_table - create table like + become_user: "{{ pg_user }}" + become: true + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test2 + like: test1 + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.table == 'test2' + - result.queries == ['CREATE TABLE "test2" (LIKE "test1")'] + - result.state == 'present' + - result.storage_params == [] + - result.tablespace == "" + - result.owner == "{{ pg_user }}" + +# Check that the table exists after the previous step, rowcount must be 1 +- name: postgresql_table - check that table exists after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test2'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +# +# Check drop table +# + +# Drop a table in check_mode: +- name: postgresql_table - drop table in check_mode + become_user: "{{ pg_user }}" + become: true + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test2 + state: absent + register: result + ignore_errors: true + check_mode: true + +- assert: + that: + - result is changed + - result.queries == ['DROP TABLE "test2"'] + - result.state == 'present' + - result.storage_params == [] + - result.tablespace == "" + - result.owner == "{{ pg_user }}" + +# Check that the table exists after the previous step, rowcount must be 1 +- name: postgresql_table - check that table exists after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test2'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +# Drop a table: +- name: postgresql_table - drop table + become_user: "{{ pg_user }}" + become: true + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test2 + state: absent + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.queries == ['DROP TABLE "test2"'] + - result.state == 'absent' + +# Check that the table doesn't exist after the previous step, rowcount must be 0 +- name: postgresql_table - check that table doesn't exist after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test2'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +# Create a table like another table including: +- name: postgresql_table - create table like with including indexes + become_user: "{{ pg_user }}" + become: true + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test2 + like: test1 + including: indexes + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.queries == ['CREATE TABLE "test2" (LIKE "test1" INCLUDING indexes)'] + - result.state == 'present' + - result.storage_params == [] + - result.tablespace == "" + - result.owner == "{{ pg_user }}" + +# Check to create table if it exists: +- name: postgresql_table - try to create existing table again + become_user: "{{ pg_user }}" + become: true + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test2 + like: test1 + including: indexes + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + +# Drop the table to prepare for the next step: +- name: postgresql_table - drop table + become_user: "{{ pg_user }}" + become: true + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test2 + state: absent + register: result + ignore_errors: true + +# Try to drop non existing table: +- name: postgresql_table - try drop dropped table again + become_user: "{{ pg_user }}" + become: true + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test2 + state: absent + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + +# +# Change ownership +# + +# Create user to prepare for the next step: +- name: postgresql_table - create the new user test_user + become: true + become_user: "{{ pg_user }}" + postgresql_user: + login_user: "{{ pg_user }}" + db: postgres + name: test_user + state: present + ignore_errors: true + +# Try to change owner to test_user in check_mode +- name: postgresql_table - change table ownership to test_user in check_mode + become: true + become_user: "{{ pg_user }}" + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test1 + owner: test_user + register: result + ignore_errors: true + check_mode: true + +- assert: + that: + - result.owner == 'alice' + - result.queries == ['ALTER TABLE "test1" OWNER TO "test_user"'] + - result.state == 'present' + - result is changed + +# Check that the tableowner was not changed to test_user +- name: postgresql_table - check that table owner was not changed + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_tables WHERE tablename = 'test1' AND tableowner = 'test_user'" + ignore_errors: true + register: result + +- assert: + that: + - result is not changed + +# Try to change owner to test_user +- name: postgresql_table - change table ownership to test_user + become: true + become_user: "{{ pg_user }}" + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test1 + owner: test_user + register: result + ignore_errors: true + +- assert: + that: + - result.owner == 'test_user' + - result.queries == ['ALTER TABLE "test1" OWNER TO "test_user"'] + - result.state == 'present' + - result is changed + +# Check that the tableowner was changed to test_user +- name: postgresql_table - check that table owner was changed + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_tables WHERE tablename = 'test1' AND tableowner = 'test_user'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +# +# Additional storage parameters +# + +# Create a table with additional storage parameters: +- name: postgresql_table - create table with storage_params + become: true + become_user: "{{ pg_user }}" + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test3 + columns: + - id int + - name text + storage_params: + - fillfactor=10 + - autovacuum_analyze_threshold=1 + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.state == 'present' + - result.queries == ['CREATE TABLE "test3" (id int,name text) WITH (fillfactor=10,autovacuum_analyze_threshold=1)'] + - result.storage_params == [ "fillfactor=10", "autovacuum_analyze_threshold=1" ] + +# Check storage parameters +- name: postgresql_table - check storage parameters + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT reloptions FROM pg_class WHERE relname = 'test3'" + ignore_errors: true + register: result + +- assert: + that: + - result.query_result[0].reloptions == ["fillfactor=10", "autovacuum_analyze_threshold=1"] +# +# Check truncate table +# + +# Insert a row to test table: +- name: postgresql_table - insert a row + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "INSERT INTO test3 (id, name) VALUES (1, 'first')" + +# Truncate a table in check_mode: +- name: postgresql_table - truncate table + become: true + become_user: "{{ pg_user }}" + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test3 + truncate: true + register: result + ignore_errors: true + check_mode: true + +- assert: + that: + - result is changed + - result.queries == ['TRUNCATE TABLE "test3"'] + - result.state == "present" + +# Check the row exists: +- name: postgresql_table - check that row exists after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT * FROM test3 WHERE id = '1'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +# Truncate a table. It always returns changed == true +# because it always creates a new table with the same schema and drop the old table: +- name: postgresql_table - truncate table + become: true + become_user: "{{ pg_user }}" + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test3 + truncate: true + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.queries == ['TRUNCATE TABLE "test3"'] + - result.state == "present" + +# Check the row exists: +- name: postgresql_table - check that row doesn't exist after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT * FROM test3 WHERE id = '1'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +# +# Check rename table +# + +# Rename a table in check_mode. +# In check_mode test4 won't be exist after the following playbook, +# so result.changed == 'absent' for the table with this name +- name: postgresql_table - rename table in check_mode + become: true + become_user: "{{ pg_user }}" + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test3 + rename: test4 + register: result + ignore_errors: true + check_mode: true + +- assert: + that: + - result is changed + - result.queries == ['ALTER TABLE "test3" RENAME TO "test4"'] + - result.state == "absent" + +# Check that the table exists after the previous step, rowcount must be 1 +- name: postgresql_table - check that table exists after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test3'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +# Rename a table: +- name: postgresql_table - rename table + become: true + become_user: "{{ pg_user }}" + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test3 + rename: test4 + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.queries == ['ALTER TABLE "test3" RENAME TO "test4"'] + - result.state == "present" + +# Check that the table test 3 doesn't exist after the previous step, rowcount must be - 0 +- name: postgresql_table - check that table exists after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test3'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +# Check that the table test 4 exists after the previous step, rowcount must be - 1 +- name: postgresql_table - check that table exists after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test4'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +# +# Check create unlogged table +# + +# Create unlogged table in check_mode: +- name: postgresql_table - create unlogged table in check_mode + become: true + become_user: "{{ pg_user }}" + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test5 + unlogged: true + register: result + ignore_errors: true + check_mode: true + +- assert: + that: + - result is changed + - result.queries == ['CREATE UNLOGGED TABLE "test5" ()'] + when: postgres_version_resp.stdout is version('9.1', '>=') + +# Check that the table doesn't exist after the previous step, rowcount must be - 0 +- name: postgresql_table - check that table doesn't exist after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test5'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +# Create unlogged table: +- name: postgresql_table - create unlogged table + become: true + become_user: "{{ pg_user }}" + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test5 + unlogged: true + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.queries == ['CREATE UNLOGGED TABLE "test5" ()'] + when: postgres_version_resp.stdout is version('9.1', '>=') + +# Check that the table exists after the previous step, rowcount must be - 1 +- name: postgresql_table - check that table exists after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test5'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('9.1', '>=') + +- assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('9.1', '>=') + +# Drop table CASCADE: +- name: postgresql_table - drop table cascade + become: true + become_user: "{{ pg_user }}" + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test5 + state: absent + cascade: true + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.queries == ['DROP TABLE "test5" CASCADE'] + when: postgres_version_resp.stdout is version('9.1', '>=') + +# Check that the table doesn't exist after the previous step, rowcount must be - 0 +- name: postgresql_table - check that table doesn't exist after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test5'" + ignore_errors: true + register: result + when: postgres_version_resp.stdout is version('9.1', '>=') + +- assert: + that: + - result.rowcount == 0 + when: postgres_version_resp.stdout is version('9.1', '>=') + +# +# Create, drop, and rename table in a specific schema: +# +- name: postgresql_table - create table in a specific schema + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: acme.test_schema_table + register: result + +- assert: + that: + - result is changed + - result.queries == ['CREATE TABLE "acme"."test_schema_table" ()'] + +- name: postgresql_table - check that table exists after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test_schema_table' and schemaname = 'acme'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_table - try to create a table with the same name and schema again + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: acme.test_schema_table + register: result + +- assert: + that: + - result is not changed + +- name: postgresql_table - create a table in the default schema for the next test + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: test_schema_table + register: result + +- assert: + that: + - result is changed + +- name: postgresql_table - drop the table from schema acme + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: postgres.acme.test_schema_table + state: absent + register: result + +- assert: + that: + - result is changed + - result.queries == ['DROP TABLE "postgres"."acme"."test_schema_table"'] + +- name: postgresql_table - check that the table doesn't exist after the previous step + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test_schema_table' and schemaname = 'acme'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 0 + +- name: postgresql_table - try to drop the table from schema acme again + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: acme.test_schema_table + state: absent + register: result + +- assert: + that: + - result is not changed + +- name: postgresql_table - check that the table with the same name in schema public exists + become_user: "{{ pg_user }}" + become: true + postgresql_query: + db: postgres + login_user: "{{ pg_user }}" + query: "SELECT 1 FROM pg_stat_all_tables WHERE relname = 'test_schema_table' and schemaname = 'public'" + ignore_errors: true + register: result + +- assert: + that: + - result.rowcount == 1 + +- name: postgresql_table - rename the table that contents a schema name + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: public.test_schema_table + rename: new_test_schema_table + trust_input: true + register: result + +- assert: + that: + - result is changed + - result.queries == ['ALTER TABLE "public"."test_schema_table" RENAME TO "new_test_schema_table"'] + +############################ +# Test trust_input parameter +- name: postgresql_table - check trust_input + postgresql_table: + db: postgres + login_user: "{{ pg_user }}" + name: postgres.acme.test_schema_table + state: absent + trust_input: false + session_role: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' + register: result + ignore_errors: true + +- assert: + that: + - result is failed + - result.msg is search('is potentially dangerous') + +# +# Clean up +# +- name: postgresql_table - drop test schema + become_user: "{{ pg_user }}" + become: true + postgresql_schema: + database: postgres + login_user: "{{ pg_user }}" + name: acme + state: absent + cascade_drop: true + +- name: postgresql_table - drop test role + become_user: "{{ pg_user }}" + become: true + postgresql_user: + db: postgres + login_user: "{{ pg_user }}" + name: "{{ item }}" + state: absent + loop: + - test_user + - alice + ignore_errors: true diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/defaults/main.yml new file mode 100644 index 000000000..1eb5b843f --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/defaults/main.yml @@ -0,0 +1,3 @@ +--- +test_tablespace_path: "/ssd" +dangerous_name: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/tasks/main.yml new file mode 100644 index 000000000..21a47ee3b --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/tasks/main.yml @@ -0,0 +1,7 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_tablespace module +- import_tasks: postgresql_tablespace_initial.yml diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/tasks/postgresql_tablespace_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/tasks/postgresql_tablespace_initial.yml new file mode 100644 index 000000000..b8e6d0b54 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_tablespace/tasks/postgresql_tablespace_initial.yml @@ -0,0 +1,245 @@ +- name: postgresql_tablespace - drop dir for test tablespace + become: true + file: + path: '{{ test_tablespace_path }}' + state: absent + ignore_errors: true + +- name: postgresql_tablespace - disable selinux + become: true + shell: setenforce 0 + ignore_errors: true + +- name: postgresql_tablespace - create dir for test tablespace + become: true + file: + path: '{{ test_tablespace_path }}' + state: directory + owner: '{{ pg_user }}' + group: '{{ pg_user }}' + mode: '0700' + ignore_errors: true + +- name: postgresql_tablespace - create test role to test change ownership + become_user: '{{ pg_user }}' + become: true + postgresql_user: + db: postgres + login_user: '{{ pg_user }}' + name: bob + state: present + ignore_errors: true + +- name: postgresql_tablespace - create test role to test change ownership + become_user: '{{ pg_user }}' + become: true + postgresql_user: + db: postgres + login_user: '{{ pg_user }}' + name: alice + state: present + ignore_errors: true + +- name: postgresql_tablespace - create a new tablespace called acme and set bob as an its owner + become_user: '{{ pg_user }}' + become: true + postgresql_tablespace: + db: postgres + login_user: '{{ pg_user }}' + name: acme + owner: bob + location: /ssd + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.owner == 'bob' + - result.queries == ["CREATE TABLESPACE \"acme\" LOCATION '/ssd'", "ALTER TABLESPACE \"acme\" OWNER TO \"bob\""] + - result.state == 'present' + - result.tablespace == 'acme' + - result.options == {} + - result.location == '/ssd' + +- name: postgresql_tablespace - try to create the same tablespace with different location + become_user: '{{ pg_user }}' + become: true + postgresql_tablespace: + db: postgres + login_user: '{{ pg_user }}' + name: acme + location: /another-ssd + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.msg == "Tablespace 'acme' exists with different location '/ssd'" + +- name: postgresql_tablespace - change tablespace owner to alice + become_user: '{{ pg_user }}' + become: true + postgresql_tablespace: + db: postgres + login_user: '{{ pg_user }}' + name: acme + owner: alice + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.owner == 'alice' + - result.queries == ["ALTER TABLESPACE \"acme\" OWNER TO \"alice\""] + - result.state == 'present' + - result.tablespace == 'acme' + - result.options == {} + +- name: postgresql_tablespace - try to change tablespace owner to alice again to be sure that nothing changes + become_user: '{{ pg_user }}' + become: true + postgresql_tablespace: + db: postgres + login_user: '{{ pg_user }}' + name: acme + owner: alice + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.owner == 'alice' + - result.queries == [] + - result.state == 'present' + - result.tablespace == 'acme' + - result.options == {} + +- name: postgresql_tablespace - change tablespace options + become_user: '{{ pg_user }}' + become: true + postgresql_tablespace: + db: postgres + login_user: '{{ pg_user }}' + name: acme + set: + seq_page_cost: 4 + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.owner == 'alice' + - result.queries == ["ALTER TABLESPACE \"acme\" SET (seq_page_cost = '4')"] + - result.state == 'present' + - result.tablespace == 'acme' + - result.options.seq_page_cost == '4' + when: postgres_version_resp.stdout is version('9.0', '>=') + +- name: postgresql_tablespace - reset seq_page_cost option + become_user: '{{ pg_user }}' + become: true + postgresql_tablespace: + login_db: postgres + login_user: '{{ pg_user }}' + name: acme + set: + seq_page_cost: reset + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.queries == ["ALTER TABLESPACE \"acme\" RESET (seq_page_cost)"] + when: postgres_version_resp.stdout is version('9.0', '>=') + +- name: postgresql_tablespace - reset seq_page_cost option again + become_user: '{{ pg_user }}' + become: true + postgresql_tablespace: + db: postgres + login_user: '{{ pg_user }}' + name: acme + set: + seq_page_cost: reset + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.queries == [] + when: postgres_version_resp.stdout is version('9.0', '>=') + +- name: postgresql_tablespace - rename tablespace + become_user: '{{ pg_user }}' + become: true + postgresql_tablespace: + db: postgres + login_user: '{{ pg_user }}' + name: acme + rename_to: foo + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.newname == 'foo' + - result.queries == ["ALTER TABLESPACE \"acme\" RENAME TO \"foo\""] + +- name: postgresql_tablespace - rename tablespace to potentially dangerous name + become_user: '{{ pg_user }}' + become: true + postgresql_tablespace: + db: postgres + login_user: '{{ pg_user }}' + name: foo + rename_to: '{{ dangerous_name }}' + trust_input: false + register: result + ignore_errors: true + +- assert: + that: + - result is failed + - result.msg == 'Passed input \'{{ dangerous_name }}\' is potentially dangerous' + +- name: postgresql_tablespace - drop tablespace + become_user: '{{ pg_user }}' + become: true + postgresql_tablespace: + db: postgres + login_user: '{{ pg_user }}' + name: foo + state: absent + trust_input: true + register: result + ignore_errors: true + +- assert: + that: + - result is changed + - result.state == 'absent' + - result.queries == ["DROP TABLESPACE \"foo\""] + +- name: postgresql_tablespace - try to drop nonexistent tablespace + become_user: '{{ pg_user }}' + become: true + postgresql_tablespace: + db: postgres + login_user: '{{ pg_user }}' + name: foo + state: absent + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.msg == "Tries to drop nonexistent tablespace 'foo'" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/aliases new file mode 100644 index 000000000..a4c92ef85 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/aliases @@ -0,0 +1,2 @@ +destructive +shippable/posix/group1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/defaults/main.yml new file mode 100644 index 000000000..dbcbea120 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/defaults/main.yml @@ -0,0 +1,4 @@ +db_name: 'ansible_db' +db_user1: 'ansible_db_user1' +db_user2: 'ansible_db_user2' +dangerous_name: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/main.yml new file mode 100644 index 000000000..150d26efd --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/main.yml @@ -0,0 +1,12 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial CI tests of postgresql_user module: +- import_tasks: postgresql_user_initial.yml + when: postgres_version_resp.stdout is version('9.4', '>=') + +# General tests: +- import_tasks: postgresql_user_general.yml + when: postgres_version_resp.stdout is version('9.4', '>=') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/postgresql_user_general.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/postgresql_user_general.yml new file mode 100644 index 000000000..cde95b0c6 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/postgresql_user_general.yml @@ -0,0 +1,802 @@ +# 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) +# Integration tests for postgresql_user module. + +- vars: + test_user: hello.user.with.dots + test_user2: hello + test_group1: group1 + test_group2: group2 + test_table: test + test_comment1: 'comment1' + test_comment2: 'comment2' + task_parameters: &task_parameters + become_user: '{{ pg_user }}' + become: true + register: result + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: postgres + + block: + # + # Common tests + # + - name: Create role in check_mode + <<: *task_parameters + check_mode: true + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + trust_input: false + + - assert: + that: + - result is changed + - result.user == '{{ test_user }}' + + - name: check that the user doesn't exist + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT rolname FROM pg_roles WHERE rolname = '{{ test_user }}'" + + - assert: + that: + - result.rowcount == 0 + + - name: Create role in actual mode + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + + - assert: + that: + - result is changed + - result.user == '{{ test_user }}' + + - name: check that the user exists + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT rolname FROM pg_roles WHERE rolname = '{{ test_user }}'" + + - assert: + that: + - result.rowcount == 1 + + - name: Add a comment on the user + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + comment: '{{ test_comment1 }}' + + - assert: + that: + - result is changed + - result.queries == ["COMMENT ON ROLE \"{{ test_user }}\" IS '{{ test_comment1 }}'"] + + - name: check the comment + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT pg_catalog.shobj_description(r.oid, 'pg_authid') AS comment + FROM pg_catalog.pg_roles r WHERE r.rolname = '{{ test_user }}' + + - assert: + that: + - result.rowcount == 1 + - result.query_result[0].comment == '{{ test_comment1 }}' + + - name: Try to add the same comment on the user + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + comment: '{{ test_comment1 }}' + trust_input: false + + - assert: + that: + - result is not changed + + - name: Try to add another comment on the user + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + comment: '{{ test_comment2 }}' + + - assert: + that: + - result is changed + - result.queries == ["COMMENT ON ROLE \"{{ test_user }}\" IS '{{ test_comment2 }}'"] + + - name: check the comment + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT pg_catalog.shobj_description(r.oid, 'pg_authid') AS comment + FROM pg_catalog.pg_roles r WHERE r.rolname = '{{ test_user }}' + + - assert: + that: + - result.rowcount == 1 + - result.query_result[0].comment == '{{ test_comment2 }}' + + - name: Try to create role again in check_mode + <<: *task_parameters + check_mode: true + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + + - assert: + that: + - result is not changed + - result.user == '{{ test_user }}' + + - name: check that the user exists + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT rolname FROM pg_roles WHERE rolname = '{{ test_user }}'" + + - assert: + that: + - result.rowcount == 1 + + - name: Try to create role again + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + + - assert: + that: + - result is not changed + - result.user == '{{ test_user }}' + + - name: check that the user exists + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT rolname FROM pg_roles WHERE rolname = '{{ test_user }}'" + + - assert: + that: + - result.rowcount == 1 + + - name: Drop role in check_mode + <<: *task_parameters + check_mode: true + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + state: absent + + - assert: + that: + - result is changed + - result.user == '{{ test_user }}' + + - name: check that the user actually exists + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT rolname FROM pg_roles WHERE rolname = '{{ test_user }}'" + + - assert: + that: + - result.rowcount == 1 + + - name: Drop role in actual mode + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + state: absent + + - assert: + that: + - result is changed + - result.user == '{{ test_user }}' + + - name: check that the user doesn't exist + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT rolname FROM pg_roles WHERE rolname = '{{ test_user }}'" + + - assert: + that: + - result.rowcount == 0 + + - name: Try to drop role in check mode again + <<: *task_parameters + check_mode: true + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + state: absent + + - assert: + that: + - result is not changed + - result.user == '{{ test_user }}' + + - name: Try to drop role in actual mode again + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + state: absent + + - assert: + that: + - result is not changed + - result.user == '{{ test_user }}' + + # + # password, no_password_changes, encrypted, expires parameters + # + + - name: Create role with password, passed as hashed md5 + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + password: md59543f1d82624df2b31672ec0f7050460 + + - assert: + that: + - result is changed + - result.user == '{{ test_user }}' + + - name: Check that the user exist with a proper password + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT rolname FROM pg_authid WHERE rolname = '{{ test_user }}' and rolpassword = 'md59543f1d82624df2b31672ec0f7050460'" + + - assert: + that: + - result.rowcount == 1 + + - name: Test no_password_changes + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + password: u123 + no_password_changes: true + + - assert: + that: + - result is not changed + - result.user == '{{ test_user }}' + + + - name: Check that nothing changed + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT rolname FROM pg_authid WHERE rolname = '{{ test_user }}' and rolpassword = 'md59543f1d82624df2b31672ec0f7050460'" + + - assert: + that: + - result.rowcount == 1 + + # Storing unencrypted passwords is not available from PostgreSQL 10 + - name: Change password, passed as unencrypted + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + password: myunencryptedpass + encrypted: false + when: postgres_version_resp.stdout is version('10', '<') + + - assert: + that: + - result is changed + - result.user == '{{ test_user }}' + when: postgres_version_resp.stdout is version('10', '<') + + - name: Check that the user exist with the unencrypted password + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT rolname FROM pg_authid WHERE rolname = '{{ test_user }}' and rolpassword = 'myunencryptedpass'" + when: postgres_version_resp.stdout is version('10', '<') + + - assert: + that: + - result.rowcount == 1 + when: postgres_version_resp.stdout is version('10', '<') + + - name: Change password, explicit encrypted=true + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + password: myunencryptedpass + encrypted: true + + - assert: + that: + - result is changed + - result.user == '{{ test_user }}' + + - name: Check that the user exist with encrypted password + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT rolname FROM pg_authid WHERE rolname = '{{ test_user }}' and rolpassword != 'myunencryptedpass'" + + - assert: + that: + - result.rowcount == 1 + + - name: Change rolvaliduntil attribute + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + expires: 'Jan 31 2020' + trust_input: false + + - assert: + that: + - result is changed + - result.user == '{{ test_user }}' + + - name: Check the prev step + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT rolname FROM pg_authid WHERE rolname = '{{ test_user }}' + AND rolvaliduntil::text like '2020-01-31%' + + - assert: + that: + - result.rowcount == 1 + + - name: Try to set the same rolvaliduntil value again + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + expires: 'Jan 31 2020' + + - assert: + that: + - result is not changed + - result.user == '{{ test_user }}' + + - name: Check that nothing changed + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT rolname FROM pg_authid WHERE rolname = '{{ test_user }}' + AND rolvaliduntil::text like '2020-01-31%' + + - assert: + that: + - result.rowcount == 1 + + # + # role_attr_flags + # + - name: Set role attributes + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + role_attr_flags: CREATEROLE,CREATEDB + trust_input: false + + - assert: + that: + - result is changed + - result.user == '{{ test_user }}' + + - name: Check the prev step + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT rolname FROM pg_authid WHERE rolname = '{{ test_user }}' + AND rolcreaterole = 't' and rolcreatedb = 't' + + - assert: + that: + - result.rowcount == 1 + + - name: Set the same role attributes again + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + role_attr_flags: CREATEROLE,CREATEDB + + - assert: + that: + - result is not changed + - result.user == '{{ test_user }}' + + - name: Check the prev step + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT rolname FROM pg_authid WHERE rolname = '{{ test_user }}' + AND rolcreaterole = 't' and rolcreatedb = 't' + + - name: Set role attributes + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + role_attr_flags: NOCREATEROLE,NOCREATEDB + + - assert: + that: + - result is changed + - result.user == '{{ test_user }}' + + - name: Check the prev step + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT rolname FROM pg_authid WHERE rolname = '{{ test_user }}' + AND rolcreaterole = 'f' and rolcreatedb = 'f' + + - assert: + that: + - result.rowcount == 1 + + - name: Set role attributes + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + role_attr_flags: NOCREATEROLE,NOCREATEDB + + - assert: + that: + - result is not changed + - result.user == '{{ test_user }}' + + - name: Check the prev step + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: > + SELECT rolname FROM pg_authid WHERE rolname = '{{ test_user }}' + AND rolcreaterole = 'f' and rolcreatedb = 'f' + + # + # priv + # + - name: Create test table + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: '{{ test_table }}' + columns: + - id int + + - name: Insert data to test table + <<: *task_parameters + postgresql_query: + query: "INSERT INTO {{ test_table }} (id) VALUES ('1')" + <<: *pg_parameters + + - name: Check that test_user is not allowed to read the data + <<: *task_parameters + postgresql_query: + db: postgres + login_user: '{{ pg_user }}' + session_role: '{{ test_user }}' + query: 'SELECT * FROM {{ test_table }}' + ignore_errors: true + + - assert: + that: + - result is failed + - "'permission denied' in result.msg" + + - name: Grant privileges + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + priv: '{{ test_table }}:SELECT' + trust_input: false + + - assert: + that: + - result is changed + + - name: Check that test_user is allowed to read the data + <<: *task_parameters + postgresql_query: + db: postgres + login_user: '{{ pg_user }}' + session_role: '{{ test_user }}' + query: 'SELECT * FROM {{ test_table }}' + + - assert: + that: + - result.rowcount == 1 + + - name: Grant the same privileges again + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + priv: '{{ test_table }}:SELECT' + + - assert: + that: + - result is not changed + + - name: Remove test table + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: '{{ test_table }}' + state: absent + + # + # fail_on_user + # + - name: Create role for test + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user2 }}' + + - name: Create test table, set owner as test_user + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: '{{ test_table }}' + owner: '{{ test_user2 }}' + + - name: Test fail_on_user + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user2 }}' + state: absent + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg == 'Unable to remove user' + + - name: Test fail_on_user + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + fail_on_user: false + + - assert: + that: + - result is not changed + + # + # Test groups parameter + # + - name: Create test group + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_group2 }}' + role_attr_flags: NOLOGIN + + - name: Create role test_group1 and grant test_group2 to test_group1 in check_mode + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_group1 }}' + groups: '{{ test_group2 }}' + role_attr_flags: NOLOGIN + check_mode: true + + - assert: + that: + - result is changed + - result.user == '{{ test_group1 }}' + - result.queries == ['CREATE USER "{{ test_group1 }}" NOLOGIN', 'GRANT "{{ test_group2 }}" TO "{{ test_group1 }}"'] + + - name: check that the user doesn't exist + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT rolname FROM pg_roles WHERE rolname = '{{ test_group1 }}'" + + - assert: + that: + - result.rowcount == 0 + + - name: check membership + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT grolist FROM pg_group WHERE groname = '{{ test_group2 }}' AND grolist != '{}'" + + - assert: + that: + - result.rowcount == 0 + + - name: Create role test_group1 and grant test_group2 to test_group1 + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_group1 }}' + groups: '{{ test_group2 }}' + role_attr_flags: NOLOGIN + trust_input: false + + - assert: + that: + - result is changed + - result.user == '{{ test_group1 }}' + - result.queries == ['CREATE USER "{{ test_group1 }}" NOLOGIN', 'GRANT "{{ test_group2 }}" TO "{{ test_group1 }}"'] + + - name: check that the user exists + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT rolname FROM pg_roles WHERE rolname = '{{ test_group1 }}'" + + - assert: + that: + - result.rowcount == 1 + + - name: check membership + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT grolist FROM pg_group WHERE groname = '{{ test_group2 }}' AND grolist != '{}'" + + - assert: + that: + - result.rowcount == 1 + + - name: Grant test_group2 to test_group1 again + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_group1 }}' + groups: '{{ test_group2 }}' + + - assert: + that: + - result is not changed + - result.user == '{{ test_group1 }}' + + - name: check membership + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT grolist FROM pg_group WHERE groname = '{{ test_group2 }}' AND grolist != '{}'" + + - assert: + that: + - result.rowcount == 1 + + - name: Grant groups to existent role + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ test_user }}' + groups: + - '{{ test_group1 }}' + - '{{ test_group2 }}' + trust_input: false + + - assert: + that: + - result is changed + - result.user == '{{ test_user }}' + - result.queries == ['GRANT "{{ test_group1 }}" TO "{{ test_user }}"', 'GRANT "{{ test_group2 }}" TO "{{ test_user }}"'] + + - name: check membership + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: "SELECT * FROM pg_group WHERE groname in ('{{ test_group1 }}', '{{ test_group2 }}') AND grolist != '{}'" + + - assert: + that: + - result.rowcount == 2 + + ######################## + # Test trust_input param + + - name: Create role with potentially dangerous name, don't trust + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ dangerous_name }}' + trust_input: false + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg == 'Passed input \'{{ dangerous_name }}\' is potentially dangerous' + + - name: Create role with potentially dangerous name, trust + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ dangerous_name }}' + + - assert: + that: + - result is changed + + ######################################################################## + # https://github.com/ansible-collections/community.postgresql/issues/122 + + - name: Create role with SELECT + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: 'issue122' + priv: 'pg_catalog.pg_stat_database:SELECT' + register: result + + - assert: + that: + - result is changed + + - name: Create role with SELECT again + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: 'issue122' + priv: 'pg_catalog.pg_stat_database:SELECT' + register: result + + - assert: + that: + - result is not changed + + always: + # + # Clean up + # + - name: Drop test table + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: '{{ test_table }}' + state: absent + + - name: Drop test user + <<: *task_parameters + postgresql_user: + <<: *pg_parameters + name: '{{ item }}' + state: absent + loop: + - '{{ test_user }}' + - '{{ test_user2 }}' + - '{{ test_group1 }}' + - '{{ test_group2 }}' + - '{{ dangerous_name }}' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/postgresql_user_initial.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/postgresql_user_initial.yml new file mode 100644 index 000000000..5d057d2dd --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/postgresql_user_initial.yml @@ -0,0 +1,156 @@ +# +# Create and destroy user, test 'password' and 'encrypted' parameters +# +# unencrypted values are not supported on newer versions +# do not run the encrypted: no tests if on 10+ +- ansible.builtin.set_fact: + encryption_values: + - 'true' + +- ansible.builtin.set_fact: + encryption_values: '{{ encryption_values + ["false"]}}' + when: postgres_version_resp.stdout is version('10', '<=') + +- include_tasks: test_password.yml + vars: + encrypted: '{{ loop_item }}' + db_password1: 'secretù' # use UTF-8 + loop: '{{ encryption_values }}' + loop_control: + loop_var: loop_item + +# BYPASSRLS role attribute was introduced in PostgreSQL 9.5, so +# we want to test attribute management differently depending +# on the version. +- ansible.builtin.set_fact: + bypassrls_supported: "{{ postgres_version_resp.stdout is version('9.5.0', '>=') }}" + +# test 'no_password_change' and 'role_attr_flags' parameters +- include_tasks: test_no_password_change.yml + vars: + no_password_changes: '{{ loop_item }}' + loop: + - 'true' + - 'false' + loop_control: + loop_var: loop_item + +### TODO: fail_on_user + +# +# Test login_user functionality +# +- name: Create a user to test login module parameters + become: true + become_user: "{{ pg_user }}" + postgresql_user: + name: "{{ db_user1 }}" + state: "present" + encrypted: 'true' + password: "password" + role_attr_flags: "CREATEDB,LOGIN,CREATEROLE" + login_user: "{{ pg_user }}" + trust_input: false + db: postgres + +- name: Create db + postgresql_db: + name: "{{ db_name }}" + state: "present" + login_user: "{{ db_user1 }}" + login_password: "password" + login_host: "localhost" + +- name: Check that database created + become: true + become_user: "{{ pg_user }}" + shell: echo "select datname from pg_database where datname = '{{ db_name }}';" | psql -d postgres + register: result + +- ansible.builtin.assert: + that: + - "result.stdout_lines[-1] == '(1 row)'" + +- name: Create a user + postgresql_user: + name: "{{ db_user2 }}" + state: "present" + encrypted: 'true' + password: "md55c8ccfd9d6711fc69a7eae647fc54f51" + db: "{{ db_name }}" + login_user: "{{ db_user1 }}" + login_password: "password" + login_host: "localhost" + trust_input: false + +- name: Check that it was created + become: true + become_user: "{{ pg_user }}" + shell: echo "select * from pg_user where usename='{{ db_user2 }}';" | psql -d postgres + register: result + +- ansible.builtin.assert: + that: + - "result.stdout_lines[-1] == '(1 row)'" + +- name: Grant database privileges + postgresql_privs: + type: "database" + state: "present" + roles: "{{ db_user2 }}" + privs: "CREATE,connect" + objs: "{{ db_name }}" + db: "{{ db_name }}" + login_user: "{{ db_user1 }}" + login_password: "password" + login_host: "localhost" + +- name: Check that the user has the requested permissions (database) + become: true + become_user: "{{ pg_user }}" + shell: echo "select datacl from pg_database where datname='{{ db_name }}';" | psql {{ db_name }} + register: result_database + +- ansible.builtin.assert: + that: + - "result_database.stdout_lines[-1] == '(1 row)'" + - "db_user2 ~ '=Cc' in result_database.stdout" + +- name: Remove user + postgresql_user: + name: "{{ db_user2 }}" + state: 'absent' + priv: "ALL" + db: "{{ db_name }}" + login_user: "{{ db_user1 }}" + login_password: "password" + login_host: "localhost" + trust_input: false + +- name: Check that they were removed + become: true + become_user: "{{ pg_user }}" + shell: echo "select * from pg_user where usename='{{ db_user2 }}';" | psql -d postgres + register: result + +- ansible.builtin.assert: + that: + - "result.stdout_lines[-1] == '(0 rows)'" + +- name: Destroy DB + postgresql_db: + state: absent + name: "{{ db_name }}" + login_user: "{{ db_user1 }}" + login_password: "password" + login_host: "localhost" + +- name: Check that database was destroyed + become: true + become_user: "{{ pg_user }}" + shell: echo "select datname from pg_database where datname = '{{ db_name }}';" | psql -d postgres + register: result + +- ansible.builtin.assert: + that: + - "result.stdout_lines[-1] == '(0 rows)'" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/test_no_password_change.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/test_no_password_change.yml new file mode 100644 index 000000000..41ecbe61f --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/test_no_password_change.yml @@ -0,0 +1,167 @@ +- vars: + task_parameters: &task_parameters + become_user: "{{ pg_user }}" + become: true + register: result + postgresql_parameters: ¶meters + db: postgres + name: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + + block: + + - name: Create a user with all role attributes + <<: *task_parameters + postgresql_user: + <<: *parameters + state: "present" + role_attr_flags: "SUPERUSER,CREATEROLE,CREATEDB,INHERIT,LOGIN{{ bypassrls_supported | ternary(',BYPASSRLS', '') }}" + no_password_changes: '{{ no_password_changes }}' # no_password_changes is ignored when user doesn't already exist + + - name: Check that the user has the requested role attributes + <<: *task_parameters + shell: "echo \"select 'super:'||rolsuper, 'createrole:'||rolcreaterole, 'create:'||rolcreatedb, 'inherit:'||rolinherit, 'login:'||rolcanlogin {{ bypassrls_supported | ternary(\", 'bypassrls:'||rolbypassrls\", '') }} from pg_roles where rolname='{{ db_user1 }}';\" | psql -d postgres" + + - assert: + that: + - "result.stdout_lines[-1] == '(1 row)'" + - "'super:t' in result.stdout_lines[-2]" + - "'createrole:t' in result.stdout_lines[-2]" + - "'create:t' in result.stdout_lines[-2]" + - "'inherit:t' in result.stdout_lines[-2]" + - "'login:t' in result.stdout_lines[-2]" + + - block: + - name: Check that the user has the requested role attribute BYPASSRLS + <<: *task_parameters + shell: "echo \"select 'bypassrls:'||rolbypassrls from pg_roles where rolname='{{ db_user1 }}';\" | psql -d postgres" + + - assert: + that: + - "not bypassrls_supported or 'bypassrls:t' in result.stdout_lines[-2]" + when: bypassrls_supported + + - name: Modify a user to have no role attributes + <<: *task_parameters + postgresql_user: + <<: *parameters + state: "present" + role_attr_flags: "NOSUPERUSER,NOCREATEROLE,NOCREATEDB,noinherit,NOLOGIN{{ bypassrls_supported | ternary(',NOBYPASSRLS', '') }}" + no_password_changes: '{{ no_password_changes }}' + + - name: Check that ansible reports it modified the role + assert: + that: + - result is changed + + - name: "Check that the user doesn't have any attribute" + <<: *task_parameters + shell: "echo \"select 'super:'||rolsuper, 'createrole:'||rolcreaterole, 'create:'||rolcreatedb, 'inherit:'||rolinherit, 'login:'||rolcanlogin from pg_roles where rolname='{{ db_user1 }}';\" | psql -d postgres" + + - assert: + that: + - "result.stdout_lines[-1] == '(1 row)'" + - "'super:f' in result.stdout_lines[-2]" + - "'createrole:f' in result.stdout_lines[-2]" + - "'create:f' in result.stdout_lines[-2]" + - "'inherit:f' in result.stdout_lines[-2]" + - "'login:f' in result.stdout_lines[-2]" + + - block: + - name: Check that the user has the requested role attribute BYPASSRLS + <<: *task_parameters + shell: "echo \"select 'bypassrls:'||rolbypassrls from pg_roles where rolname='{{ db_user1 }}';\" | psql -d postgres" + + - assert: + that: + - "not bypassrls_supported or 'bypassrls:f' in result.stdout_lines[-2]" + when: bypassrls_supported + + - name: Try to add an invalid attribute + <<: *task_parameters + postgresql_user: + <<: *parameters + state: "present" + role_attr_flags: "NOSUPERUSER,NOCREATEROLE,NOCREATEDB,noinherit,NOLOGIN{{ bypassrls_supported | ternary(',NOBYPASSRLS', '') }},INVALID" + no_password_changes: '{{ no_password_changes }}' + ignore_errors: true + + - name: Check that ansible reports failure + assert: + that: + - result is not changed + - result is failed + - "result.msg == 'Invalid role_attr_flags specified: INVALID'" + + - name: Modify a single role attribute on a user + <<: *task_parameters + postgresql_user: + <<: *parameters + state: "present" + role_attr_flags: "LOGIN" + no_password_changes: '{{ no_password_changes }}' + + - name: Check that ansible reports it modified the role + assert: + that: + - result is changed + + - name: Check the role attributes + <<: *task_parameters + shell: echo "select 'super:'||rolsuper, 'createrole:'||rolcreaterole, 'create:'||rolcreatedb, 'inherit:'||rolinherit, 'login:'||rolcanlogin from pg_roles where rolname='{{ db_user1 }}';" | psql -d postgres + + - assert: + that: + - "result.stdout_lines[-1] == '(1 row)'" + - "'super:f' in result.stdout_lines[-2]" + - "'createrole:f' in result.stdout_lines[-2]" + - "'create:f' in result.stdout_lines[-2]" + - "'inherit:f' in result.stdout_lines[-2]" + - "'login:t' in result.stdout_lines[-2]" + + - block: + - name: Check the role attribute BYPASSRLS + <<: *task_parameters + shell: echo "select 'bypassrls:'||rolbypassrls from pg_roles where rolname='{{ db_user1 }}';" | psql -d postgres + + - assert: + that: + - "( postgres_version_resp.stdout is version('9.5.0', '<')) or 'bypassrls:f' in result.stdout_lines[-2]" + when: bypassrls_supported + + - name: Check that using same attribute a second time does nothing + <<: *task_parameters + postgresql_user: + <<: *parameters + state: "present" + role_attr_flags: "LOGIN" + no_password_changes: '{{ no_password_changes }}' + environment: + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - name: Check there isn't any update reported + assert: + that: + - result is not changed + + - name: Cleanup the user + <<: *task_parameters + postgresql_user: + <<: *parameters + state: 'absent' + no_password_changes: '{{ no_password_changes }}' # user deletion: no_password_changes is ignored + + - name: Check that user was removed + <<: *task_parameters + shell: echo "select * from pg_user where usename='{{ db_user1 }}';" | psql -d postgres + + - assert: + that: + - "result.stdout_lines[-1] == '(0 rows)'" + + always: + - name: Cleanup the user + <<: *task_parameters + postgresql_user: + <<: *parameters + state: 'absent' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/test_password.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/test_password.yml new file mode 100644 index 000000000..aece258fd --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user/tasks/test_password.yml @@ -0,0 +1,429 @@ +- vars: + task_parameters: &task_parameters + become_user: "{{ pg_user }}" + become: true + register: result + postgresql_query_parameters: &query_parameters + db: postgres + login_user: "{{ pg_user }}" + postgresql_parameters: ¶meters + <<: *query_parameters + name: "{{ db_user1 }}" + + block: + - name: 'Check that PGOPTIONS environment variable is effective (1/2)' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: '{{ db_password1 }}' + ignore_errors: true + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - name: 'Check that PGOPTIONS environment variable is effective (2/2)' + assert: + that: + - "{{ result is failed }}" + + - name: 'Create a user (password encrypted: {{ encrypted }})' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: '{{ db_password1 }}' + encrypted: '{{ encrypted }}' + environment: + PGCLIENTENCODING: 'UTF8' + + - block: &changed # block is only used here in order to be able to define YAML anchor + - name: Check that ansible reports it was created + assert: + that: + - "{{ result is changed }}" + + - name: Check that it was created + <<: *task_parameters + shell: echo "select * from pg_user where usename='{{ db_user1 }}';" | psql -d postgres + + - assert: + that: + - "result.stdout_lines[-1] == '(1 row)'" + + - name: Check that creating user a second time does nothing + <<: *task_parameters + postgresql_user: + <<: *parameters + password: '{{ db_password1 }}' + encrypted: '{{ encrypted }}' + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - block: ¬_changed # block is only used here in order to be able to define YAML anchor + - name: Check that ansible reports no change + assert: + that: + - "{{ result is not changed }}" + + - name: 'Define an expiration time' + <<: *task_parameters + postgresql_user: + <<: *parameters + expires: '2025-01-01' + environment: + PGCLIENTENCODING: 'UTF8' + + - <<: *changed + + - name: 'Redefine the same expiration time' + <<: *task_parameters + postgresql_user: + expires: '2025-01-01' + <<: *parameters + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - <<: *not_changed + + - block: + + - name: 'Using MD5-hashed password: check that password not changed when using cleartext password' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: '{{ db_password1 }}' + encrypted: 'true' + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - <<: *not_changed + + - name: "Using MD5-hashed password: check that password not changed when using md5 hash with 'ENCRYPTED'" + <<: *task_parameters + postgresql_user: + <<: *parameters + password: "md5{{ (db_password1 ~ db_user1) | hash('md5')}}" + encrypted: 'true' + environment: + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - <<: *not_changed + + - name: "Using MD5-hashed password: check that password not changed when using md5 hash with 'UNENCRYPTED'" + <<: *task_parameters + postgresql_user: + <<: *parameters + password: "md5{{ (db_password1 ~ db_user1) | hash('md5')}}" + encrypted: 'false' + environment: + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - <<: *not_changed + + - name: 'Redefine the same expiration time and password (encrypted)' + <<: *task_parameters + postgresql_user: + <<: *parameters + encrypted: 'true' + password: "md5{{ (db_password1 ~ db_user1) | hash('md5')}}" + expires: '2025-01-01' + environment: + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - <<: *not_changed + + - name: 'Using MD5-hashed password: check that password changed when using another cleartext password' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: 'prefix{{ db_password1 }}' + encrypted: 'true' + environment: + PGCLIENTENCODING: 'UTF8' + + - <<: *changed + + - name: "Using MD5-hashed password: check that password changed when using another md5 hash with 'ENCRYPTED'" + <<: *task_parameters + postgresql_user: + <<: *parameters + password: "md5{{ ('prefix1' ~ db_password1 ~ db_user1) | hash('md5')}}" + encrypted: 'true' + + - <<: *changed + + - name: "Using MD5-hashed password: check that password changed when using md5 hash with 'UNENCRYPTED'" + <<: *task_parameters + postgresql_user: + <<: *parameters + password: "md5{{ ('prefix2' ~ db_password1 ~ db_user1) | hash('md5')}}" + encrypted: 'false' + register: change_pass_unencrypted + failed_when: + - change_pass_unencrypted is failed + # newer version of psycopg2 no longer supported unencrypted password, we ignore the error + - '"UNENCRYPTED PASSWORD is no longer supported" not in change_pass_unencrypted.msg' + + - <<: *changed + + - name: 'Using MD5-hashed password: check that password changed when clearing the password' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: '' + encrypted: 'true' + environment: + PGCLIENTENCODING: 'UTF8' + + - <<: *changed + + - name: 'Using MD5-hashed password: check that password not changed when clearing the password again' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: '' + encrypted: 'true' + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - <<: *not_changed + + - name: 'Using cleartext password: check that password not changed when clearing the password again' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: '' + encrypted: 'false' + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - <<: *not_changed + + - name: 'Using MD5-hashed password: check that password changed when using a cleartext password' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: '{{ db_password1 }}' + encrypted: 'true' + environment: + PGCLIENTENCODING: 'UTF8' + + - <<: *changed + + when: encrypted == 'true' and postgres_version_resp.stdout is version('14', '<') + + - block: + + - name: 'Using cleartext password: check that password not changed when using cleartext password' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: "{{ db_password1 }}" + encrypted: 'false' + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - <<: *not_changed + + - name: 'Redefine the same expiration time and password (not encrypted)' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: "{{ db_password1 }}" + encrypted: 'false' + expires: '2025-01-01' + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - <<: *not_changed + + - name: 'Using cleartext password: check that password changed when using another cleartext password' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: "changed{{ db_password1 }}" + encrypted: 'false' + environment: + PGCLIENTENCODING: 'UTF8' + + - <<: *changed + + - name: 'Using cleartext password: check that password changed when clearing the password' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: '' + encrypted: 'false' + environment: + PGCLIENTENCODING: 'UTF8' + + - <<: *changed + + - name: 'Using cleartext password: check that password not changed when clearing the password again' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: '' + encrypted: 'false' + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - <<: *not_changed + + - name: 'Using MD5-hashed password: check that password not changed when clearing the password again' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: '' + encrypted: 'true' + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - <<: *not_changed + + - name: 'Using cleartext password: check that password changed when using cleartext password' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: "{{ db_password1 }}" + encrypted: 'false' + environment: + PGCLIENTENCODING: 'UTF8' + + - <<: *changed + + when: encrypted == 'false' + + # start of block scram-sha-256 + # scram-sha-256 password encryption type is supported since PostgreSQL 10 + - when: postgres_version_resp.stdout is version('10', '>=') + block: + + - name: 'Using cleartext password with scram-sha-256: resetting password' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: "" + encrypted: "{{ encrypted }}" + environment: + PGCLIENTENCODING: 'UTF8' + + - name: 'Using cleartext password with scram-sha-256: check that password is changed when using cleartext password' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: "{{ db_password1 }}" + encrypted: "{{ encrypted }}" + environment: + PGCLIENTENCODING: 'UTF8' + # ansible postgresql_user module interface does not (yet) support forcing password_encryption + # type value, we'll have to hack it in env variable to force correct encryption + PGOPTIONS: "-c password_encryption=scram-sha-256" + + - <<: *changed + + - name: 'Using cleartext password with scram-sha-256: ensure password is properly encrypted' + <<: *task_parameters + postgresql_query: + <<: *query_parameters + query: select * from pg_authid where rolname=%s and rolpassword like %s + positional_args: + - '{{ db_user1 }}' + - 'SCRAM-SHA-256$%' + + - assert: + that: + - result.rowcount == 1 + + - name: 'Using cleartext password with scram-sha-256: check that password is not changed when using the same password' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: "{{ db_password1 }}" + encrypted: "{{ encrypted }}" + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: "-c password_encryption=scram-sha-256" + + - <<: *not_changed + + - name: 'Using cleartext password with scram-sha-256: check that password is changed when using another cleartext password' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: "changed{{ db_password1 }}" + encrypted: "{{ encrypted }}" + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: "-c password_encryption=scram-sha-256" + + - <<: *changed + + - name: 'Using cleartext password with scram-sha-256: check that password is changed when clearing the password' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: '' + encrypted: "{{ encrypted }}" + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: "-c password_encryption=scram-sha-256" + + - <<: *changed + + - name: 'Using cleartext password with scram-sha-256: check that password is not changed when clearing the password again' + <<: *task_parameters + postgresql_user: + <<: *parameters + password: '' + encrypted: "{{ encrypted }}" + environment: + PGCLIENTENCODING: 'UTF8' + PGOPTIONS: "-c password_encryption=scram-sha-256" + + - <<: *not_changed + + # end of block scram-sha-256 + + - name: Remove user + <<: *task_parameters + postgresql_user: + state: 'absent' + <<: *parameters + + - <<: *changed + + - name: Check that they were removed + <<: *task_parameters + shell: echo "select * from pg_user where usename='{{ db_user1 }}';" | psql -d postgres + environment: + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - assert: + that: + - "result.stdout_lines[-1] == '(0 rows)'" + + - name: Check that removing user a second time does nothing + <<: *task_parameters + postgresql_user: + state: 'absent' + <<: *parameters + environment: + PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed + + - <<: *not_changed + + always: + - name: Remove user + <<: *task_parameters + postgresql_user: + state: 'absent' + <<: *parameters diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/aliases b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/aliases new file mode 100644 index 000000000..786e05315 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/aliases @@ -0,0 +1,4 @@ +destructive +shippable/posix/group1 +skip/freebsd +skip/rhel diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/defaults/main.yml new file mode 100644 index 000000000..f697cefd3 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/defaults/main.yml @@ -0,0 +1,12 @@ +pg_user: postgres +db_default: postgres + +test_table1: acme1 +test_table2: acme2 +test_table3: acme3 +test_idx1: idx1 +test_idx2: idx2 +test_func1: func1 +test_func2: func2 +test_func3: func3 +test_schema1: schema1 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/meta/main.yml new file mode 100644 index 000000000..4ce5a5837 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_postgresql_db diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/tasks/main.yml new file mode 100644 index 000000000..fa47fdc58 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/tasks/main.yml @@ -0,0 +1,8 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Initial tests of postgresql_user_obj_stat_info module: +- import_tasks: postgresql_user_obj_stat_info.yml + when: postgres_version_resp.stdout is version('9.4', '>=') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/tasks/postgresql_user_obj_stat_info.yml b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/tasks/postgresql_user_obj_stat_info.yml new file mode 100644 index 000000000..62f72d9ec --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/postgresql_user_obj_stat_info/tasks/postgresql_user_obj_stat_info.yml @@ -0,0 +1,222 @@ +--- +# 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) + +- vars: + task_parameters: &task_parameters + become_user: '{{ pg_user }}' + become: true + register: result + pg_parameters: &pg_parameters + login_user: '{{ pg_user }}' + login_db: '{{ db_default }}' + + block: + # Preparation: + # 0. create test schema + # 1. create test tables + # 2. create test indexes + # 3. create test functions + # 4. enable track_functions and restart + + - name: Create schema + <<: *task_parameters + postgresql_schema: + <<: *pg_parameters + name: '{{ test_schema1 }}' + + - name: Create test tables + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: '{{ item }}' + columns: + - id int + loop: + - '{{ test_table1 }}' + - '{{ test_table2 }}' + + - name: Create test table in another schema + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: '{{ test_schema1 }}.{{ test_table3 }}' + + - name: Create test indexes + <<: *task_parameters + postgresql_idx: + <<: *pg_parameters + name: '{{ item }}' + table: '{{ test_table1 }}' + columns: + - id + loop: + - '{{ test_idx1 }}' + - '{{ test_idx2 }}' + + - name: Set track_function (restart is required) + <<: *task_parameters + postgresql_set: + <<: *pg_parameters + name: track_functions + value: all + + # To avoid CI timeouts + - name: Kill all postgres processes + shell: 'pkill -u {{ pg_user }}' + become: true + when: ansible_facts.distribution == 'CentOS' and ansible_facts.distribution_major_version == '8' + ignore_errors: true + + - name: Stop PostgreSQL + become: true + service: + name: "{{ postgresql_service }}" + state: stopped + when: (ansible_facts.distribution_major_version != '8' and ansible_facts.distribution == 'CentOS') or ansible_facts.distribution != 'CentOS' + + - name: Pause between stop and start PosgreSQL + pause: + seconds: 5 + + - name: Start PostgreSQL + become: true + service: + name: "{{ postgresql_service }}" + state: started + + - name: Create test functions + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: 'CREATE FUNCTION {{ item }}() RETURNS boolean AS $$ BEGIN RETURN 1; END; $$ LANGUAGE PLPGSQL' + loop: + - '{{ test_func1 }}' + - '{{ test_func2 }}' + - '{{ test_schema1 }}.{{ test_func3 }}' + + - name: Touch test functions + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: 'SELECT {{ item }}()' + loop: + - '{{ test_func1 }}' + - '{{ test_func2 }}' + - '{{ test_schema1 }}.{{ test_func3 }}' + + ####### + # Tests + ####### + # 0. Without filter + - name: Collect all stats + <<: *task_parameters + postgresql_user_obj_stat_info: + <<: *pg_parameters + + - assert: + that: + - result is not changed + - result.tables.public.{{ test_table1 }}.size == 0 + - result.tables.public.{{ test_table1 }}.size == 0 + - result.tables.{{ test_schema1 }}.{{ test_table3 }}.size == 0 + - result.functions.public.{{ test_func1 }}.calls == 1 + - result.functions.public.{{ test_func2 }}.calls == 1 + - result.functions.{{ test_schema1 }}.{{ test_func3 }}.calls == 1 + - result.indexes.public.{{ test_idx1 }}.idx_scan == 0 + - result.indexes.public.{{ test_idx2 }}.idx_scan == 0 + + # 1. With filter + - name: Collect stats with filter + <<: *task_parameters + postgresql_user_obj_stat_info: + <<: *pg_parameters + filter: tables, indexes + + - assert: + that: + - result is not changed + - result.tables.public.{{ test_table1 }}.size == 0 + - result.tables.public.{{ test_table1 }}.size == 0 + - result.tables.{{ test_schema1 }}.{{ test_table3 }}.size == 0 + - result.functions == {} + - result.indexes.public.{{ test_idx1 }}.idx_scan == 0 + - result.indexes.public.{{ test_idx2 }}.idx_scan == 0 + + # 2. With schema + - name: Collect stats for objects in certain schema + <<: *task_parameters + postgresql_user_obj_stat_info: + <<: *pg_parameters + schema: public + + - assert: + that: + - result is not changed + - result.tables.public.{{ test_table1 }}.size == 0 + - result.tables.public.{{ test_table1 }}.size == 0 + - result.indexes.public.{{ test_idx1 }}.idx_scan == 0 + - result.indexes.public.{{ test_idx2 }}.idx_scan == 0 + - result.functions.public.{{ test_func1 }}.calls == 1 + - result.functions.public.{{ test_func2 }}.calls == 1 + - result.tables.{{ test_schema1 }} is not defined + + + # 3. With wrong schema + - name: Try to collect data in nonexistent schema + <<: *task_parameters + postgresql_user_obj_stat_info: + <<: *pg_parameters + schema: nonexistent + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg == "Schema 'nonexistent' does not exist" + + # 4. Test Trust Input + - name: Try running with SQL injection + <<: *task_parameters + postgresql_user_obj_stat_info: + <<: *pg_parameters + session_role: 'curious.anonymous"; SELECT * FROM information_schema.tables; --' + trust_input: false + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg is search('is potentially dangerous') + + ########## + # Clean up + ########## + - name: Drop schema + <<: *task_parameters + postgresql_schema: + <<: *pg_parameters + name: '{{ test_schema1 }}' + state: absent + cascade_drop: true + + - name: Drop test tables + <<: *task_parameters + postgresql_table: + <<: *pg_parameters + name: '{{ item }}' + state: absent + loop: + - '{{ test_table1 }}' + - '{{ test_table2 }}' + + - name: Drop test functions + <<: *task_parameters + postgresql_query: + <<: *pg_parameters + query: 'DROP FUNCTION {{ item }}()' + loop: + - '{{ test_func1 }}' + - '{{ test_func2 }}' + - '{{ test_schema1 }}.{{ test_func3 }}' + ignore_errors: true diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_pkg_mgr/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_pkg_mgr/tasks/main.yml new file mode 100644 index 000000000..cc4e3b0dd --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_pkg_mgr/tasks/main.yml @@ -0,0 +1,17 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- set_fact: + pkg_mgr: community.general.pkgng + ansible_pkg_mgr: community.general.pkgng + cacheable: true + when: ansible_os_family == "FreeBSD" + +- set_fact: + pkg_mgr: community.general.zypper + ansible_pkg_mgr: community.general.zypper + cacheable: true + when: ansible_os_family == "Suse" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/defaults/main.yml new file mode 100644 index 000000000..973d41591 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/defaults/main.yml @@ -0,0 +1,21 @@ +postgresql_service: postgresql + +postgresql_packages: + - postgresql-server + - python-psycopg2 + +pg_user: postgres +pg_group: root + +locale_latin_suffix: +locale_utf8_suffix: + +postgis: postgis + +# defaults for test SSL +ssl_db: 'ssl_db' +ssl_user: 'ssl_user' +ssl_pass: 'ssl_pass' +ssl_rootcert: '/etc/server-ca.crt' +ssl_cert: '/etc/client.crt' +ssl_key: '/etc/client.key'
\ No newline at end of file diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--0.sql b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--0.sql new file mode 100644 index 000000000..626b0fb48 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--0.sql @@ -0,0 +1,2 @@ +CREATE OR REPLACE FUNCTION dummy_display_ext_version() +RETURNS text LANGUAGE SQL AS 'SELECT (''0'')::text'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--1.0.sql b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--1.0.sql new file mode 100644 index 000000000..53c79666b --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--1.0.sql @@ -0,0 +1,2 @@ +CREATE OR REPLACE FUNCTION dummy_display_ext_version() +RETURNS text LANGUAGE SQL AS 'SELECT (''1.0'')::text'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--2.0.sql b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--2.0.sql new file mode 100644 index 000000000..227ba1b4c --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--2.0.sql @@ -0,0 +1,2 @@ +CREATE OR REPLACE FUNCTION dummy_display_ext_version() +RETURNS text LANGUAGE SQL AS 'SELECT (''2.0'')::text'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3-1.0-1.sql b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3-1.0-1.sql new file mode 100644 index 000000000..3c23cb5d1 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3-1.0-1.sql @@ -0,0 +1,2 @@ +CREATE OR REPLACE FUNCTION dummy_display_ext_version() +RETURNS text LANGUAGE SQL AS 'SELECT (''3-1.0-1'')::text'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3-1.0.sql b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3-1.0.sql new file mode 100644 index 000000000..252680ad6 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3-1.0.sql @@ -0,0 +1,2 @@ +CREATE OR REPLACE FUNCTION dummy_display_ext_version() +RETURNS text LANGUAGE SQL AS 'SELECT (''3-1.0'')::text'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3-1.foo.sql b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3-1.foo.sql new file mode 100644 index 000000000..6d706ede9 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3-1.foo.sql @@ -0,0 +1,2 @@ +CREATE OR REPLACE FUNCTION dummy_display_ext_version() +RETURNS text LANGUAGE SQL AS 'SELECT (''3-1.foo'')::text'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3.0-1.sql b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3.0-1.sql new file mode 100644 index 000000000..b366bd17d --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3.0-1.sql @@ -0,0 +1,2 @@ +CREATE OR REPLACE FUNCTION dummy_display_ext_version() +RETURNS text LANGUAGE SQL AS 'SELECT (''3.0-1'')::text'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3.0-foo.sql b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3.0-foo.sql new file mode 100644 index 000000000..659e2113b --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3.0-foo.sql @@ -0,0 +1,2 @@ +CREATE OR REPLACE FUNCTION dummy_display_ext_version() +RETURNS text LANGUAGE SQL AS 'SELECT (''3.0-foo'')::text'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3.0.sql b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3.0.sql new file mode 100644 index 000000000..7d6a60e54 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3.0.sql @@ -0,0 +1,2 @@ +CREATE OR REPLACE FUNCTION dummy_display_ext_version() +RETURNS text LANGUAGE SQL AS 'SELECT (''3.0'')::text'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3.beta.sql b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3.beta.sql new file mode 100644 index 000000000..0e945a1d0 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--3.beta.sql @@ -0,0 +1,2 @@ +CREATE OR REPLACE FUNCTION dummy_display_ext_version() +RETURNS text LANGUAGE SQL AS 'SELECT (''3.beta'')::text'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--4.0.sql b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--4.0.sql new file mode 100644 index 000000000..33aaef568 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--4.0.sql @@ -0,0 +1,2 @@ +CREATE OR REPLACE FUNCTION dummy_display_ext_version() +RETURNS text LANGUAGE SQL AS 'SELECT (''4.0'')::text'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--v4.sql b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--v4.sql new file mode 100644 index 000000000..bb966db98 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy--v4.sql @@ -0,0 +1,2 @@ +CREATE OR REPLACE FUNCTION dummy_display_ext_version() +RETURNS text LANGUAGE SQL AS 'SELECT (''v4'')::text'; diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy.control b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy.control new file mode 100644 index 000000000..fda97bc58 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/dummy.control @@ -0,0 +1,3 @@ +comment = 'dummy extension used to test postgresql_ext Ansible module' +default_version = '4.0' +relocatable = true diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/pg_hba.conf b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/pg_hba.conf new file mode 100644 index 000000000..58de3607f --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/files/pg_hba.conf @@ -0,0 +1,10 @@ +# !!! This file managed by Ansible. Any local changes may be overwritten. !!! + +# Database administrative login by UNIX sockets +# note: you may wish to restrict this further later +local all {{ pg_user }} trust + +# TYPE DATABASE USER CIDR-ADDRESS METHOD +local all all md5 +host all all 127.0.0.1/32 md5 +host all all ::1/128 md5 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/meta/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/meta/main.yml new file mode 100644 index 000000000..5438ced5c --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_pkg_mgr diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/tasks/main.yml new file mode 100644 index 000000000..80bb3c4d4 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/tasks/main.yml @@ -0,0 +1,279 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: python 2 + set_fact: + python_suffix: '' + when: ansible_python_version is version('3', '<') + +- name: python 3 + set_fact: + python_suffix: -py3 + when: ansible_python_version is version('3', '>=') + +- name: Include distribution and Python version specific variables + include_vars: '{{ lookup(''first_found'', params) }}' + vars: + params: + files: + - '{{ ansible_distribution }}-{{ ansible_distribution_major_version }}{{ python_suffix }}.yml' + - '{{ ansible_distribution }}-{{ ansible_distribution_version }}{{ python_suffix }}.yml' + - '{{ ansible_os_family }}{{ python_suffix }}.yml' + - default{{ python_suffix }}.yml + paths: + - '{{ role_path }}/vars' + +- name: Make sure the dbus service is enabled under systemd + shell: systemctl enable dbus || systemctl enable dbus-broker + ignore_errors: true + when: ansible_service_mgr == 'systemd' and ansible_distribution == 'Fedora' + +- name: Make sure the dbus service is started under systemd + systemd: + name: dbus + state: started + when: ansible_service_mgr == 'systemd' and ansible_distribution == 'Fedora' + +- name: Kill all postgres processes + shell: 'pkill -u {{ pg_user }}' + become: true + when: ansible_facts.distribution == 'CentOS' and ansible_facts.distribution_major_version == '8' + ignore_errors: true + +- name: stop postgresql service + service: name={{ postgresql_service }} state=stopped + ignore_errors: true + +- name: remove old db (RedHat) + file: + path: '{{ pg_dir }}' + state: absent + ignore_errors: true + when: ansible_os_family == "RedHat" + +- name: remove old db config and files (debian) + file: + path: '{{ loop_item }}' + state: absent + ignore_errors: true + when: ansible_os_family == "Debian" + loop: + - /etc/postgresql + - /var/lib/postgresql + loop_control: + loop_var: loop_item + +# +# Install PostgreSQL 15 on Ubuntu 20.04 +- name: Install PostgreSQL 15 on Ubuntu 20.04 + when: + - ansible_facts.distribution == 'Ubuntu' + - ansible_facts.distribution_major_version == '20' + block: + - name: Run autoremove + become: true + apt: + autoremove: true + + - name: Install wget + package: + name: wget + + - name: Create the file repository configuration + lineinfile: + create: true + line: "deb http://apt.postgresql.org/pub/repos/apt {{ ansible_facts['distribution_release'] }}-pgdg main" + path: '/etc/apt/sources.list.d/pgdg.list' + state: 'present' + + - name: Import the repository signing key + ansible.builtin.apt_key: + state: present + url: https://www.postgresql.org/media/keys/ACCC4CF8.asc + + - name: Update the package lists + apt: + update_cache: true + + - name: Install locale needed + shell: 'locale-gen {{ item }}' + loop: + - es_ES + - pt_BR + + - name: Update locale + shell: 'update-locale' +## +# + +- name: install dependencies for postgresql test + package: + name: '{{ postgresql_package_item }}' + state: present + with_items: '{{ postgresql_packages }}' + loop_control: + loop_var: postgresql_package_item + +- name: Initialize postgres (RedHat systemd) + command: postgresql-setup initdb + when: ansible_os_family == "RedHat" and ansible_service_mgr == "systemd" + +- name: Initialize postgres (RedHat sysv) + command: /sbin/service postgresql initdb + when: ansible_os_family == "RedHat" and ansible_service_mgr != "systemd" + +- name: Initialize postgres (Debian) + shell: . /usr/share/postgresql-common/maintscripts-functions && set_system_locale && /usr/bin/pg_createcluster -u postgres {{ pg_ver }} main + args: + creates: /etc/postgresql/{{ pg_ver }}/ + when: ansible_os_family == 'Debian' + +- name: Copy pg_hba into place + template: + src: files/pg_hba.conf + dest: '{{ pg_hba_location }}' + owner: '{{ pg_user }}' + group: '{{ pg_group }}' + mode: '0644' + +- name: Generate locales (Debian) + locale_gen: + name: '{{ item }}' + state: present + with_items: + - pt_BR + - es_ES + when: ansible_os_family == 'Debian' + +- block: + - name: Install langpacks (RHEL8) + yum: + name: + - glibc-langpack-es + - glibc-langpack-pt + - glibc-all-langpacks + state: present + when: ansible_distribution_major_version is version('8', '>=') + + - name: Check if locales need to be generated (RedHat) + shell: localedef --list-archive | grep -a -q '^{{ locale }}$' + register: locale_present + ignore_errors: true + with_items: + - es_ES + - pt_BR + loop_control: + loop_var: locale + + - block: + - name: Reinstall internationalization files + command: yum -y reinstall glibc-common + rescue: + - name: Install internationalization files + yum: + name: glibc-common + state: present + when: locale_present is failed + + - name: Generate locale (RedHat) + command: localedef -f ISO-8859-1 -i {{ item.locale }} {{ item.locale }} + when: item is failed + with_items: '{{ locale_present.results }}' + when: ansible_os_family == 'RedHat' and ansible_distribution != 'Fedora' + +- name: Install glibc langpacks (Fedora >= 24) + package: + name: '{{ item }}' + state: latest + with_items: + - glibc-langpack-es + - glibc-langpack-pt + when: ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('24', '>=') + +- name: start postgresql service + service: name={{ postgresql_service }} state=started + +- name: Pause between start and stop + pause: + seconds: 5 + +- name: Kill all postgres processes + shell: 'pkill -u {{ pg_user }}' + become: true + when: ansible_facts.distribution == 'CentOS' and ansible_facts.distribution_major_version == '8' + ignore_errors: true + register: terminate + +- name: Stop postgresql service + service: name={{ postgresql_service }} state=stopped + when: terminate is not succeeded + +- name: Pause between stop and start + pause: + seconds: 5 + +- name: Start postgresql service + service: name={{ postgresql_service }} state=started + +- name: copy control file for dummy ext + copy: + src: dummy.control + dest: /usr/share/postgresql/{{ pg_ver }}/extension/dummy.control + mode: '0444' + when: ansible_os_family == 'Debian' + +- name: copy version files for dummy ext + copy: + src: '{{ item }}' + dest: /usr/share/postgresql/{{ pg_ver }}/extension/{{ item }} + mode: '0444' + with_items: + - dummy--0.sql + - dummy--1.0.sql + - dummy--2.0.sql + - dummy--3.0.sql + - dummy--3.0-1.sql + - dummy--3.0-foo.sql + - dummy--3.beta.sql + - dummy--3-1.0.sql + - dummy--3-1.0-1.sql + - dummy--3-1.foo.sql + - dummy--v4.sql + - dummy--4.0.sql + when: ansible_os_family == 'Debian' + +- name: add update paths + file: + path: /usr/share/postgresql/{{ pg_ver }}/extension/{{ item }} + mode: '0444' + state: touch + with_items: + - dummy--0--1.0.sql + - dummy--1.0--2.0.sql + - dummy--2.0--3.0.sql + - dummy--3.0--3.0-1.sql + - dummy--3.0-1--3.0-foo.sql + - dummy--3.0-foo--3.beta.sql + - dummy--3.beta--3-1.0.sql + - dummy--3-1.0--3-1.0-1.sql + - dummy--3-1.0-1--3-1.foo.sql + - dummy--3-1.foo--v4.sql + - dummy--v4--4.0.sql + when: ansible_os_family == 'Debian' + +- name: Get PostgreSQL version + become_user: '{{ pg_user }}' + become: true + shell: echo 'SHOW SERVER_VERSION' | psql --tuples-only --no-align --dbname postgres + register: postgres_version_resp + +- name: Print PostgreSQL server version + debug: + msg: '{{ postgres_version_resp.stdout }}' + +- import_tasks: ssl.yml + when: + - ansible_os_family == 'Debian' + - postgres_version_resp.stdout is version('9.4', '>=') diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/tasks/ssl.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/tasks/ssl.yml new file mode 100644 index 000000000..9f53e80ea --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/tasks/ssl.yml @@ -0,0 +1,108 @@ +- name: postgresql SSL - create database + become_user: '{{ pg_user }}' + become: true + postgresql_db: + name: '{{ ssl_db }}' + +- name: postgresql SSL - create role + become_user: '{{ pg_user }}' + become: true + postgresql_user: + name: '{{ ssl_user }}' + role_attr_flags: SUPERUSER + password: '{{ ssl_pass }}' + +- name: postgresql SSL - install openssl + become: true + package: name=openssl state=present + +- name: postgresql SSL - create certs 1 (Make a self-signed server CA) + become_user: root + become: true + shell: openssl req -sha256 -new -x509 -days 365 -nodes -out /etc/server-ca.crt -keyout /etc/server-ca.key -subj "/CN=" + +- name: postgresql SSL - create certs 2 (Generate server CSR) + become_user: root + become: true + shell: openssl req -sha256 -new -nodes -subj "/CN=127.0.0.1" -out /etc/server.csr -keyout /etc/server.key + +- name: postgresql SSL - create certs 3 (Sign a server certificate) + become_user: root + become: true + shell: openssl x509 -req -sha256 -days 365 -in /etc/server.csr -CA /etc/server-ca.crt -CAkey /etc/server-ca.key -CAcreateserial -out /etc/server.crt + +- name: postgresql SSL - create certs 4 (Make a self-signed client CA) + become_user: root + become: true + shell: openssl req -sha256 -new -x509 -days 365 -nodes -out /etc/client-ca.crt -keyout /etc/client-ca.key -subj "/CN=" + +- name: postgresql SSL - create certs 5 (Generate client CSR) + become_user: root + become: true + shell: openssl req -sha256 -new -nodes -subj "/CN={{ ssl_user }}" -out /etc/client.csr -keyout /etc/client.key + +- name: postgresql SSL - create certs 6 (Sign a client certificate) + become_user: root + become: true + shell: openssl x509 -req -sha256 -days 365 -in /etc/client.csr -CA /etc/client-ca.crt -CAkey /etc/client-ca.key -CAcreateserial -out /etc/client.crt + +- name: postgresql SSL - set right permissions to files + become_user: root + become: true + file: + path: '{{ item }}' + mode: '0600' + owner: '{{ pg_user }}' + group: '{{ pg_user }}' + with_items: + - /etc/server.key + - /etc/server.crt + - /etc/server.csr + - /etc/client.csr + - /etc/client.key + - /etc/client-ca.crt + - /etc/client-ca.key + - /etc/server-ca.key + - /etc/server-ca.crt + +- name: postgresql SSL - enable SSL + become_user: '{{ pg_user }}' + become: true + postgresql_set: + login_user: '{{ pg_user }}' + db: postgres + name: ssl + value: true + +- name: postgresql SSL - add ssl_cert_file + become_user: '{{ pg_user }}' + become: true + postgresql_set: + login_user: '{{ pg_user }}' + db: postgres + name: ssl_cert_file + value: /etc/server.crt + +- name: postgresql SSL - add ssl_key_file + become_user: '{{ pg_user }}' + become: true + postgresql_set: + login_user: '{{ pg_user }}' + db: postgres + name: ssl_key_file + value: /etc/server.key + +- name: postgresql SSL - add ssl_ca_file + become_user: '{{ pg_user }}' + become: true + postgresql_set: + login_user: '{{ pg_user }}' + db: postgres + name: ssl_ca_file + value: /etc/client-ca.crt + +- name: postgresql SSL - reload PostgreSQL to enable ssl on + become: true + service: + name: '{{ postgresql_service }}' + state: reloaded diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/Debian-8.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/Debian-8.yml new file mode 100644 index 000000000..932738d39 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/Debian-8.yml @@ -0,0 +1,9 @@ +postgresql_packages: + - "postgresql" + - "postgresql-common" + - "python-psycopg2" + +pg_hba_location: "/etc/postgresql/9.4/main/pg_hba.conf" +pg_dir: "/var/lib/postgresql/9.4/main" +pg_auto_conf: "{{ pg_dir }}/postgresql.auto.conf" +pg_ver: 9.4 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/RedHat-py3.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/RedHat-py3.yml new file mode 100644 index 000000000..72041a3d7 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/RedHat-py3.yml @@ -0,0 +1,9 @@ +postgresql_packages: + - "postgresql-server" + - "python3-psycopg2" + - "bzip2" + - "xz" + +pg_hba_location: "/var/lib/pgsql/data/pg_hba.conf" +pg_dir: "/var/lib/pgsql/data" +pg_auto_conf: "{{ pg_dir }}/postgresql.auto.conf" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/RedHat.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/RedHat.yml new file mode 100644 index 000000000..30720f8fe --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/RedHat.yml @@ -0,0 +1,8 @@ +postgresql_packages: + - "postgresql-server" + - "python-psycopg2" + - "bzip2" + +pg_hba_location: "/var/lib/pgsql/data/pg_hba.conf" +pg_dir: "/var/lib/pgsql/data" +pg_auto_conf: "{{ pg_dir }}/postgresql.auto.conf" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/Ubuntu-20-py3.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/Ubuntu-20-py3.yml new file mode 100644 index 000000000..ff543a385 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/Ubuntu-20-py3.yml @@ -0,0 +1,13 @@ +postgresql_packages: + - "apt-utils" + - "postgresql" + - "postgresql-common" + - "python3-psycopg2" + - "postgresql-client" + +pg_hba_location: "/etc/postgresql/15/main/pg_hba.conf" +pg_dir: "/var/lib/postgresql/15/main" +pg_auto_conf: "{{ pg_dir }}/postgresql.auto.conf" +pg_ver: 15 + +postgis: postgresql-15-postgis-3 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/default-py3.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/default-py3.yml new file mode 100644 index 000000000..3ff3e0de5 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/default-py3.yml @@ -0,0 +1,7 @@ +postgresql_packages: + - "postgresql-server" + - "python3-psycopg2" + +pg_hba_location: "/var/lib/pgsql/data/pg_hba.conf" +pg_dir: "/var/lib/pgsql/data" +pg_auto_conf: "{{ pg_dir }}/postgresql.auto.conf" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/default.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/default.yml new file mode 100644 index 000000000..71f1cd46e --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_db/vars/default.yml @@ -0,0 +1,7 @@ +postgresql_packages: + - "postgresql-server" + - "python-psycopg2" + +pg_hba_location: "/var/lib/pgsql/data/pg_hba.conf" +pg_dir: "/var/lib/pgsql/data" +pg_auto_conf: "{{ pg_dir }}/postgresql.auto.conf" diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/defaults/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/defaults/main.yml new file mode 100644 index 000000000..5ac314c4b --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/defaults/main.yml @@ -0,0 +1,26 @@ +# General: +pg_user: postgres +db_default: postgres + +pg_package_list: +- postgresql +- postgresql-client +- python3-psycopg2 + +packages_to_remove: +- postgresql +- postgresql-client + +# Master specific defaults: +primary_root_dir: '/var/lib/pgsql/primary' +primary_data_dir: '{{ primary_root_dir }}/data' +primary_postgresql_conf: '{{ primary_data_dir }}/postgresql.conf' +primary_pg_hba_conf: '{{ primary_data_dir }}/pg_hba.conf' +primary_port: 5431 + +# Replica specific defaults: +replica_root_dir: '/var/lib/pgsql/replica' +replica_data_dir: '{{ replica_root_dir }}/data' +replica_postgresql_conf: '{{ replica_data_dir }}/postgresql.conf' +replica_pg_hba_conf: '{{ replica_data_dir }}/pg_hba.conf' +replica_port: 5434 diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/handlers/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/handlers/main.yml new file mode 100644 index 000000000..ea230c778 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/handlers/main.yml @@ -0,0 +1,24 @@ +- name: Stop services + become: true + become_user: '{{ pg_user }}' + shell: '{{ pg_ctl }} -D {{ item.datadir }} -o "-p {{ item.port }}" -m immediate stop' + loop: + - { datadir: '{{ primary_data_dir }}', port: '{{ primary_port }}' } + - { datadir: '{{ replica_data_dir }}', port: '{{ replica_port }}' } + listen: stop postgresql + +- name: Remove packages + apt: + name: '{{ packages_to_remove }}' + state: absent + listen: cleanup postgresql + +- name: Remove FS objects + file: + state: absent + path: "{{ item }}" + force: true + loop: + - "{{ primary_root_dir }}" + - "{{ replica_root_dir }}" + listen: cleanup postgresql diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/tasks/main.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/tasks/main.yml new file mode 100644 index 000000000..4c6421a18 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/tasks/main.yml @@ -0,0 +1,13 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# 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) + +# Setup PostgreSQL primary-standby replication into one container: +- import_tasks: setup_postgresql_cluster.yml + when: + - ansible_distribution == 'Ubuntu' + - ansible_distribution_major_version >= '18' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/tasks/setup_postgresql_cluster.yml b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/tasks/setup_postgresql_cluster.yml new file mode 100644 index 000000000..2bff42e78 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/tasks/setup_postgresql_cluster.yml @@ -0,0 +1,149 @@ +- name: Remove preinstalled packages + apt: + name: '{{ packages_to_remove }}' + state: absent + become: true + +- name: Run autoremove + become: true + apt: + autoremove: true + +- name: Configure Ubuntu 20 for PostgreSQL + when: + - ansible_facts['distribution'] == 'Ubuntu' + - ansible_facts['distribution_major_version'] is version('20', 'ge') + block: + - name: Install wget + package: + name: wget + + - name: Add PGDG repository + lineinfile: + create: true + line: "deb http://apt.postgresql.org/pub/repos/apt {{ ansible_facts['distribution_release'] }}-pgdg main" + path: '/etc/apt/sources.list.d/pgdg.list' + state: 'present' + + - name: Add PGDG GPG key + ansible.builtin.apt_key: + state: present + url: https://www.postgresql.org/media/keys/ACCC4CF8.asc + + - name: Update apt cache + apt: + update_cache: true + +- name: Install apt-utils + apt: + name: apt-utils + +- name: Install packages + apt: + name: '{{ pg_package_list }}' + policy_rc_d: 101 # prevent the service from starting + notify: cleanup postgresql + +- name: Delete postgresql related files + file: + state: absent + path: '{{ item }}' + force: true + loop: + - '{{ primary_root_dir }}' + - '{{ replica_root_dir }}' + - /etc/postgresql + - /var/lib/postgresql + +- name: Create dirs needed + file: + state: directory + recurse: true + path: '{{ item }}' + owner: postgres + group: postgres + mode: '0700' + loop: + - '{{ primary_data_dir }}' + - '{{ replica_data_dir }}' + - /var/lib/postgresql + notify: cleanup postgresql + +- name: Find initdb + shell: find /usr/lib -type f -name "initdb" + register: result + +- name: Set path to initdb + set_fact: + initdb: '{{ result.stdout }}' + +- name: Initialize databases + become: true + become_user: '{{ pg_user }}' + shell: '{{ initdb }} --pgdata {{ item }}' + loop: + - '{{ primary_data_dir }}' + - '{{ replica_data_dir }}' + +- name: Copy config templates + template: + src: '{{ item.conf_templ }}' + dest: '{{ item.conf_dest }}' + owner: postgres + group: postgres + force: true + loop: + - conf_templ: primary_postgresql.conf.j2 + conf_dest: '{{ primary_postgresql_conf }}' + - conf_templ: replica_postgresql.conf.j2 + conf_dest: '{{ replica_postgresql_conf }}' + - conf_templ: pg_hba.conf.j2 + conf_dest: '{{ primary_pg_hba_conf }}' + - conf_templ: pg_hba.conf.j2 + conf_dest: '{{ replica_pg_hba_conf }}' + +- name: Find pg_ctl + shell: find /usr/lib -type f -name "pg_ctl" + register: result + +- name: Set path to initdb + set_fact: + pg_ctl: '{{ result.stdout }}' + +- name: Start primary + become: true + become_user: '{{ pg_user }}' + shell: '{{ pg_ctl }} -D {{ primary_data_dir }} -o "-p {{ primary_port }}" -l {{ primary_data_dir }}/primary.log start' + notify: + - stop postgresql + +- name: Start replica + become: true + become_user: '{{ pg_user }}' + shell: '{{ pg_ctl }} -D {{ replica_data_dir }} -o "-p {{ replica_port }}" -l {{ replica_data_dir }}/replica.log start' + +- name: Check connectivity to the primary and get PostgreSQL version + become: true + become_user: '{{ pg_user }}' + postgresql_ping: + db: '{{ db_default }}' + login_user: '{{ pg_user }}' + login_port: '{{ primary_port }}' + register: result + +- name: Check connectivity to the replica and get PostgreSQL version + become: true + become_user: '{{ pg_user }}' + postgresql_ping: + db: '{{ db_default }}' + login_user: '{{ pg_user }}' + login_port: '{{ replica_port }}' + +- name: Define server version + set_fact: + pg_major_version: '{{ result.server_version.major }}' + pg_minor_version: '{{ result.server_version.minor }}' + +- name: Print PostgreSQL version + debug: + msg: PostgreSQL version is {{ pg_major_version }}.{{ pg_minor_version }} diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/templates/pg_hba.conf.j2 b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/templates/pg_hba.conf.j2 new file mode 100644 index 000000000..62e05ffc8 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/templates/pg_hba.conf.j2 @@ -0,0 +1,7 @@ +local all all trust +local replication logical_replication trust +host replication logical_replication 127.0.0.1/32 trust +host replication logical_replication 0.0.0.0/0 trust +local all logical_replication trust +host all logical_replication 127.0.0.1/32 trust +host all logical_replication 0.0.0.0/0 trust diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/templates/primary_postgresql.conf.j2 b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/templates/primary_postgresql.conf.j2 new file mode 100644 index 000000000..545769f35 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/templates/primary_postgresql.conf.j2 @@ -0,0 +1,28 @@ +# Important parameters: +listen_addresses='*' +port = {{ primary_port }} +wal_level = logical +max_wal_senders = 8 +track_commit_timestamp = on +max_replication_slots = 10 + +# Unimportant parameters: +max_connections=10 +shared_buffers=8MB +dynamic_shared_memory_type=posix +log_destination='stderr' +logging_collector=on +log_directory='log' +log_filename='postgresql-%a.log' +log_truncate_on_rotation=on +log_rotation_age=1d +log_rotation_size=0 +log_line_prefix='%m[%p]' +log_timezone='W-SU' +datestyle='iso,mdy' +timezone='W-SU' +lc_messages='en_US.UTF-8' +lc_monetary='en_US.UTF-8' +lc_numeric='en_US.UTF-8' +lc_time='en_US.UTF-8' +default_text_search_config='pg_catalog.english' diff --git a/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/templates/replica_postgresql.conf.j2 b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/templates/replica_postgresql.conf.j2 new file mode 100644 index 000000000..206ab2eb3 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/integration/targets/setup_postgresql_replication/templates/replica_postgresql.conf.j2 @@ -0,0 +1,28 @@ +# Important parameters: +listen_addresses='*' +port = {{ replica_port }} +wal_level = logical +max_wal_senders = 8 +track_commit_timestamp = on +max_replication_slots = 10 + +# Unimportant parameters: +max_connections=10 +shared_buffers=8MB +dynamic_shared_memory_type=posix +log_destination='stderr' +logging_collector=on +log_directory='log' +log_filename='postgresql-%a.log' +log_truncate_on_rotation=on +log_rotation_age=1d +log_rotation_size=0 +log_line_prefix='%m[%p]' +log_timezone='W-SU' +datestyle='iso,mdy' +timezone='W-SU' +lc_messages='en_US.UTF-8' +lc_monetary='en_US.UTF-8' +lc_numeric='en_US.UTF-8' +lc_time='en_US.UTF-8' +default_text_search_config='pg_catalog.english' diff --git a/ansible_collections/community/postgresql/tests/requirements.yml b/ansible_collections/community/postgresql/tests/requirements.yml new file mode 100644 index 000000000..5a2c9c805 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/requirements.yml @@ -0,0 +1,3 @@ +integration_tests_dependencies: +- community.general +unit_tests_dependencies: [] diff --git a/ansible_collections/community/postgresql/tests/sanity/extra/no-unwanted-files.json b/ansible_collections/community/postgresql/tests/sanity/extra/no-unwanted-files.json new file mode 100644 index 000000000..c789a7fd3 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/sanity/extra/no-unwanted-files.json @@ -0,0 +1,7 @@ +{ + "include_symlinks": true, + "prefixes": [ + "plugins/" + ], + "output": "path-message" +} diff --git a/ansible_collections/community/postgresql/tests/sanity/extra/no-unwanted-files.py b/ansible_collections/community/postgresql/tests/sanity/extra/no-unwanted-files.py new file mode 100755 index 000000000..49806f2e2 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/sanity/extra/no-unwanted-files.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +"""Prevent unwanted files from being added to the source tree.""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import sys + + +def main(): + """Main entry point.""" + paths = sys.argv[1:] or sys.stdin.read().splitlines() + + allowed_extensions = ( + '.cs', + '.ps1', + '.psm1', + '.py', + ) + + skip_paths = set([ + ]) + + skip_directories = ( + ) + + for path in paths: + if path in skip_paths: + continue + + if any(path.startswith(skip_directory) for skip_directory in skip_directories): + continue + + ext = os.path.splitext(path)[1] + + if ext not in allowed_extensions: + print('%s: extension must be one of: %s' % (path, ', '.join(allowed_extensions))) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/postgresql/tests/sanity/ignore-2.12.txt b/ansible_collections/community/postgresql/tests/sanity/ignore-2.12.txt new file mode 100644 index 000000000..b9cd1303f --- /dev/null +++ b/ansible_collections/community/postgresql/tests/sanity/ignore-2.12.txt @@ -0,0 +1,5 @@ +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang +plugins/modules/postgresql_db.py use-argspec-type-path +plugins/modules/postgresql_db.py validate-modules:use-run-command-not-popen +plugins/modules/postgresql_tablespace.py validate-modules:mutually_exclusive-unknown diff --git a/ansible_collections/community/postgresql/tests/sanity/ignore-2.13.txt b/ansible_collections/community/postgresql/tests/sanity/ignore-2.13.txt new file mode 100644 index 000000000..b9cd1303f --- /dev/null +++ b/ansible_collections/community/postgresql/tests/sanity/ignore-2.13.txt @@ -0,0 +1,5 @@ +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang +plugins/modules/postgresql_db.py use-argspec-type-path +plugins/modules/postgresql_db.py validate-modules:use-run-command-not-popen +plugins/modules/postgresql_tablespace.py validate-modules:mutually_exclusive-unknown diff --git a/ansible_collections/community/postgresql/tests/sanity/ignore-2.14.txt b/ansible_collections/community/postgresql/tests/sanity/ignore-2.14.txt new file mode 100644 index 000000000..b9cd1303f --- /dev/null +++ b/ansible_collections/community/postgresql/tests/sanity/ignore-2.14.txt @@ -0,0 +1,5 @@ +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang +plugins/modules/postgresql_db.py use-argspec-type-path +plugins/modules/postgresql_db.py validate-modules:use-run-command-not-popen +plugins/modules/postgresql_tablespace.py validate-modules:mutually_exclusive-unknown diff --git a/ansible_collections/community/postgresql/tests/sanity/ignore-2.15.txt b/ansible_collections/community/postgresql/tests/sanity/ignore-2.15.txt new file mode 100644 index 000000000..58b57c247 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/sanity/ignore-2.15.txt @@ -0,0 +1,6 @@ +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang +plugins/modules/postgresql_db.py use-argspec-type-path +plugins/modules/postgresql_db.py validate-modules:use-run-command-not-popen +plugins/modules/postgresql_tablespace.py validate-modules:mutually_exclusive-unknown +plugins/module_utils/version.py pylint:unused-import diff --git a/ansible_collections/community/postgresql/tests/sanity/ignore-2.16.txt b/ansible_collections/community/postgresql/tests/sanity/ignore-2.16.txt new file mode 100644 index 000000000..58b57c247 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/sanity/ignore-2.16.txt @@ -0,0 +1,6 @@ +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang +plugins/modules/postgresql_db.py use-argspec-type-path +plugins/modules/postgresql_db.py validate-modules:use-run-command-not-popen +plugins/modules/postgresql_tablespace.py validate-modules:mutually_exclusive-unknown +plugins/module_utils/version.py pylint:unused-import diff --git a/ansible_collections/community/postgresql/tests/unit/plugins/module_utils/__init__.py b/ansible_collections/community/postgresql/tests/unit/plugins/module_utils/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/community/postgresql/tests/unit/plugins/module_utils/__init__.py diff --git a/ansible_collections/community/postgresql/tests/unit/plugins/module_utils/test_postgres.py b/ansible_collections/community/postgresql/tests/unit/plugins/module_utils/test_postgres.py new file mode 100644 index 000000000..975542446 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/unit/plugins/module_utils/test_postgres.py @@ -0,0 +1,338 @@ +# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +from os import environ + +import pytest + +import ansible_collections.community.postgresql.plugins.module_utils.postgres as pg + + +INPUT_DICT = dict( + session_role=dict(default=''), + login_user=dict(default='postgres'), + login_password=dict(default='test', no_log=True), + login_host=dict(default='test'), + login_unix_socket=dict(default=''), + port=dict(type='int', default=5432, aliases=['login_port']), + ssl_mode=dict( + default='prefer', + choices=['allow', 'disable', 'prefer', 'require', 'verify-ca', 'verify-full'] + ), + ca_cert=dict(aliases=['ssl_rootcert']), + ssl_cert=dict(type='path'), + ssl_key=dict(type='path'), +) + +EXPECTED_DICT = dict( + user=dict(default='postgres'), + password=dict(default='test', no_log=True), + host=dict(default='test'), + port=dict(type='int', default=5432, aliases=['login_port']), + sslmode=dict( + default='prefer', + choices=['allow', 'disable', 'prefer', 'require', 'verify-ca', 'verify-full'] + ), + sslrootcert=dict(aliases=['ssl_rootcert']), + sslcert=dict(type='path'), + sslkey=dict(type='path'), +) + + +class TestPostgresCommonArgSpec(): + + """ + Namespace for testing postgresql_common_arg_spec() function. + """ + + def test_postgres_common_argument_spec(self): + """ + Test for postgresql_common_arg_spec() function. + + The tested function just returns a dictionary with the default + parameters and their values for PostgreSQL modules. + The return and expected dictionaries must be compared. + """ + expected_dict = dict( + login_user=dict(default='postgres', aliases=['login']), + login_password=dict(default='', no_log=True), + login_host=dict(default='', aliases=['host']), + login_unix_socket=dict(default='', aliases=['unix_socket']), + port=dict(type='int', default=5432, aliases=['login_port']), + ssl_mode=dict( + default='prefer', + choices=['allow', 'disable', 'prefer', 'require', 'verify-ca', 'verify-full'] + ), + ca_cert=dict(aliases=['ssl_rootcert']), + ssl_cert=dict(type='path'), + ssl_key=dict(type='path'), + connect_params=dict(default={}, type='dict'), + ) + assert pg.postgres_common_argument_spec() == expected_dict + + # Setting new values for checking environment variables + expected_dict['port']['default'] = 5435 + expected_dict['login_user']['default'] = 'test_user' + + # Setting environment variables + environ['PGUSER'] = 'test_user' + environ['PGPORT'] = '5435' + assert pg.postgres_common_argument_spec() == expected_dict + + +@pytest.fixture +def m_psycopg2(): + """Return mock object for psycopg2 emulation.""" + global Cursor + Cursor = None + + class Cursor(): + def __init__(self): + self.passed_query = None + + def execute(self, query): + self.passed_query = query + + def close(self): + pass + + global DbConnection + DbConnection = None + + class DbConnection(): + def __init__(self): + pass + + def cursor(self, cursor_factory=None): + return Cursor() + + def set_session(self, autocommit=None): + pass + + def set_isolation_level(self, isolevel): + pass + + class Extras(): + def __init__(self): + self.DictCursor = True + + class Extensions(): + def __init__(self): + self.ISOLATION_LEVEL_AUTOCOMMIT = True + + class DummyPsycopg2(): + def __init__(self): + self.__version__ = '2.4.3' + self.extras = Extras() + self.extensions = Extensions() + + def connect(self, host=None, port=None, user=None, + password=None, sslmode=None, sslrootcert=None, connect_params=None): + if user == 'Exception': + raise Exception() + + return DbConnection() + + return DummyPsycopg2() + + +class TestEnsureReqLibs(): + + """ + Namespace for testing ensure_required_libs() function. + + If there is something wrong with libs, the function invokes fail_json() + method of AnsibleModule object passed as an argument called 'module'. + Therefore we must check: + 1. value of err_msg attribute of m_ansible_module mock object. + """ + + @pytest.fixture(scope='class') + def m_ansible_module(self): + """Return an object of dummy AnsibleModule class.""" + class Dummym_ansible_module(): + def __init__(self): + self.params = {'ca_cert': False} + self.err_msg = '' + + def fail_json(self, msg): + self.err_msg = msg + + return Dummym_ansible_module() + + def test_ensure_req_libs_has_not_psycopg2(self, m_ansible_module): + """Test ensure_required_libs() with psycopg2 is None.""" + # HAS_PSYCOPG2 is False by default + pg.ensure_required_libs(m_ansible_module) + assert 'Failed to import the required Python library (psycopg2)' in m_ansible_module.err_msg + + def test_ensure_req_libs_has_psycopg2(self, m_ansible_module, monkeypatch): + """Test ensure_required_libs() with psycopg2 is not None.""" + monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True) + + pg.ensure_required_libs(m_ansible_module) + assert m_ansible_module.err_msg == '' + + def test_ensure_req_libs_ca_cert(self, m_ansible_module, m_psycopg2, monkeypatch): + """ + Test with module.params['ca_cert'], psycopg2 version is suitable. + """ + m_ansible_module.params['ca_cert'] = True + monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True) + monkeypatch.setattr(pg, 'psycopg2', m_psycopg2) + + pg.ensure_required_libs(m_ansible_module) + assert m_ansible_module.err_msg == '' + + def test_ensure_req_libs_ca_cert_low_psycopg2_ver(self, m_ansible_module, m_psycopg2, monkeypatch): + """ + Test with module.params['ca_cert'], psycopg2 version is wrong. + """ + m_ansible_module.params['ca_cert'] = True + monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True) + # Set wrong psycopg2 version number: + psycopg2 = m_psycopg2 + psycopg2.__version__ = '2.4.2' + monkeypatch.setattr(pg, 'psycopg2', psycopg2) + + pg.ensure_required_libs(m_ansible_module) + assert 'psycopg2 must be at least 2.4.3' in m_ansible_module.err_msg + + +@pytest.fixture(scope='class') +def m_ansible_module(): + """Return an object of dummy AnsibleModule class.""" + class DummyAnsibleModule(): + def __init__(self): + + # take default params from argument spec + spec = pg.postgres_common_argument_spec() + params = dict() + for k in spec.keys(): + params[k] = spec[k].get('default') + + self.params = params + self.err_msg = '' + self.warn_msg = '' + + def fail_json(self, msg): + self.err_msg = msg + + def warn(self, msg): + self.warn_msg = msg + + return DummyAnsibleModule() + + +class TestConnectToDb(): + + """ + Namespace for testing connect_to_db() function. + + When some connection errors occure connect_to_db() caught any of them + and invoke fail_json() or warn() methods of AnsibleModule object + depending on the passed parameters. + connect_to_db may return db_connection object or None if errors occured. + Therefore we must check: + 1. Values of err_msg and warn_msg attributes of m_ansible_module mock object. + 2. Types of return objects (db_connection and cursor). + """ + + def test_connect_to_db(self, m_ansible_module, monkeypatch, m_psycopg2): + """Test connect_to_db(), common test.""" + monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True) + monkeypatch.setattr(pg, 'psycopg2', m_psycopg2) + + conn_params = pg.get_conn_params(m_ansible_module, m_ansible_module.params) + db_connection, dummy = pg.connect_to_db(m_ansible_module, conn_params) + cursor = db_connection.cursor() + # if errors, db_connection returned as None: + assert type(db_connection) == DbConnection + assert type(cursor) == Cursor + assert m_ansible_module.err_msg == '' + # The default behaviour, normal in this case: + assert 'Database name has not been passed' in m_ansible_module.warn_msg + + def test_session_role(self, m_ansible_module, monkeypatch, m_psycopg2): + """Test connect_to_db(), switch on session_role.""" + monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True) + monkeypatch.setattr(pg, 'psycopg2', m_psycopg2) + + m_ansible_module.params['session_role'] = 'test_role' + conn_params = pg.get_conn_params(m_ansible_module, m_ansible_module.params) + db_connection, dummy = pg.connect_to_db(m_ansible_module, conn_params) + cursor = db_connection.cursor() + # if errors, db_connection returned as None: + assert type(db_connection) == DbConnection + assert type(cursor) == Cursor + assert m_ansible_module.err_msg == '' + # The default behaviour, normal in this case: + assert 'Database name has not been passed' in m_ansible_module.warn_msg + + def test_fail_on_conn_true(self, m_ansible_module, monkeypatch, m_psycopg2): + """ + Test connect_to_db(), fail_on_conn arg passed as True (the default behavior). + """ + monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True) + monkeypatch.setattr(pg, 'psycopg2', m_psycopg2) + + m_ansible_module.params['login_user'] = 'Exception' # causes Exception + + conn_params = pg.get_conn_params(m_ansible_module, m_ansible_module.params) + db_connection, dummy = pg.connect_to_db(m_ansible_module, conn_params, fail_on_conn=True) + + assert 'unable to connect to database' in m_ansible_module.err_msg + assert db_connection is None + + def test_fail_on_conn_false(self, m_ansible_module, monkeypatch, m_psycopg2): + """ + Test connect_to_db(), fail_on_conn arg passed as False. + """ + monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True) + monkeypatch.setattr(pg, 'psycopg2', m_psycopg2) + + m_ansible_module.params['login_user'] = 'Exception' # causes Exception + + conn_params = pg.get_conn_params(m_ansible_module, m_ansible_module.params) + db_connection, dummy = pg.connect_to_db(m_ansible_module, conn_params, fail_on_conn=False) + + assert m_ansible_module.err_msg == '' + assert 'PostgreSQL server is unavailable' in m_ansible_module.warn_msg + assert db_connection is None + + def test_autocommit_true(self, m_ansible_module, monkeypatch, m_psycopg2): + """ + Test connect_to_db(), autocommit arg passed as True (the default is False). + """ + monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True) + + # case 1: psycopg2.__version >= 2.4.2 (the default in m_psycopg2) + monkeypatch.setattr(pg, 'psycopg2', m_psycopg2) + + conn_params = pg.get_conn_params(m_ansible_module, m_ansible_module.params) + db_connection, dummy = pg.connect_to_db(m_ansible_module, conn_params, autocommit=True) + cursor = db_connection.cursor() + + # if errors, db_connection returned as None: + assert type(db_connection) == DbConnection + assert type(cursor) == Cursor + assert m_ansible_module.err_msg == '' + + +class TestGetConnParams(): + + """Namespace for testing get_conn_params() function.""" + + def test_get_conn_params_def(self, m_ansible_module, m_psycopg2, monkeypatch): + """Test get_conn_params(), warn_db_default kwarg is default.""" + monkeypatch.setattr(pg, 'psycopg2', m_psycopg2) + assert pg.get_conn_params(m_ansible_module, INPUT_DICT) == EXPECTED_DICT + assert m_ansible_module.warn_msg == 'Database name has not been passed, used default database to connect to.' + + def test_get_conn_params_warn_db_def_false(self, m_ansible_module, m_psycopg2, monkeypatch): + """Test get_conn_params(), warn_db_default kwarg is False.""" + monkeypatch.setattr(pg, 'psycopg2', m_psycopg2) + assert pg.get_conn_params(m_ansible_module, INPUT_DICT, warn_db_default=False) == EXPECTED_DICT + assert m_ansible_module.warn_msg == '' diff --git a/ansible_collections/community/postgresql/tests/unit/plugins/module_utils/test_saslprep.py b/ansible_collections/community/postgresql/tests/unit/plugins/module_utils/test_saslprep.py new file mode 100644 index 000000000..62a1704ad --- /dev/null +++ b/ansible_collections/community/postgresql/tests/unit/plugins/module_utils/test_saslprep.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Andrey Tuzhilin <andrei.tuzhilin@gmail.com> +# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.community.postgresql.plugins.module_utils.saslprep import saslprep + + +VALID = [ + (u'', u''), + (u'\u00A0', u' '), + (u'a', u'a'), + (u'й', u'й'), + (u'\u30DE\u30C8\u30EA\u30C3\u30AF\u30B9', u'\u30DE\u30C8\u30EA\u30C3\u30AF\u30B9'), + (u'The\u00ADM\u00AAtr\u2168', u'TheMatrIX'), + (u'I\u00ADX', u'IX'), + (u'user', u'user'), + (u'USER', u'USER'), + (u'\u00AA', u'a'), + (u'\u2168', u'IX'), + (u'\u05BE\u00A0\u05BE', u'\u05BE\u0020\u05BE'), +] + +INVALID = [ + (None, TypeError), + (b'', TypeError), + (u'\u0221', ValueError), + (u'\u0007', ValueError), + (u'\u0627\u0031', ValueError), + (u'\uE0001', ValueError), + (u'\uE0020', ValueError), + (u'\uFFF9', ValueError), + (u'\uFDD0', ValueError), + (u'\u0000', ValueError), + (u'\u06DD', ValueError), + (u'\uFFFFD', ValueError), + (u'\uD800', ValueError), + (u'\u200E', ValueError), + (u'\u05BE\u00AA\u05BE', ValueError), +] + + +@pytest.mark.parametrize('source,target', VALID) +def test_saslprep_conversions(source, target): + assert saslprep(source) == target + + +@pytest.mark.parametrize('source,exception', INVALID) +def test_saslprep_exceptions(source, exception): + with pytest.raises(exception) as ex: + saslprep(source) diff --git a/ansible_collections/community/postgresql/tests/unit/plugins/modules/__init__.py b/ansible_collections/community/postgresql/tests/unit/plugins/modules/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/community/postgresql/tests/unit/plugins/modules/__init__.py diff --git a/ansible_collections/community/postgresql/tests/unit/plugins/modules/test_postgresql_set.py b/ansible_collections/community/postgresql/tests/unit/plugins/modules/test_postgresql_set.py new file mode 100644 index 000000000..a10678202 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/unit/plugins/modules/test_postgresql_set.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru> + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.community.postgresql.plugins.modules.postgresql_set import pretty_to_bytes + + +@pytest.mark.parametrize('input_,expected', [ + ('', ''), + ('test', 'test'), + ('0.1', 0.1), + ('1024', 1024), + ('1024B', 1024), + ('1kB', 1024), + ('100kB', 102400), + ('1MB', 1048576), + ('100MB', 104857600), + ('1GB', 1073741824), + ('10GB', 10737418240), + ('127.0.0.1', '127.0.0.1') +] +) +def test_pretty_to_bytes(input_, expected): + assert pretty_to_bytes(input_) == expected diff --git a/ansible_collections/community/postgresql/tests/utils/constraints.txt b/ansible_collections/community/postgresql/tests/utils/constraints.txt new file mode 100644 index 000000000..ae6000ae1 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/utils/constraints.txt @@ -0,0 +1,52 @@ +coverage >= 4.2, < 5.0.0, != 4.3.2 ; python_version <= '3.7' # features in 4.2+ required, avoid known bug in 4.3.2 on python 2.6, coverage 5.0+ incompatible +coverage >= 4.5.4, < 5.0.0 ; python_version > '3.7' # coverage had a bug in < 4.5.4 that would cause unit tests to hang in Python 3.8, coverage 5.0+ incompatible +cryptography < 2.2 ; python_version < '2.7' # cryptography 2.2 drops support for python 2.6 +deepdiff < 4.0.0 ; python_version < '3' # deepdiff 4.0.0 and later require python 3 +jinja2 < 2.11 ; python_version < '2.7' # jinja2 2.11 and later require python 2.7 or later +urllib3 < 1.24 ; python_version < '2.7' # urllib3 1.24 and later require python 2.7 or later +pywinrm >= 0.3.0 # message encryption support +sphinx < 1.6 ; python_version < '2.7' # sphinx 1.6 and later require python 2.7 or later +sphinx < 1.8 ; python_version >= '2.7' # sphinx 1.8 and later are currently incompatible with rstcheck 3.3 +pygments >= 2.4.0 # Pygments 2.4.0 includes bugfixes for YAML and YAML+Jinja lexers +wheel < 0.30.0 ; python_version < '2.7' # wheel 0.30.0 and later require python 2.7 or later +yamllint != 1.8.0, < 1.14.0 ; python_version < '2.7' # yamllint 1.8.0 and 1.14.0+ require python 2.7+ +pycrypto >= 2.6 # Need features found in 2.6 and greater +ncclient >= 0.5.2 # Need features added in 0.5.2 and greater +idna < 2.6, >= 2.5 # linode requires idna < 2.9, >= 2.5, requests requires idna < 2.6, but cryptography will cause the latest version to be installed instead +paramiko < 2.4.0 ; python_version < '2.7' # paramiko 2.4.0 drops support for python 2.6 +pytest < 3.3.0 ; python_version < '2.7' # pytest 3.3.0 drops support for python 2.6 +pytest < 5.0.0 ; python_version == '2.7' # pytest 5.0.0 and later will no longer support python 2.7 +pytest-forked < 1.0.2 ; python_version < '2.7' # pytest-forked 1.0.2 and later require python 2.7 or later +pytest-forked >= 1.0.2 ; python_version >= '2.7' # pytest-forked before 1.0.2 does not work with pytest 4.2.0+ (which requires python 2.7+) +ntlm-auth >= 1.3.0 # message encryption support using cryptography +requests < 2.20.0 ; python_version < '2.7' # requests 2.20.0 drops support for python 2.6 +requests-ntlm >= 1.1.0 # message encryption support +requests-credssp >= 0.1.0 # message encryption support +voluptuous >= 0.11.0 # Schema recursion via Self +openshift >= 0.6.2, < 0.9.0 # merge_type support +virtualenv < 16.0.0 ; python_version < '2.7' # virtualenv 16.0.0 and later require python 2.7 or later +pathspec < 0.6.0 ; python_version < '2.7' # pathspec 0.6.0 and later require python 2.7 or later +pyopenssl < 18.0.0 ; python_version < '2.7' # pyOpenSSL 18.0.0 and later require python 2.7 or later +pyfmg == 0.6.1 # newer versions do not pass current unit tests +pyyaml < 5.1 ; python_version < '2.7' # pyyaml 5.1 and later require python 2.7 or later +pycparser < 2.19 ; python_version < '2.7' # pycparser 2.19 and later require python 2.7 or later +mock >= 2.0.0 # needed for features backported from Python 3.6 unittest.mock (assert_called, assert_called_once...) +pytest-mock >= 1.4.0 # needed for mock_use_standalone_module pytest option +xmltodict < 0.12.0 ; python_version < '2.7' # xmltodict 0.12.0 and later require python 2.7 or later +lxml < 4.3.0 ; python_version < '2.7' # lxml 4.3.0 and later require python 2.7 or later +pyvmomi < 6.0.0 ; python_version < '2.7' # pyvmomi 6.0.0 and later require python 2.7 or later +pyone == 1.1.9 # newer versions do not pass current integration tests +boto3 < 1.11 ; python_version < '2.7' # boto3 1.11 drops Python 2.6 support +botocore >= 1.10.0, < 1.14 ; python_version < '2.7' # adds support for the following AWS services: secretsmanager, fms, and acm-pca; botocore 1.14 drops Python 2.6 support +botocore >= 1.10.0 ; python_version >= '2.7' # adds support for the following AWS services: secretsmanager, fms, and acm-pca +setuptools < 45 ; python_version <= '2.7' # setuptools 45 and later require python 3.5 or later +cffi >= 1.14.2, != 1.14.3 # Yanked version which older versions of pip will still install: + +# freeze pylint and its requirements for consistent test results +astroid == 2.2.5 +isort == 4.3.15 +lazy-object-proxy == 1.3.1 +mccabe == 0.6.1 +pylint == 2.3.1 +typed-ast == 1.4.0 # 1.4.0 is required to compile on Python 3.8 +wrapt == 1.11.1 diff --git a/ansible_collections/community/postgresql/tests/utils/shippable/aix.sh b/ansible_collections/community/postgresql/tests/utils/shippable/aix.sh new file mode 100755 index 000000000..cd3014cca --- /dev/null +++ b/ansible_collections/community/postgresql/tests/utils/shippable/aix.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +stage="${S:-prod}" +provider="${P:-default}" + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" diff --git a/ansible_collections/community/postgresql/tests/utils/shippable/check_matrix.py b/ansible_collections/community/postgresql/tests/utils/shippable/check_matrix.py new file mode 100755 index 000000000..608db6923 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/utils/shippable/check_matrix.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +"""Verify the currently executing Shippable test matrix matches the one defined in the "shippable.yml" file.""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import datetime +import json +import os +import re +import sys +import time + +try: + from typing import NoReturn +except ImportError: + NoReturn = None + +try: + # noinspection PyCompatibility + from urllib2 import urlopen # pylint: disable=ansible-bad-import-from +except ImportError: + # noinspection PyCompatibility + from urllib.request import urlopen + + +def main(): # type: () -> None + """Main entry point.""" + repo_full_name = os.environ['REPO_FULL_NAME'] + required_repo_full_name = 'ansible-collections/community.postgresql' + + if repo_full_name != required_repo_full_name: + sys.stderr.write('Skipping matrix check on repo "%s" which is not "%s".\n' % (repo_full_name, required_repo_full_name)) + return + + with open('shippable.yml', 'rb') as yaml_file: + yaml = yaml_file.read().decode('utf-8').splitlines() + + defined_matrix = [match.group(1) for match in [re.search(r'^ *- env: T=(.*)$', line) for line in yaml] if match and match.group(1) != 'none'] + + if not defined_matrix: + fail('No matrix entries found in the "shippable.yml" file.', + 'Did you modify the "shippable.yml" file?') + + run_id = os.environ['SHIPPABLE_BUILD_ID'] + sleep = 1 + jobs = [] + + for attempts_remaining in range(4, -1, -1): + try: + jobs = json.loads(urlopen('https://api.shippable.com/jobs?runIds=%s' % run_id).read()) + + if not isinstance(jobs, list): + raise Exception('Shippable run %s data is not a list.' % run_id) + + break + except Exception as ex: + if not attempts_remaining: + fail('Unable to retrieve Shippable run %s matrix.' % run_id, + str(ex)) + + sys.stderr.write('Unable to retrieve Shippable run %s matrix: %s\n' % (run_id, ex)) + sys.stderr.write('Trying again in %d seconds...\n' % sleep) + time.sleep(sleep) + sleep *= 2 + + if len(jobs) != len(defined_matrix): + if len(jobs) == 1: + hint = '\n\nMake sure you do not use the "Rebuild with SSH" option.' + else: + hint = '' + + fail('Shippable run %s has %d jobs instead of the expected %d jobs.' % (run_id, len(jobs), len(defined_matrix)), + 'Try re-running the entire matrix.%s' % hint) + + actual_matrix = dict((job.get('jobNumber'), dict(tuple(line.split('=', 1)) for line in job.get('env', [])).get('T', '')) for job in jobs) + errors = [(job_number, test, actual_matrix.get(job_number)) for job_number, test in enumerate(defined_matrix, 1) if actual_matrix.get(job_number) != test] + + if len(errors): + error_summary = '\n'.join('Job %s expected "%s" but found "%s" instead.' % (job_number, expected, actual) for job_number, expected, actual in errors) + + fail('Shippable run %s has a job matrix mismatch.' % run_id, + 'Try re-running the entire matrix.\n\n%s' % error_summary) + + +def fail(message, output): # type: (str, str) -> NoReturn + # Include a leading newline to improve readability on Shippable "Tests" tab. + # Without this, the first line becomes indented. + output = '\n' + output.strip() + + timestamp = datetime.datetime.utcnow().replace(microsecond=0).isoformat() + + # hack to avoid requiring junit-xml, which isn't pre-installed on Shippable outside our test containers + xml = ''' +<?xml version="1.0" encoding="utf-8"?> +<testsuites disabled="0" errors="1" failures="0" tests="1" time="0.0"> +\t<testsuite disabled="0" errors="1" failures="0" file="None" log="None" name="ansible-test" skipped="0" tests="1" time="0" timestamp="%s" url="None"> +\t\t<testcase classname="timeout" name="timeout"> +\t\t\t<error message="%s" type="error">%s</error> +\t\t</testcase> +\t</testsuite> +</testsuites> +''' % (timestamp, message, output) + + path = 'shippable/testresults/check-matrix.xml' + dir_path = os.path.dirname(path) + + if not os.path.exists(dir_path): + os.makedirs(dir_path) + + with open(path, 'w') as junit_fd: + junit_fd.write(xml.lstrip()) + + sys.stderr.write(message + '\n') + sys.stderr.write(output + '\n') + + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/postgresql/tests/utils/shippable/freebsd.sh b/ansible_collections/community/postgresql/tests/utils/shippable/freebsd.sh new file mode 100755 index 000000000..cd3014cca --- /dev/null +++ b/ansible_collections/community/postgresql/tests/utils/shippable/freebsd.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +stage="${S:-prod}" +provider="${P:-default}" + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" diff --git a/ansible_collections/community/postgresql/tests/utils/shippable/linux.sh b/ansible_collections/community/postgresql/tests/utils/shippable/linux.sh new file mode 100755 index 000000000..9cc2f966c --- /dev/null +++ b/ansible_collections/community/postgresql/tests/utils/shippable/linux.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +image="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --docker "${image}" diff --git a/ansible_collections/community/postgresql/tests/utils/shippable/remote.sh b/ansible_collections/community/postgresql/tests/utils/shippable/remote.sh new file mode 100755 index 000000000..cd3014cca --- /dev/null +++ b/ansible_collections/community/postgresql/tests/utils/shippable/remote.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +stage="${S:-prod}" +provider="${P:-default}" + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" diff --git a/ansible_collections/community/postgresql/tests/utils/shippable/rhel.sh b/ansible_collections/community/postgresql/tests/utils/shippable/rhel.sh new file mode 100755 index 000000000..cd3014cca --- /dev/null +++ b/ansible_collections/community/postgresql/tests/utils/shippable/rhel.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +stage="${S:-prod}" +provider="${P:-default}" + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" diff --git a/ansible_collections/community/postgresql/tests/utils/shippable/sanity.sh b/ansible_collections/community/postgresql/tests/utils/shippable/sanity.sh new file mode 100755 index 000000000..c216220e8 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/utils/shippable/sanity.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +group="${args[1]}" + +if [ "${BASE_BRANCH:-}" ]; then + base_branch="origin/${BASE_BRANCH}" +else + base_branch="" +fi + +if [ "${group}" == "extra" ]; then + # ansible-galaxy -vvv collection install community.internal_test_tools + git clone --single-branch --depth 1 https://github.com/ansible-collections/community.internal_test_tools.git ../internal_test_tools + + ../internal_test_tools/tools/run.py --color + exit +fi + +# shellcheck disable=SC2086 +ansible-test sanity --color -v --junit ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} \ + --docker --base-branch "${base_branch}" \ + --allow-disabled diff --git a/ansible_collections/community/postgresql/tests/utils/shippable/shippable.sh b/ansible_collections/community/postgresql/tests/utils/shippable/shippable.sh new file mode 100755 index 000000000..b181297f9 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/utils/shippable/shippable.sh @@ -0,0 +1,208 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +ansible_version="${args[0]}" +script="${args[1]}" + +function join { + local IFS="$1"; + shift; + echo "$*"; +} + +test="$(join / "${args[@]:1}")" + +docker images ansible/ansible +docker images quay.io/ansible/* +docker ps + +for container in $(docker ps --format '{{.Image}} {{.ID}}' | grep -v -e '^drydock/' -e '^quay.io/ansible/azure-pipelines-test-container:' | sed 's/^.* //'); do + docker rm -f "${container}" || true # ignore errors +done + +docker ps + +if [ -d /home/shippable/cache/ ]; then + ls -la /home/shippable/cache/ +fi + +command -v python +python -V + +function retry +{ + # shellcheck disable=SC2034 + for repetition in 1 2 3; do + set +e + "$@" + result=$? + set -e + if [ ${result} == 0 ]; then + return ${result} + fi + echo "@* -> ${result}" + done + echo "Command '@*' failed 3 times!" + exit 255 +} + +command -v pip +pip --version +pip list --disable-pip-version-check +if [ "${ansible_version}" == "devel" ]; then + retry pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check +else + retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check +fi + +if [ "${SHIPPABLE_BUILD_ID:-}" ]; then + export ANSIBLE_COLLECTIONS_PATHS="${HOME}/.ansible" + SHIPPABLE_RESULT_DIR="$(pwd)/shippable" + TEST_DIR="${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/postgresql" + mkdir -p "${TEST_DIR}" + cp -aT "${SHIPPABLE_BUILD_DIR}" "${TEST_DIR}" + cd "${TEST_DIR}" +else + export ANSIBLE_COLLECTIONS_PATHS="${PWD}/../../../" +fi + +# START: HACK install dependencies for integration tests +if [ "${script}" != "units" ] && [ "${script}" != "sanity" ] && [ "${ansible_version}" != "2.9" ]; then + git clone --depth=1 --single-branch https://github.com/ansible-collections/community.general \ + "${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/general" +fi +# END: HACK + +export PYTHONIOENCODING='utf-8' + +if [ "${JOB_TRIGGERED_BY_NAME:-}" == "nightly-trigger" ]; then + COVERAGE=yes + COMPLETE=yes +fi + +if [ -n "${COVERAGE:-}" ]; then + # on-demand coverage reporting triggered by setting the COVERAGE environment variable to a non-empty value + export COVERAGE="--coverage" +elif [[ "${COMMIT_MESSAGE}" =~ ci_coverage ]]; then + # on-demand coverage reporting triggered by having 'ci_coverage' in the latest commit message + export COVERAGE="--coverage" +else + # on-demand coverage reporting disabled (default behavior, always-on coverage reporting remains enabled) + export COVERAGE="--coverage-check" +fi + +if [ -n "${COMPLETE:-}" ]; then + # disable change detection triggered by setting the COMPLETE environment variable to a non-empty value + export CHANGED="" +elif [[ "${COMMIT_MESSAGE}" =~ ci_complete ]]; then + # disable change detection triggered by having 'ci_complete' in the latest commit message + export CHANGED="" +else + # enable change detection (default behavior) + export CHANGED="--changed" +fi + +if [ "${IS_PULL_REQUEST:-}" == "true" ]; then + # run unstable tests which are targeted by focused changes on PRs + export UNSTABLE="--allow-unstable-changed" +else + # do not run unstable tests outside PRs + export UNSTABLE="" +fi + +# remove empty core/extras module directories from PRs created prior to the repo-merge +find plugins -type d -empty -print -delete + +function cleanup +{ + # for complete on-demand coverage generate a report for all files with no coverage on the "sanity/5" job so we only have one copy + if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ] && [ "${test}" == "sanity/5" ]; then + stub="--stub" + # trigger coverage reporting for stubs even if no other coverage data exists + mkdir -p tests/output/coverage/ + else + stub="" + fi + + if [ -d tests/output/coverage/ ]; then + if find tests/output/coverage/ -mindepth 1 -name '.*' -prune -o -print -quit | grep -q .; then + process_coverage='yes' # process existing coverage files + elif [ "${stub}" ]; then + process_coverage='yes' # process coverage when stubs are enabled + else + process_coverage='' + fi + + if [ "${process_coverage}" ]; then + # use python 3.7 for coverage to avoid running out of memory during coverage xml processing + # only use it for coverage to avoid the additional overhead of setting up a virtual environment for a potential no-op job + virtualenv --python /usr/bin/python3.7 ~/ansible-venv + set +ux + . ~/ansible-venv/bin/activate + set -ux + + # shellcheck disable=SC2086 + ansible-test coverage xml --color -v --requirements --group-by command --group-by version ${stub:+"$stub"} + cp -a tests/output/reports/coverage=*.xml "$SHIPPABLE_RESULT_DIR/codecoverage/" + + if [ "${ansible_version}" != "2.9" ]; then + # analyze and capture code coverage aggregated by integration test target + ansible-test coverage analyze targets generate -v "$SHIPPABLE_RESULT_DIR/testresults/coverage-analyze-targets.json" + fi + + # upload coverage report to codecov.io only when using complete on-demand coverage + if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ]; then + for file in tests/output/reports/coverage=*.xml; do + flags="${file##*/coverage=}" + flags="${flags%-powershell.xml}" + flags="${flags%.xml}" + # remove numbered component from stub files when converting to tags + flags="${flags//stub-[0-9]*/stub}" + flags="${flags//=/,}" + flags="${flags//[^a-zA-Z0-9_,]/_}" + + bash <(curl -s https://ansible-ci-files.s3.us-east-1.amazonaws.com/codecov/codecov.sh) \ + -f "${file}" \ + -F "${flags}" \ + -n "${test}" \ + -t 00c0f9fa-ac1b-43d3-addf-99de803232c1 \ + -X coveragepy \ + -X gcov \ + -X fix \ + -X search \ + -X xcode \ + || echo "Failed to upload code coverage report to codecov.io: ${file}" + done + fi + fi + fi + + if [ -d tests/output/junit/ ]; then + cp -aT tests/output/junit/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi + + if [ -d tests/output/data/ ]; then + cp -a tests/output/data/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi + + if [ -d tests/output/bot/ ]; then + cp -aT tests/output/bot/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi +} + +if [ "${SHIPPABLE_BUILD_ID:-}" ]; then trap cleanup EXIT; fi + +if [[ "${COVERAGE:-}" == "--coverage" ]]; then + timeout=60 +else + timeout=50 +fi + +ansible-test env --dump --show --timeout "${timeout}" --color -v + +if [ "${SHIPPABLE_BUILD_ID:-}" ]; then "tests/utils/shippable/check_matrix.py"; fi +"tests/utils/shippable/${script}.sh" "${test}" diff --git a/ansible_collections/community/postgresql/tests/utils/shippable/timing.py b/ansible_collections/community/postgresql/tests/utils/shippable/timing.py new file mode 100755 index 000000000..fb538271b --- /dev/null +++ b/ansible_collections/community/postgresql/tests/utils/shippable/timing.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3.7 +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import time + +start = time.time() + +sys.stdin.reconfigure(errors='surrogateescape') +sys.stdout.reconfigure(errors='surrogateescape') + +for line in sys.stdin: + seconds = time.time() - start + sys.stdout.write('%02d:%02d %s' % (seconds // 60, seconds % 60, line)) + sys.stdout.flush() diff --git a/ansible_collections/community/postgresql/tests/utils/shippable/timing.sh b/ansible_collections/community/postgresql/tests/utils/shippable/timing.sh new file mode 100755 index 000000000..77e257830 --- /dev/null +++ b/ansible_collections/community/postgresql/tests/utils/shippable/timing.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -o pipefail -eu + +"$@" 2>&1 | "$(dirname "$0")/timing.py" diff --git a/ansible_collections/community/postgresql/tests/utils/shippable/units.sh b/ansible_collections/community/postgresql/tests/utils/shippable/units.sh new file mode 100755 index 000000000..f204dc87e --- /dev/null +++ b/ansible_collections/community/postgresql/tests/utils/shippable/units.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +group="${args[1]}" + +if [[ "${COVERAGE:-}" == "--coverage" ]]; then + timeout=90 +else + timeout=30 +fi + +group1=() + +case "${group}" in + 1) options=("${group1[@]:+${group1[@]}}") ;; +esac + +ansible-test env --timeout "${timeout}" --color -v + +# shellcheck disable=SC2086 +ansible-test units --color -v --docker default ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} \ + "${options[@]:+${options[@]}}" \ |