Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

postgresql_db: add the rename value to the state option #107

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- postgresql_db - add the ``rename`` value to the ``state`` option (https://github.com/ansible-collections/community.postgresql/pull/107).
69 changes: 67 additions & 2 deletions plugins/modules/postgresql_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,16 @@
- Supported compression formats for dump and restore include C(.pgc), C(.bz2), C(.gz) and C(.xz).
- Supported formats for dump and restore include C(.sql), C(.tar), and C(.dir) (for the directory format which is supported since collection version 1.4.0).
- "Restore program is selected by target file format: C(.tar), C(.pgc), and C(.dir) are handled by pg_restore, other with pgsql."
- "."
Andersson007 marked this conversation as resolved.
Show resolved Hide resolved
- C(rename) is used to rename the database C(name) to C(target).
- If the database C(name) exists, it will be renamed to C(target).
- If the database C(name) does not exist and the C(target) database exists,
the module will report that nothing has changed.
- If both the databases exist as well as when they have the same value, an error will be raised.
- When I(state=rename), in addition to the C(name) option, the module requires the C(target) option. Other options are ignored.
Supported since collection version 1.4.0.
type: str
choices: [ absent, dump, present, restore ]
choices: [ absent, dump, present, rename, restore ]
default: present
target:
description:
Expand Down Expand Up @@ -207,6 +215,17 @@
community.postgresql.postgresql_db:
name: foo
tablespace: bar

# Rename the database foo to bar.
# If the database foo exists, it will be renamed to bar.
# If the database foo does not exist and the bar database exists,
# the module will report that nothing has changed.
# If both the databases exist, an error will be raised.
- name: Rename the database foo to bar
community.postgresql.postgresql_db:
name: foo
state: rename
target: bar
'''

RETURN = r'''
Expand Down Expand Up @@ -534,6 +553,33 @@ def set_tablespace(cursor, db, tablespace):
cursor.execute(query)
return True


def rename_db(module, cursor, db, target, check_mode=False):
source_db = db_exists(cursor, db)
target_db = db_exists(cursor, target)

if source_db and target_db:
module.fail_json(msg='Both the source and the target databases exist.')

if not source_db and target_db:
# If the source db doesn't exist and
# the target db exists, we assume that
# the desired state has been reached and
# respectively nothing needs to be changed
return False

if not source_db and not target_db:
module.fail_json(msg='The source and the target databases do not exist.')

if source_db and not target_db:
if check_mode:
return True

query = 'ALTER DATABASE "%s" RENAME TO "%s"' % (db, target)
executed_commands.append(query)
cursor.execute(query)
return True

# ===========================================
# Module execution.
#
Expand All @@ -548,7 +594,8 @@ def main():
encoding=dict(type='str', default=''),
lc_collate=dict(type='str', default=''),
lc_ctype=dict(type='str', default=''),
state=dict(type='str', default='present', choices=['absent', 'dump', 'present', 'restore']),
state=dict(type='str', default='present',
choices=['absent', 'dump', 'present', 'rename', 'restore']),
target=dict(type='path', default=''),
target_opts=dict(type='str', default=''),
maintenance_db=dict(type='str', default="postgres"),
Expand Down Expand Up @@ -581,6 +628,16 @@ def main():
dump_extra_args = module.params['dump_extra_args']
trust_input = module.params['trust_input']

if state == 'rename':
if not target:
module.fail_json(msg='The "target" option must be defined when the "rename" option is used.')

if db == target:
module.fail_json(msg='The "name/db" option and the "target" option cannot be the same.')

if maintenance_db == db:
module.fail_json(msg='The "maintenance_db" option and the "name/db" option cannot be the same.')

# Check input
if not trust_input:
# Check input for potentially dangerous elements:
Expand Down Expand Up @@ -645,8 +702,13 @@ def main():
if module.check_mode:
if state == "absent":
changed = db_exists(cursor, db)

elif state == "present":
changed = not db_matches(cursor, db, owner, template, encoding, lc_collate, lc_ctype, conn_limit, tablespace)

elif state == "rename":
changed = rename_db(module, cursor, db, target, check_mode=True)

module.exit_json(changed=changed, db=db, executed_commands=executed_commands)

if state == "absent":
Expand Down Expand Up @@ -677,6 +739,9 @@ def main():
except SQLParseError as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())

elif state == 'rename':
changed = rename_db(module, cursor, db, target)

except NotSupportedError as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
except SystemExit:
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/targets/postgresql_db/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
# 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:
Expand Down
261 changes: 261 additions & 0 deletions tests/integration/targets/postgresql_db/tasks/state_rename.yml
Original file line number Diff line number Diff line change
@@ -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: yes

- 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: yes

- 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: yes

- 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: yes

- 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: yes
check_mode: yes

- 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: yes

- 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: yes

- 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: yes

- 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: yes

- 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