mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
New module postgresql_copy: copy data between a file and a table (#56835)
* New module postgresql_copy * New module postgresql_copy: added tests * New module postgresql_copy: changed tests * New module postgresql_copy: doc format fixes * New module postgresql_copy: fixes * New module postgresql_copy: added upper, PostgreSQL * New module postgresql_copy: fixed description * New module postgresql_copy: added note about superuser * New module postgresql_copy: remove SQLParseError * New module postgresql_copy: fixed opt_need_quotes type * New module postgresql_copy: fixed check_mode * New module postgresql_copy: small fix
This commit is contained in:
parent
0c66a5d3ca
commit
96bf243265
4 changed files with 691 additions and 4 deletions
424
lib/ansible/modules/database/postgresql/postgresql_copy.py
Normal file
424
lib/ansible/modules/database/postgresql/postgresql_copy.py
Normal file
|
@ -0,0 +1,424 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {
|
||||||
|
'metadata_version': '1.1',
|
||||||
|
'supported_by': 'community',
|
||||||
|
'status': ['preview']
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: postgresql_copy
|
||||||
|
short_description: Copy data between a file/program and a PostgreSQL table
|
||||||
|
description:
|
||||||
|
- Copy data between a file/program and a PostgreSQL table U(https://www.postgresql.org/docs/current/sql-copy.html).
|
||||||
|
version_added: '2.9'
|
||||||
|
|
||||||
|
options:
|
||||||
|
copy_to:
|
||||||
|
description:
|
||||||
|
- Copy the contents of a table to a file.
|
||||||
|
- Can also copy the results of a SELECT query.
|
||||||
|
- Mutually exclusive with I(copy_from) and I(dst).
|
||||||
|
type: path
|
||||||
|
aliases: [ to ]
|
||||||
|
copy_from:
|
||||||
|
description:
|
||||||
|
- Copy data from a file to a table (appending the data to whatever is in the table already).
|
||||||
|
- Mutually exclusive with I(copy_to) and I(src).
|
||||||
|
type: path
|
||||||
|
aliases: [ from ]
|
||||||
|
src:
|
||||||
|
description:
|
||||||
|
- Copy data from I(copy_from) to I(src=tablename).
|
||||||
|
- Used with I(copy_to) only.
|
||||||
|
type: str
|
||||||
|
aliases: [ source ]
|
||||||
|
dst:
|
||||||
|
description:
|
||||||
|
- Copy data to I(dst=tablename) from I(copy_from=/path/to/data.file).
|
||||||
|
- Used with I(copy_from) only.
|
||||||
|
type: str
|
||||||
|
aliases: [ destination ]
|
||||||
|
columns:
|
||||||
|
description:
|
||||||
|
- List of column names for the src/dst table to COPY FROM/TO.
|
||||||
|
type: list
|
||||||
|
aliases: [ column ]
|
||||||
|
program:
|
||||||
|
description:
|
||||||
|
- Mark I(src)/I(dst) as a program. Data will be copied to/from a program.
|
||||||
|
- See block Examples and PROGRAM arg description U(https://www.postgresql.org/docs/current/sql-copy.html).
|
||||||
|
type: bool
|
||||||
|
options:
|
||||||
|
description:
|
||||||
|
- Options of COPY command.
|
||||||
|
- See the full list of available options U(https://www.postgresql.org/docs/current/sql-copy.html).
|
||||||
|
type: dict
|
||||||
|
db:
|
||||||
|
description:
|
||||||
|
- Name of database to connect to.
|
||||||
|
type: str
|
||||||
|
aliases: [ login_db ]
|
||||||
|
session_role:
|
||||||
|
description:
|
||||||
|
- Switch to session_role after connecting.
|
||||||
|
The specified session_role must be a role that the current login_user is a member of.
|
||||||
|
- Permissions checking for SQL commands is carried out as though
|
||||||
|
the session_role were the one that had logged in originally.
|
||||||
|
type: str
|
||||||
|
|
||||||
|
notes:
|
||||||
|
- Supports PostgreSQL version 9.4+.
|
||||||
|
- COPY command is only allowed to database superusers.
|
||||||
|
- if I(check_mode=yes), we just check the src/dst table availability
|
||||||
|
and return the COPY query that aclually has not been executed.
|
||||||
|
- If i(check_mode=yes) and the source has been passed as SQL, the module
|
||||||
|
will execute it and rolled the transaction back but pay attention
|
||||||
|
it can affect database performance (e.g., if SQL collects a lot of data).
|
||||||
|
|
||||||
|
author:
|
||||||
|
- Andrew Klychkov (@Andersson007)
|
||||||
|
|
||||||
|
extends_documentation_fragment: postgres
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
- name: Copy text TAB-separated data from file /tmp/data.txt to acme table
|
||||||
|
postgresql_copy:
|
||||||
|
copy_from: /tmp/data.txt
|
||||||
|
dst: acme
|
||||||
|
|
||||||
|
- name: Copy CSV (comma-separated) data from file /tmp/data.csv to columns id, name of table acme
|
||||||
|
postgresql_copy:
|
||||||
|
copy_from: /tmp/data.csv
|
||||||
|
dst: acme
|
||||||
|
columns: id,name
|
||||||
|
options:
|
||||||
|
format: csv
|
||||||
|
|
||||||
|
- name: >
|
||||||
|
Copy text vertical-bar-separated data from file /tmp/data.txt to bar table.
|
||||||
|
The NULL values are specified as N
|
||||||
|
postgresql_copy:
|
||||||
|
copy_from: /tmp/data.csv
|
||||||
|
dst: bar
|
||||||
|
options:
|
||||||
|
delimiter: '|'
|
||||||
|
null: 'N'
|
||||||
|
|
||||||
|
- name: Copy data from acme table to file /tmp/data.txt in text format, TAB-separated
|
||||||
|
postgresql_copy:
|
||||||
|
src: acme
|
||||||
|
copy_to: /tmp/data.txt
|
||||||
|
|
||||||
|
- name: Copy data from SELECT query to/tmp/data.csv in CSV format
|
||||||
|
postgresql_copy:
|
||||||
|
src: 'SELECT * FROM acme'
|
||||||
|
copy_to: /tmp/data.csv
|
||||||
|
options:
|
||||||
|
format: csv
|
||||||
|
|
||||||
|
- name: Copy CSV data from my_table to gzip
|
||||||
|
postgresql_copy:
|
||||||
|
src: my_table
|
||||||
|
copy_to: 'gzip > /tmp/data.csv.gz'
|
||||||
|
program: yes
|
||||||
|
options:
|
||||||
|
format: csv
|
||||||
|
|
||||||
|
- name: >
|
||||||
|
Copy data from columns id, name of table bar to /tmp/data.txt.
|
||||||
|
Output format is text, vertical-bar-separated, NULL as N
|
||||||
|
postgresql_copy:
|
||||||
|
src: bar
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
copy_to: /tmp/data.csv
|
||||||
|
options:
|
||||||
|
delimiter: '|'
|
||||||
|
null: 'N'
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = r'''
|
||||||
|
queries:
|
||||||
|
description: List of executed queries.
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
sample: [ "COPY test_table FROM '/tmp/data_file.txt' (FORMAT csv, DELIMITER ',', NULL 'NULL')" ]
|
||||||
|
src:
|
||||||
|
description: Data source.
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
sample: "mytable"
|
||||||
|
dst:
|
||||||
|
description: Data destination.
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
sample: "/tmp/data.csv"
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
from psycopg2.extras import DictCursor
|
||||||
|
except ImportError:
|
||||||
|
# psycopg2 is checked by connect_to_db()
|
||||||
|
# from ansible.module_utils.postgres
|
||||||
|
pass
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.database import pg_quote_identifier
|
||||||
|
from ansible.module_utils.postgres import connect_to_db, postgres_common_argument_spec
|
||||||
|
from ansible.module_utils._text import to_native
|
||||||
|
from ansible.module_utils.six import iteritems
|
||||||
|
|
||||||
|
|
||||||
|
def exec_sql(obj, query, ddl=False, add_to_executed=True):
|
||||||
|
"""Execute SQL.
|
||||||
|
|
||||||
|
Auxiliary function for PostgreSQL user classes.
|
||||||
|
|
||||||
|
Returns a query result if possible or True/False if ddl=True arg was passed.
|
||||||
|
It necessary for statements that don't return any result (like DDL queries).
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
obj (obj) -- must be an object of a user class.
|
||||||
|
The object must have module (AnsibleModule class object) and
|
||||||
|
cursor (psycopg cursor object) attributes
|
||||||
|
query (str) -- SQL query to execute
|
||||||
|
ddl (bool) -- must return True or False instead of rows (typical for DDL queries)
|
||||||
|
(default False)
|
||||||
|
add_to_executed (bool) -- append the query to obj.executed_queries attribute
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
obj.cursor.execute(query)
|
||||||
|
|
||||||
|
if add_to_executed:
|
||||||
|
obj.executed_queries.append(query)
|
||||||
|
|
||||||
|
if not ddl:
|
||||||
|
res = obj.cursor.fetchall()
|
||||||
|
return res
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
obj.module.fail_json(msg="Cannot execute SQL '%s': %s" % (query, to_native(e)))
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class PgCopyData(object):
|
||||||
|
|
||||||
|
"""Implements behavior of COPY FROM, COPY TO PostgreSQL command.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
module (AnsibleModule) -- object of AnsibleModule class
|
||||||
|
cursor (cursor) -- cursor objec of psycopg2 library
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
module (AnsibleModule) -- object of AnsibleModule class
|
||||||
|
cursor (cursor) -- cursor objec of psycopg2 library
|
||||||
|
changed (bool) -- something was changed after execution or not
|
||||||
|
executed_queries (list) -- executed queries
|
||||||
|
dst (str) -- data destination table (when copy_from)
|
||||||
|
src (str) -- data source table (when copy_to)
|
||||||
|
opt_need_quotes (tuple) -- values of these options must be passed
|
||||||
|
to SQL in quotes
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, module, cursor):
|
||||||
|
self.module = module
|
||||||
|
self.cursor = cursor
|
||||||
|
self.executed_queries = []
|
||||||
|
self.changed = False
|
||||||
|
self.dst = ''
|
||||||
|
self.src = ''
|
||||||
|
self.opt_need_quotes = (
|
||||||
|
'DELIMITER',
|
||||||
|
'NULL',
|
||||||
|
'QUOTE',
|
||||||
|
'ESCAPE',
|
||||||
|
'ENCODING',
|
||||||
|
)
|
||||||
|
|
||||||
|
def copy_from(self):
|
||||||
|
"""Implements COPY FROM command behavior."""
|
||||||
|
|
||||||
|
self.src = self.module.params['copy_from']
|
||||||
|
self.dst = self.module.params['dst']
|
||||||
|
|
||||||
|
query_fragments = ['COPY %s' % pg_quote_identifier(self.dst, 'table')]
|
||||||
|
|
||||||
|
if self.module.params.get('columns'):
|
||||||
|
query_fragments.append('(%s)' % ','.join(self.module.params['columns']))
|
||||||
|
|
||||||
|
query_fragments.append('FROM')
|
||||||
|
|
||||||
|
if self.module.params.get('program'):
|
||||||
|
query_fragments.append('PROGRAM')
|
||||||
|
|
||||||
|
query_fragments.append("'%s'" % self.src)
|
||||||
|
|
||||||
|
if self.module.params.get('options'):
|
||||||
|
query_fragments.append(self.__transform_options())
|
||||||
|
|
||||||
|
# Note: check mode is implemented here:
|
||||||
|
if self.module.check_mode:
|
||||||
|
self.changed = self.__check_table(self.dst)
|
||||||
|
|
||||||
|
if self.changed:
|
||||||
|
self.executed_queries.append(' '.join(query_fragments))
|
||||||
|
else:
|
||||||
|
if exec_sql(self, ' '.join(query_fragments), ddl=True):
|
||||||
|
self.changed = True
|
||||||
|
|
||||||
|
def copy_to(self):
|
||||||
|
"""Implements COPY TO command behavior."""
|
||||||
|
|
||||||
|
self.src = self.module.params['src']
|
||||||
|
self.dst = self.module.params['copy_to']
|
||||||
|
|
||||||
|
if 'SELECT ' in self.src.upper():
|
||||||
|
# If src is SQL SELECT statement:
|
||||||
|
query_fragments = ['COPY (%s)' % self.src]
|
||||||
|
else:
|
||||||
|
# If src is a table:
|
||||||
|
query_fragments = ['COPY %s' % pg_quote_identifier(self.src, 'table')]
|
||||||
|
|
||||||
|
if self.module.params.get('columns'):
|
||||||
|
query_fragments.append('(%s)' % ','.join(self.module.params['columns']))
|
||||||
|
|
||||||
|
query_fragments.append('TO')
|
||||||
|
|
||||||
|
if self.module.params.get('program'):
|
||||||
|
query_fragments.append('PROGRAM')
|
||||||
|
|
||||||
|
query_fragments.append("'%s'" % self.dst)
|
||||||
|
|
||||||
|
if self.module.params.get('options'):
|
||||||
|
query_fragments.append(self.__transform_options())
|
||||||
|
|
||||||
|
# Note: check mode is implemented here:
|
||||||
|
if self.module.check_mode:
|
||||||
|
self.changed = self.__check_table(self.src)
|
||||||
|
|
||||||
|
if self.changed:
|
||||||
|
self.executed_queries.append(' '.join(query_fragments))
|
||||||
|
else:
|
||||||
|
if exec_sql(self, ' '.join(query_fragments), ddl=True):
|
||||||
|
self.changed = True
|
||||||
|
|
||||||
|
def __transform_options(self):
|
||||||
|
"""Transform options dict into a suitable string."""
|
||||||
|
|
||||||
|
for (key, val) in iteritems(self.module.params['options']):
|
||||||
|
if key.upper() in self.opt_need_quotes:
|
||||||
|
self.module.params['options'][key] = "'%s'" % val
|
||||||
|
|
||||||
|
opt = ['%s %s' % (key, val) for (key, val) in iteritems(self.module.params['options'])]
|
||||||
|
return '(%s)' % ', '.join(opt)
|
||||||
|
|
||||||
|
def __check_table(self, table):
|
||||||
|
"""Check table or SQL in transaction mode for check_mode.
|
||||||
|
|
||||||
|
Return True if it is OK.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
table (str) - Table name that needs to be checked.
|
||||||
|
It can be SQL SELECT statement that was passed
|
||||||
|
instead of the table name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if 'SELECT ' in table.upper():
|
||||||
|
# In this case table is actually SQL SELECT statement.
|
||||||
|
# If SQL fails, it's handled by exec_sql():
|
||||||
|
exec_sql(self, table, add_to_executed=False)
|
||||||
|
# If exec_sql was passed, it means all is OK:
|
||||||
|
return True
|
||||||
|
|
||||||
|
exec_sql(self, 'SELECT 1 FROM %s' % pg_quote_identifier(table, 'table'),
|
||||||
|
add_to_executed=False)
|
||||||
|
# If SQL was executed successfully:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Module execution.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = postgres_common_argument_spec()
|
||||||
|
argument_spec.update(
|
||||||
|
copy_to=dict(type='path', aliases=['to']),
|
||||||
|
copy_from=dict(type='path', aliases=['from']),
|
||||||
|
src=dict(type='str', aliases=['source']),
|
||||||
|
dst=dict(type='str', aliases=['destination']),
|
||||||
|
columns=dict(type='list', aliases=['column']),
|
||||||
|
options=dict(type='dict'),
|
||||||
|
program=dict(type='bool'),
|
||||||
|
db=dict(type='str', aliases=['login_db']),
|
||||||
|
session_role=dict(type='str'),
|
||||||
|
)
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=argument_spec,
|
||||||
|
supports_check_mode=True,
|
||||||
|
mutually_exclusive=[
|
||||||
|
['copy_from', 'copy_to'],
|
||||||
|
['copy_from', 'src'],
|
||||||
|
['copy_to', 'dst'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Note: we don't need to check mutually exclusive params here, because they are
|
||||||
|
# checked automatically by AnsibleModule (mutually_exclusive=[] list above).
|
||||||
|
if module.params.get('copy_from') and not module.params.get('dst'):
|
||||||
|
module.fail_json(msg='dst param is necessary with copy_from')
|
||||||
|
|
||||||
|
elif module.params.get('copy_to') and not module.params.get('src'):
|
||||||
|
module.fail_json(msg='src param is necessary with copy_to')
|
||||||
|
|
||||||
|
# Connect to DB and make cursor object:
|
||||||
|
db_connection = connect_to_db(module, autocommit=False)
|
||||||
|
cursor = db_connection.cursor(cursor_factory=DictCursor)
|
||||||
|
|
||||||
|
##############
|
||||||
|
# Create the object and do main job:
|
||||||
|
data = PgCopyData(module, cursor)
|
||||||
|
|
||||||
|
# Note: parameters like dst, src, etc. are got
|
||||||
|
# from module object into data object of PgCopyData class.
|
||||||
|
# Therefore not need to pass args to the methods below.
|
||||||
|
# Note: check mode is implemented inside the methods below
|
||||||
|
# by checking passed module.check_mode arg.
|
||||||
|
if module.params.get('copy_to'):
|
||||||
|
data.copy_to()
|
||||||
|
|
||||||
|
elif module.params.get('copy_from'):
|
||||||
|
data.copy_from()
|
||||||
|
|
||||||
|
# Finish:
|
||||||
|
if module.check_mode:
|
||||||
|
db_connection.rollback()
|
||||||
|
else:
|
||||||
|
db_connection.commit()
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
db_connection.close()
|
||||||
|
|
||||||
|
# Return some values:
|
||||||
|
module.exit_json(
|
||||||
|
changed=data.changed,
|
||||||
|
queries=data.executed_queries,
|
||||||
|
src=data.src,
|
||||||
|
dst=data.dst,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -46,10 +46,14 @@ options:
|
||||||
aliases: [ ssl_rootcert ]
|
aliases: [ ssl_rootcert ]
|
||||||
notes:
|
notes:
|
||||||
- The default authentication assumes that you are either logging in as or sudo'ing to the C(postgres) account on the host.
|
- The default authentication assumes that you are either logging in as or sudo'ing to the C(postgres) account on the host.
|
||||||
- This module uses I(psycopg2), a Python PostgreSQL database adapter. You must ensure that psycopg2 is installed on
|
- To avoid "Peer authentication failed for user postgres" error,
|
||||||
the host before using this module. If the remote host is the PostgreSQL server (which is the default case), then
|
use postgres user as a I(become_user).
|
||||||
PostgreSQL must also be installed on the remote host. For Ubuntu-based systems, install the C(postgresql), C(libpq-dev),
|
- This module uses psycopg2, a Python PostgreSQL database adapter. You must
|
||||||
and C(python-psycopg2) packages on the remote host before using this module.
|
ensure that psycopg2 is installed on the host before using this module.
|
||||||
|
- If the remote host is the PostgreSQL server (which is the default case), then
|
||||||
|
PostgreSQL must also be installed on the remote host.
|
||||||
|
- For Ubuntu-based systems, install the postgresql, libpq-dev, and python-psycopg2 packages
|
||||||
|
on the remote host before using this module.
|
||||||
- The ca_cert parameter requires at least Postgres version 8.4 and I(psycopg2) version 2.4.3.
|
- The ca_cert parameter requires at least Postgres version 8.4 and I(psycopg2) version 2.4.3.
|
||||||
requirements: [ psycopg2 ]
|
requirements: [ psycopg2 ]
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -833,6 +833,10 @@
|
||||||
- include: test_target_role.yml
|
- include: test_target_role.yml
|
||||||
when: postgres_version_resp.stdout is version('9.1', '>=')
|
when: postgres_version_resp.stdout is version('9.1', '>=')
|
||||||
|
|
||||||
|
# Test postgresql_copy module
|
||||||
|
- include: postgresql_copy.yml
|
||||||
|
when: postgres_version_resp.stdout is version('9.4', '>=')
|
||||||
|
|
||||||
# Test postgresql_ext.
|
# Test postgresql_ext.
|
||||||
# pg_extension system view is available from PG 9.1.
|
# pg_extension system view is available from PG 9.1.
|
||||||
# The tests are restricted by Fedora because there will be errors related with
|
# The tests are restricted by Fedora because there will be errors related with
|
||||||
|
|
255
test/integration/targets/postgresql/tasks/postgresql_copy.yml
Normal file
255
test/integration/targets/postgresql/tasks/postgresql_copy.yml
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
# 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: yes
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_copy:
|
||||||
|
<<: *pg_parameters
|
||||||
|
copy_to: '{{ data_file_txt }}'
|
||||||
|
src: '{{ test_table }}'
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.changed == true
|
||||||
|
|
||||||
|
# check that nothing changed after the previous step:
|
||||||
|
- name: postgresql_copy - check that data_file_txt doesn't exist
|
||||||
|
<<: *task_parameters
|
||||||
|
ignore_errors: yes
|
||||||
|
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: yes
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_copy:
|
||||||
|
<<: *pg_parameters
|
||||||
|
copy_from: '{{ data_file_txt }}'
|
||||||
|
dst: '{{ test_table }}'
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.changed == true
|
||||||
|
|
||||||
|
# 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: yes
|
||||||
|
ignore_errors: yes
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_copy:
|
||||||
|
<<: *pg_parameters
|
||||||
|
copy_to: '{{ data_file_txt }}'
|
||||||
|
src: non_existent_table
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.failed == true
|
||||||
|
- result.queries is not defined
|
||||||
|
|
||||||
|
- 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 }}'
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.changed == true
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.changed == true
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.changed == true
|
||||||
|
- 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: yes
|
||||||
|
columns: id, name
|
||||||
|
options:
|
||||||
|
delimiter: '|'
|
||||||
|
when: ansible_distribution != 'FreeBSD'
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.changed == true
|
||||||
|
- 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: yes
|
||||||
|
columns: id, name
|
||||||
|
options:
|
||||||
|
delimiter: ','
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.changed == true
|
||||||
|
- 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 }}'
|
Loading…
Reference in a new issue