* postgresql_idx: added CI tests, misc fixes
* postgresql_idx: fix sanity
... | ... |
@@ -18,9 +18,10 @@ DOCUMENTATION = r''' |
18 | 18 |
module: postgresql_idx |
19 | 19 |
short_description: Create or drop indexes from a PostgreSQL database |
20 | 20 |
description: |
21 |
-- Creates or drops indexes from a remote PostgreSQL database |
|
21 |
+- Create or drop indexes from a PostgreSQL database |
|
22 | 22 |
U(https://www.postgresql.org/docs/current/sql-createindex.html). |
23 |
-version_added: "2.8" |
|
23 |
+version_added: '2.8' |
|
24 |
+ |
|
24 | 25 |
options: |
25 | 26 |
idxname: |
26 | 27 |
description: |
... | ... |
@@ -103,6 +104,8 @@ options: |
103 | 103 |
- List of index columns. |
104 | 104 |
- Mutually exclusive with I(state=absent). |
105 | 105 |
type: list |
106 |
+ aliases: |
|
107 |
+ - column |
|
106 | 108 |
cond: |
107 | 109 |
description: |
108 | 110 |
- Index conditions. |
... | ... |
@@ -118,7 +121,7 @@ options: |
118 | 118 |
concurrent: |
119 | 119 |
description: |
120 | 120 |
- Enable or disable concurrent mode (CREATE / DROP INDEX CONCURRENTLY). |
121 |
- - Mutually exclusive with check mode and I(cascade=yes). |
|
121 |
+ - Mutually exclusive with I(cascade=yes). |
|
122 | 122 |
type: bool |
123 | 123 |
default: yes |
124 | 124 |
tablespace: |
... | ... |
@@ -140,31 +143,31 @@ options: |
140 | 140 |
- Mutually exclusive with I(concurrent=yes) |
141 | 141 |
type: bool |
142 | 142 |
default: no |
143 |
+ |
|
143 | 144 |
notes: |
144 | 145 |
- The default authentication assumes that you are either logging in as or |
145 | 146 |
sudo'ing to the postgres account on the host. |
146 |
-- I(cuncurrent=yes) cannot be used in check mode because |
|
147 |
- "CREATE INDEX CONCURRENTLY" cannot run inside a transaction block. |
|
148 | 147 |
- This module uses psycopg2, a Python PostgreSQL database adapter. You must |
149 | 148 |
ensure that psycopg2 is installed on the host before using this module. |
150 | 149 |
- If the remote host is the PostgreSQL server (which is the default case), then |
151 | 150 |
PostgreSQL must also be installed on the remote host. |
152 | 151 |
- For Ubuntu-based systems, install the postgresql, libpq-dev, and python-psycopg2 packages |
153 | 152 |
on the remote host before using this module. |
154 |
-requirements: [ psycopg2 ] |
|
153 |
+ |
|
154 |
+requirements: |
|
155 |
+- psycopg2 |
|
156 |
+ |
|
155 | 157 |
author: |
156 | 158 |
- Andrew Klychkov (@Andersson007) |
157 | 159 |
''' |
158 | 160 |
|
159 | 161 |
EXAMPLES = r''' |
160 |
-# For create / drop index in check mode use concurrent=no and --check |
|
161 |
- |
|
162 | 162 |
- name: Create btree index if not exists test_idx concurrently covering columns id and name of table products |
163 | 163 |
postgresql_idx: |
164 | 164 |
db: acme |
165 | 165 |
table: products |
166 | 166 |
columns: id,name |
167 |
- idxname: test_idx |
|
167 |
+ name: test_idx |
|
168 | 168 |
|
169 | 169 |
- name: Create btree index test_idx concurrently with tablespace called ssd and storage parameter |
170 | 170 |
postgresql_idx: |
... | ... |
@@ -258,14 +261,15 @@ valid: |
258 | 258 |
|
259 | 259 |
import traceback |
260 | 260 |
|
261 |
+PSYCOPG2_IMP_ERR = None |
|
261 | 262 |
try: |
262 | 263 |
import psycopg2 |
263 | 264 |
HAS_PSYCOPG2 = True |
264 | 265 |
except ImportError: |
265 | 266 |
HAS_PSYCOPG2 = False |
267 |
+ PSYCOPG2_IMP_ERR = traceback.format_exc() |
|
266 | 268 |
|
267 |
-import ansible.module_utils.postgres as pgutils |
|
268 |
-from ansible.module_utils.basic import AnsibleModule |
|
269 |
+from ansible.module_utils.basic import AnsibleModule, missing_required_lib |
|
269 | 270 |
from ansible.module_utils.database import SQLParseError |
270 | 271 |
from ansible.module_utils.postgres import postgres_common_argument_spec |
271 | 272 |
from ansible.module_utils._text import to_native |
... | ... |
@@ -441,7 +445,7 @@ def main(): |
441 | 441 |
concurrent=dict(type='bool', default=True), |
442 | 442 |
table=dict(type='str'), |
443 | 443 |
idxtype=dict(type='str', aliases=['type']), |
444 |
- columns=dict(type='list'), |
|
444 |
+ columns=dict(type='list', aliases=['column']), |
|
445 | 445 |
cond=dict(type='str'), |
446 | 446 |
session_role=dict(type='str'), |
447 | 447 |
tablespace=dict(type='str'), |
... | ... |
@@ -454,6 +458,9 @@ def main(): |
454 | 454 |
supports_check_mode=True, |
455 | 455 |
) |
456 | 456 |
|
457 |
+ if not HAS_PSYCOPG2: |
|
458 |
+ module.fail_json(msg=missing_required_lib('psycopg2'), exception=PSYCOPG2_IMP_ERR) |
|
459 |
+ |
|
457 | 460 |
idxname = module.params["idxname"] |
458 | 461 |
state = module.params["state"] |
459 | 462 |
concurrent = module.params["concurrent"] |
... | ... |
@@ -468,8 +475,8 @@ def main(): |
468 | 468 |
cascade = module.params["cascade"] |
469 | 469 |
schema = module.params["schema"] |
470 | 470 |
|
471 |
- if concurrent and (module.check_mode or cascade): |
|
472 |
- module.fail_json(msg="Cuncurrent mode and check mode/cascade are mutually exclusive") |
|
471 |
+ if concurrent and cascade: |
|
472 |
+ module.fail_json(msg="Cuncurrent mode and cascade parameters are mutually exclusive") |
|
473 | 473 |
|
474 | 474 |
if state == 'present': |
475 | 475 |
if not table: |
... | ... |
@@ -485,9 +492,6 @@ def main(): |
485 | 485 |
if cascade and state != 'absent': |
486 | 486 |
module.fail_json(msg="cascade parameter used only with state=absent") |
487 | 487 |
|
488 |
- if not HAS_PSYCOPG2: |
|
489 |
- module.fail_json(msg="the python psycopg2 module is required") |
|
490 |
- |
|
491 | 488 |
# To use defaults values, keyword arguments must be absent, so |
492 | 489 |
# check which values are empty and don't include in the **kw |
493 | 490 |
# dictionary |
... | ... |
@@ -511,11 +515,6 @@ def main(): |
511 | 511 |
if psycopg2.__version__ < '2.4.3' and sslrootcert is not None: |
512 | 512 |
module.fail_json(msg='psycopg2 must be at least 2.4.3 in order to user the ca_cert parameter') |
513 | 513 |
|
514 |
- if module.check_mode and concurrent: |
|
515 |
- module.fail_json(msg="Cannot concurrently create or drop index %s " |
|
516 |
- "inside the transaction block. The check is possible " |
|
517 |
- "in not concurrent mode only" % idxname) |
|
518 |
- |
|
519 | 514 |
try: |
520 | 515 |
db_connection = psycopg2.connect(**kw) |
521 | 516 |
if concurrent: |
... | ... |
@@ -529,9 +528,9 @@ def main(): |
529 | 529 |
if 'sslrootcert' in e.args[0]: |
530 | 530 |
module.fail_json(msg='Postgresql server must be at least version 8.4 to support sslrootcert') |
531 | 531 |
|
532 |
- module.fail_json(msg="unable to connect to database: %s" % to_native(e)) |
|
532 |
+ module.fail_json(msg="Unable to connect to database: %s" % to_native(e)) |
|
533 | 533 |
except Exception as e: |
534 |
- module.fail_json(msg="unable to connect to database: %s" % to_native(e)) |
|
534 |
+ module.fail_json(msg="Unable to connect to database: %s" % to_native(e)) |
|
535 | 535 |
|
536 | 536 |
if session_role: |
537 | 537 |
try: |
... | ... |
@@ -547,6 +546,27 @@ def main(): |
547 | 547 |
kw = index.get_info() |
548 | 548 |
kw['query'] = '' |
549 | 549 |
|
550 |
+ # |
|
551 |
+ # check_mode start |
|
552 |
+ if module.check_mode: |
|
553 |
+ if state == 'present' and index.exists: |
|
554 |
+ kw['changed'] = False |
|
555 |
+ module.exit_json(**kw) |
|
556 |
+ |
|
557 |
+ elif state == 'present' and not index.exists: |
|
558 |
+ kw['changed'] = True |
|
559 |
+ module.exit_json(**kw) |
|
560 |
+ |
|
561 |
+ elif state == 'absent' and not index.exists: |
|
562 |
+ kw['changed'] = False |
|
563 |
+ module.exit_json(**kw) |
|
564 |
+ |
|
565 |
+ elif state == 'absent' and index.exists: |
|
566 |
+ kw['changed'] = True |
|
567 |
+ module.exit_json(**kw) |
|
568 |
+ # check_mode end |
|
569 |
+ # |
|
570 |
+ |
|
550 | 571 |
if state == "present": |
551 | 572 |
if idxtype and idxtype.upper() not in VALID_IDX_TYPES: |
552 | 573 |
module.fail_json(msg="Index type '%s' of %s is not in valid types" % (idxtype, idxname)) |
... | ... |
@@ -570,7 +590,7 @@ def main(): |
570 | 570 |
kw['state'] = 'absent' |
571 | 571 |
kw['query'] = index.executed_query |
572 | 572 |
|
573 |
- if not module.check_mode and not kw['valid'] and concurrent: |
|
573 |
+ if not kw['valid']: |
|
574 | 574 |
db_connection.rollback() |
575 | 575 |
module.warn("Index %s is invalid! ROLLBACK" % idxname) |
576 | 576 |
|
... | ... |
@@ -59,6 +59,46 @@ |
59 | 59 |
# Do main tests |
60 | 60 |
# |
61 | 61 |
|
62 |
+# Create index in check_mode |
|
63 |
+- name: postgresql_idx - create btree index in check_mode |
|
64 |
+ become_user: "{{ pg_user }}" |
|
65 |
+ become: yes |
|
66 |
+ postgresql_idx: |
|
67 |
+ db: postgres |
|
68 |
+ login_user: "{{ pg_user }}" |
|
69 |
+ table: test_table |
|
70 |
+ columns: id, story |
|
71 |
+ idxname: test0_idx |
|
72 |
+ check_mode: yes |
|
73 |
+ register: result |
|
74 |
+ ignore_errors: yes |
|
75 |
+ |
|
76 |
+- assert: |
|
77 |
+ that: |
|
78 |
+ - result.changed == true |
|
79 |
+ - result.tblname == '' |
|
80 |
+ - result.name == 'test0_idx' |
|
81 |
+ - result.state == 'absent' |
|
82 |
+ - result.valid != '' |
|
83 |
+ - result.tblspace == '' |
|
84 |
+ - result.storage_params == [] |
|
85 |
+ - result.schema == '' |
|
86 |
+ - result.query == '' |
|
87 |
+ |
|
88 |
+# Check that actually nothing changed, rowcount must be 0 |
|
89 |
+- name: postgresql_idx - check nothing changed after the previous step |
|
90 |
+ become_user: "{{ pg_user }}" |
|
91 |
+ become: yes |
|
92 |
+ postgresql_query: |
|
93 |
+ db: postgres |
|
94 |
+ login_user: "{{ pg_user }}" |
|
95 |
+ query: "SELECT 1 FROM pg_indexes WHERE indexname = 'test0_idx'" |
|
96 |
+ register: result |
|
97 |
+ |
|
98 |
+- assert: |
|
99 |
+ that: |
|
100 |
+ - result.rowcount == 0 |
|
101 |
+ |
|
62 | 102 |
# Create btree index if not exists test_idx concurrently covering id and story columns |
63 | 103 |
- name: postgresql_idx - create btree index concurrently |
64 | 104 |
become_user: "{{ pg_user }}" |
... | ... |
@@ -84,6 +124,20 @@ |
84 | 84 |
- result.schema == 'public' |
85 | 85 |
- result.query == 'CREATE INDEX CONCURRENTLY test0_idx ON public.test_table USING BTREE (id, story)' |
86 | 86 |
|
87 |
+# Check that the index exists after the previous step, rowcount must be 1 |
|
88 |
+- name: postgresql_idx - check the index exists after the previous step |
|
89 |
+ become_user: "{{ pg_user }}" |
|
90 |
+ become: yes |
|
91 |
+ postgresql_query: |
|
92 |
+ db: postgres |
|
93 |
+ login_user: "{{ pg_user }}" |
|
94 |
+ query: "SELECT 1 FROM pg_indexes WHERE indexname = 'test0_idx'" |
|
95 |
+ register: result |
|
96 |
+ |
|
97 |
+- assert: |
|
98 |
+ that: |
|
99 |
+ - result.rowcount == 1 |
|
100 |
+ |
|
87 | 101 |
# Check that if index exists that changes nothing |
88 | 102 |
- name: postgresql_idx - try to create existing index again |
89 | 103 |
become_user: "{{ pg_user }}" |
... | ... |
@@ -198,6 +252,47 @@ |
198 | 198 |
- result.schema == 'public' |
199 | 199 |
- result.query == 'CREATE INDEX CONCURRENTLY test1_idx ON public.test_table USING BTREE (id) WHERE id > 1 AND id != 10' |
200 | 200 |
|
201 |
+# Drop index from spacific schema with cascade in check_mode |
|
202 |
+- name: postgresql_idx - drop index from specific schema cascade in check_mode |
|
203 |
+ become_user: "{{ pg_user }}" |
|
204 |
+ become: yes |
|
205 |
+ postgresql_idx: |
|
206 |
+ db: postgres |
|
207 |
+ login_user: "{{ pg_user }}" |
|
208 |
+ schema: foo |
|
209 |
+ name: foo_test_idx |
|
210 |
+ cascade: yes |
|
211 |
+ state: absent |
|
212 |
+ concurrent: no |
|
213 |
+ check_mode: yes |
|
214 |
+ register: result |
|
215 |
+ ignore_errors: yes |
|
216 |
+ |
|
217 |
+- assert: |
|
218 |
+ that: |
|
219 |
+ - result.changed == true |
|
220 |
+ - result.name == 'foo_test_idx' |
|
221 |
+ - result.state == 'present' |
|
222 |
+ - result.schema == 'foo' |
|
223 |
+ - result.query == '' |
|
224 |
+ when: tablespace.rc == 0 |
|
225 |
+ |
|
226 |
+# Check that the index exists after the previous step, rowcount must be 1 |
|
227 |
+- name: postgresql_idx - check the index exists after the previous step |
|
228 |
+ become_user: "{{ pg_user }}" |
|
229 |
+ become: yes |
|
230 |
+ postgresql_query: |
|
231 |
+ db: postgres |
|
232 |
+ login_user: "{{ pg_user }}" |
|
233 |
+ query: "SELECT 1 FROM pg_indexes WHERE indexname = 'foo_test_idx'" |
|
234 |
+ register: result |
|
235 |
+ when: tablespace.rc == 0 |
|
236 |
+ |
|
237 |
+- assert: |
|
238 |
+ that: |
|
239 |
+ - result.rowcount == 1 |
|
240 |
+ when: tablespace.rc == 0 |
|
241 |
+ |
|
201 | 242 |
# Drop index from spacific schema with cascade |
202 | 243 |
- name: postgresql_idx - drop index from specific schema cascade |
203 | 244 |
become_user: "{{ pg_user }}" |
... | ... |
@@ -222,6 +317,22 @@ |
222 | 222 |
- result.query == 'DROP INDEX foo.foo_test_idx CASCADE' |
223 | 223 |
when: tablespace.rc == 0 |
224 | 224 |
|
225 |
+# Check that the index doesn't exist after the previous step, rowcount must be 0 |
|
226 |
+- name: postgresql_idx - check the index doesn't exist after the previous step |
|
227 |
+ become_user: "{{ pg_user }}" |
|
228 |
+ become: yes |
|
229 |
+ postgresql_query: |
|
230 |
+ db: postgres |
|
231 |
+ login_user: "{{ pg_user }}" |
|
232 |
+ query: "SELECT 1 FROM pg_indexes WHERE indexname = 'foo_test_idx'" |
|
233 |
+ register: result |
|
234 |
+ when: tablespace.rc == 0 |
|
235 |
+ |
|
236 |
+- assert: |
|
237 |
+ that: |
|
238 |
+ - result.rowcount == 0 |
|
239 |
+ when: tablespace.rc == 0 |
|
240 |
+ |
|
225 | 241 |
# Try to drop not existing index |
226 | 242 |
- name: postgresql_idx - try to drop not existing index |
227 | 243 |
become_user: "{{ pg_user }}" |