mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
627 lines
20 KiB
Python
627 lines
20 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# 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)
|
|
|
|
from __future__ import (absolute_import, division, print_function)
|
|
__metaclass__ = type
|
|
|
|
DOCUMENTATION = r'''
|
|
---
|
|
module: postgresql_sequence
|
|
short_description: Create, drop, or alter a PostgreSQL sequence
|
|
description:
|
|
- Allows to create, drop or change the definition of a sequence generator.
|
|
options:
|
|
sequence:
|
|
description:
|
|
- The name of the sequence.
|
|
required: true
|
|
type: str
|
|
aliases:
|
|
- name
|
|
state:
|
|
description:
|
|
- The sequence state.
|
|
- If I(state=absent) other options will be ignored except of I(name) and
|
|
I(schema).
|
|
default: present
|
|
choices: [ absent, present ]
|
|
type: str
|
|
data_type:
|
|
description:
|
|
- Specifies the data type of the sequence. Valid types are bigint, integer,
|
|
and smallint. bigint is the default. The data type determines the default
|
|
minimum and maximum values of the sequence. For more info see the
|
|
documentation
|
|
U(https://www.postgresql.org/docs/current/sql-createsequence.html).
|
|
- Supported from PostgreSQL 10.
|
|
choices: [ bigint, integer, smallint ]
|
|
type: str
|
|
increment:
|
|
description:
|
|
- Increment specifies which value is added to the current sequence value
|
|
to create a new value.
|
|
- A positive value will make an ascending sequence, a negative one a
|
|
descending sequence. The default value is 1.
|
|
type: int
|
|
minvalue:
|
|
description:
|
|
- Minvalue determines the minimum value a sequence can generate. The
|
|
default for an ascending sequence is 1. The default for a descending
|
|
sequence is the minimum value of the data type.
|
|
type: int
|
|
aliases:
|
|
- min
|
|
maxvalue:
|
|
description:
|
|
- Maxvalue determines the maximum value for the sequence. The default for
|
|
an ascending sequence is the maximum
|
|
value of the data type. The default for a descending sequence is -1.
|
|
type: int
|
|
aliases:
|
|
- max
|
|
start:
|
|
description:
|
|
- Start allows the sequence to begin anywhere. The default starting value
|
|
is I(minvalue) for ascending sequences and I(maxvalue) for descending
|
|
ones.
|
|
type: int
|
|
cache:
|
|
description:
|
|
- Cache specifies how many sequence numbers are to be preallocated and
|
|
stored in memory for faster access. The minimum value is 1 (only one
|
|
value can be generated at a time, i.e., no cache), and this is also
|
|
the default.
|
|
type: int
|
|
cycle:
|
|
description:
|
|
- The cycle option allows the sequence to wrap around when the I(maxvalue)
|
|
or I(minvalue) has been reached by an ascending or descending sequence
|
|
respectively. If the limit is reached, the next number generated will be
|
|
the minvalue or maxvalue, respectively.
|
|
- If C(false) (NO CYCLE) is specified, any calls to nextval after the sequence
|
|
has reached its maximum value will return an error. False (NO CYCLE) is
|
|
the default.
|
|
type: bool
|
|
default: no
|
|
cascade:
|
|
description:
|
|
- Automatically drop objects that depend on the sequence, and in turn all
|
|
objects that depend on those objects.
|
|
- Ignored if I(state=present).
|
|
- Only used with I(state=absent).
|
|
type: bool
|
|
default: no
|
|
rename_to:
|
|
description:
|
|
- The new name for the I(sequence).
|
|
- Works only for existing sequences.
|
|
type: str
|
|
owner:
|
|
description:
|
|
- Set the owner for the I(sequence).
|
|
type: str
|
|
schema:
|
|
description:
|
|
- The schema of the I(sequence). This is be used to create and relocate
|
|
a I(sequence) in the given schema.
|
|
default: public
|
|
type: str
|
|
newschema:
|
|
description:
|
|
- The new schema for the I(sequence). Will be used for moving a
|
|
I(sequence) to another I(schema).
|
|
- Works only for existing sequences.
|
|
type: str
|
|
session_role:
|
|
description:
|
|
- Switch to session_role after connecting. The specified I(session_role)
|
|
must be a role that the current I(login_user) is a member of.
|
|
- Permissions checking for SQL commands is carried out as though
|
|
the I(session_role) were the one that had logged in originally.
|
|
type: str
|
|
db:
|
|
description:
|
|
- Name of database to connect to and run queries against.
|
|
type: str
|
|
aliases:
|
|
- database
|
|
- login_db
|
|
trust_input:
|
|
description:
|
|
- If C(no), check whether values of parameters I(sequence), I(schema), I(rename_to),
|
|
I(owner), I(newschema), I(session_role) are potentially dangerous.
|
|
- It makes sense to use C(yes) only when SQL injections via the parameters are possible.
|
|
type: bool
|
|
default: yes
|
|
version_added: '0.2.0'
|
|
notes:
|
|
- If you do not pass db parameter, sequence will be created in the database
|
|
named postgres.
|
|
seealso:
|
|
- module: community.general.postgresql_table
|
|
- module: community.general.postgresql_owner
|
|
- module: community.general.postgresql_privs
|
|
- module: community.general.postgresql_tablespace
|
|
- name: CREATE SEQUENCE reference
|
|
description: Complete reference of the CREATE SEQUENCE command documentation.
|
|
link: https://www.postgresql.org/docs/current/sql-createsequence.html
|
|
- name: ALTER SEQUENCE reference
|
|
description: Complete reference of the ALTER SEQUENCE command documentation.
|
|
link: https://www.postgresql.org/docs/current/sql-altersequence.html
|
|
- name: DROP SEQUENCE reference
|
|
description: Complete reference of the DROP SEQUENCE command documentation.
|
|
link: https://www.postgresql.org/docs/current/sql-dropsequence.html
|
|
author:
|
|
- Tobias Birkefeld (@tcraxs)
|
|
- Thomas O'Donnell (@andytom)
|
|
extends_documentation_fragment:
|
|
- community.general.postgres
|
|
|
|
'''
|
|
|
|
EXAMPLES = r'''
|
|
- name: Create an ascending bigint sequence called foobar in the default
|
|
database
|
|
community.general.postgresql_sequence:
|
|
name: foobar
|
|
|
|
- name: Create an ascending integer sequence called foobar, starting at 101
|
|
community.general.postgresql_sequence:
|
|
name: foobar
|
|
data_type: integer
|
|
start: 101
|
|
|
|
- name: Create an descending sequence called foobar, starting at 101 and
|
|
preallocated 10 sequence numbers in cache
|
|
community.general.postgresql_sequence:
|
|
name: foobar
|
|
increment: -1
|
|
cache: 10
|
|
start: 101
|
|
|
|
- name: Create an ascending sequence called foobar, which cycle between 1 to 10
|
|
community.general.postgresql_sequence:
|
|
name: foobar
|
|
cycle: yes
|
|
min: 1
|
|
max: 10
|
|
|
|
- name: Create an ascending bigint sequence called foobar in the default
|
|
database with owner foobar
|
|
community.general.postgresql_sequence:
|
|
name: foobar
|
|
owner: foobar
|
|
|
|
- name: Rename an existing sequence named foo to bar
|
|
community.general.postgresql_sequence:
|
|
name: foo
|
|
rename_to: bar
|
|
|
|
- name: Change the schema of an existing sequence to foobar
|
|
community.general.postgresql_sequence:
|
|
name: foobar
|
|
newschema: foobar
|
|
|
|
- name: Change the owner of an existing sequence to foobar
|
|
community.general.postgresql_sequence:
|
|
name: foobar
|
|
owner: foobar
|
|
|
|
- name: Drop a sequence called foobar
|
|
community.general.postgresql_sequence:
|
|
name: foobar
|
|
state: absent
|
|
|
|
- name: Drop a sequence called foobar with cascade
|
|
community.general.postgresql_sequence:
|
|
name: foobar
|
|
cascade: yes
|
|
state: absent
|
|
'''
|
|
|
|
RETURN = r'''
|
|
state:
|
|
description: Sequence state at the end of execution.
|
|
returned: always
|
|
type: str
|
|
sample: 'present'
|
|
sequence:
|
|
description: Sequence name.
|
|
returned: always
|
|
type: str
|
|
sample: 'foobar'
|
|
queries:
|
|
description: List of queries that was tried to be executed.
|
|
returned: always
|
|
type: str
|
|
sample: [ "CREATE SEQUENCE \"foo\"" ]
|
|
schema:
|
|
description: Name of the schema of the sequence
|
|
returned: always
|
|
type: str
|
|
sample: 'foo'
|
|
data_type:
|
|
description: Shows the current data type of the sequence.
|
|
returned: always
|
|
type: str
|
|
sample: 'bigint'
|
|
increment:
|
|
description: The value of increment of the sequence. A positive value will
|
|
make an ascending sequence, a negative one a descending
|
|
sequence.
|
|
returned: always
|
|
type: int
|
|
sample: '-1'
|
|
minvalue:
|
|
description: The value of minvalue of the sequence.
|
|
returned: always
|
|
type: int
|
|
sample: '1'
|
|
maxvalue:
|
|
description: The value of maxvalue of the sequence.
|
|
returned: always
|
|
type: int
|
|
sample: '9223372036854775807'
|
|
start:
|
|
description: The value of start of the sequence.
|
|
returned: always
|
|
type: int
|
|
sample: '12'
|
|
cycle:
|
|
description: Shows if the sequence cycle or not.
|
|
returned: always
|
|
type: str
|
|
sample: 'NO'
|
|
owner:
|
|
description: Shows the current owner of the sequence
|
|
after the successful run of the task.
|
|
returned: always
|
|
type: str
|
|
sample: 'postgres'
|
|
newname:
|
|
description: Shows the new sequence name after rename.
|
|
returned: on success
|
|
type: str
|
|
sample: 'barfoo'
|
|
newschema:
|
|
description: Shows the new schema of the sequence after schema change.
|
|
returned: on success
|
|
type: str
|
|
sample: 'foobar'
|
|
'''
|
|
|
|
|
|
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_collections.community.general.plugins.module_utils.database import (
|
|
check_input,
|
|
)
|
|
from ansible_collections.community.general.plugins.module_utils.postgres import (
|
|
connect_to_db,
|
|
exec_sql,
|
|
get_conn_params,
|
|
postgres_common_argument_spec,
|
|
)
|
|
|
|
|
|
class Sequence(object):
|
|
"""Implements behavior of CREATE, ALTER or DROP SEQUENCE PostgreSQL command.
|
|
|
|
Arguments:
|
|
module (AnsibleModule) -- object of AnsibleModule class
|
|
cursor (cursor) -- cursor object of psycopg2 library
|
|
|
|
Attributes:
|
|
module (AnsibleModule) -- object of AnsibleModule class
|
|
cursor (cursor) -- cursor object of psycopg2 library
|
|
changed (bool) -- something was changed after execution or not
|
|
executed_queries (list) -- executed queries
|
|
name (str) -- name of the sequence
|
|
owner (str) -- name of the owner of the sequence
|
|
schema (str) -- name of the schema (default: public)
|
|
data_type (str) -- data type of the sequence
|
|
start_value (int) -- value of the sequence start
|
|
minvalue (int) -- minimum value of the sequence
|
|
maxvalue (int) -- maximum value of the sequence
|
|
increment (int) -- increment value of the sequence
|
|
cycle (bool) -- sequence can cycle or not
|
|
new_name (str) -- name of the renamed sequence
|
|
new_schema (str) -- name of the new schema
|
|
exists (bool) -- sequence exists or not
|
|
"""
|
|
|
|
def __init__(self, module, cursor):
|
|
self.module = module
|
|
self.cursor = cursor
|
|
self.executed_queries = []
|
|
self.name = self.module.params['sequence']
|
|
self.owner = ''
|
|
self.schema = self.module.params['schema']
|
|
self.data_type = ''
|
|
self.start_value = ''
|
|
self.minvalue = ''
|
|
self.maxvalue = ''
|
|
self.increment = ''
|
|
self.cycle = ''
|
|
self.new_name = ''
|
|
self.new_schema = ''
|
|
self.exists = False
|
|
# Collect info
|
|
self.get_info()
|
|
|
|
def get_info(self):
|
|
"""Getter to refresh and get sequence info"""
|
|
query = ("SELECT "
|
|
"s.sequence_schema AS schemaname, "
|
|
"s.sequence_name AS sequencename, "
|
|
"pg_get_userbyid(c.relowner) AS sequenceowner, "
|
|
"s.data_type::regtype AS data_type, "
|
|
"s.start_value AS start_value, "
|
|
"s.minimum_value AS min_value, "
|
|
"s.maximum_value AS max_value, "
|
|
"s.increment AS increment_by, "
|
|
"s.cycle_option AS cycle "
|
|
"FROM information_schema.sequences s "
|
|
"JOIN pg_class c ON c.relname = s.sequence_name "
|
|
"LEFT JOIN pg_namespace n ON n.oid = c.relnamespace "
|
|
"WHERE NOT pg_is_other_temp_schema(n.oid) "
|
|
"AND c.relkind = 'S'::\"char\" "
|
|
"AND sequence_name = %(name)s "
|
|
"AND sequence_schema = %(schema)s")
|
|
|
|
res = exec_sql(self, query,
|
|
query_params={'name': self.name, 'schema': self.schema},
|
|
add_to_executed=False)
|
|
|
|
if not res:
|
|
self.exists = False
|
|
return False
|
|
|
|
if res:
|
|
self.exists = True
|
|
self.schema = res[0]['schemaname']
|
|
self.name = res[0]['sequencename']
|
|
self.owner = res[0]['sequenceowner']
|
|
self.data_type = res[0]['data_type']
|
|
self.start_value = res[0]['start_value']
|
|
self.minvalue = res[0]['min_value']
|
|
self.maxvalue = res[0]['max_value']
|
|
self.increment = res[0]['increment_by']
|
|
self.cycle = res[0]['cycle']
|
|
|
|
def create(self):
|
|
"""Implements CREATE SEQUENCE command behavior."""
|
|
query = ['CREATE SEQUENCE']
|
|
query.append(self.__add_schema())
|
|
|
|
if self.module.params.get('data_type'):
|
|
query.append('AS %s' % self.module.params['data_type'])
|
|
|
|
if self.module.params.get('increment'):
|
|
query.append('INCREMENT BY %s' % self.module.params['increment'])
|
|
|
|
if self.module.params.get('minvalue'):
|
|
query.append('MINVALUE %s' % self.module.params['minvalue'])
|
|
|
|
if self.module.params.get('maxvalue'):
|
|
query.append('MAXVALUE %s' % self.module.params['maxvalue'])
|
|
|
|
if self.module.params.get('start'):
|
|
query.append('START WITH %s' % self.module.params['start'])
|
|
|
|
if self.module.params.get('cache'):
|
|
query.append('CACHE %s' % self.module.params['cache'])
|
|
|
|
if self.module.params.get('cycle'):
|
|
query.append('CYCLE')
|
|
|
|
return exec_sql(self, ' '.join(query), return_bool=True)
|
|
|
|
def drop(self):
|
|
"""Implements DROP SEQUENCE command behavior."""
|
|
query = ['DROP SEQUENCE']
|
|
query.append(self.__add_schema())
|
|
|
|
if self.module.params.get('cascade'):
|
|
query.append('CASCADE')
|
|
|
|
return exec_sql(self, ' '.join(query), return_bool=True)
|
|
|
|
def rename(self):
|
|
"""Implements ALTER SEQUENCE RENAME TO command behavior."""
|
|
query = ['ALTER SEQUENCE']
|
|
query.append(self.__add_schema())
|
|
query.append('RENAME TO "%s"' % self.module.params['rename_to'])
|
|
|
|
return exec_sql(self, ' '.join(query), return_bool=True)
|
|
|
|
def set_owner(self):
|
|
"""Implements ALTER SEQUENCE OWNER TO command behavior."""
|
|
query = ['ALTER SEQUENCE']
|
|
query.append(self.__add_schema())
|
|
query.append('OWNER TO "%s"' % self.module.params['owner'])
|
|
|
|
return exec_sql(self, ' '.join(query), return_bool=True)
|
|
|
|
def set_schema(self):
|
|
"""Implements ALTER SEQUENCE SET SCHEMA command behavior."""
|
|
query = ['ALTER SEQUENCE']
|
|
query.append(self.__add_schema())
|
|
query.append('SET SCHEMA "%s"' % self.module.params['newschema'])
|
|
|
|
return exec_sql(self, ' '.join(query), return_bool=True)
|
|
|
|
def __add_schema(self):
|
|
return '"%s"."%s"' % (self.schema, self.name)
|
|
|
|
|
|
# ===========================================
|
|
# Module execution.
|
|
#
|
|
|
|
def main():
|
|
argument_spec = postgres_common_argument_spec()
|
|
argument_spec.update(
|
|
sequence=dict(type='str', required=True, aliases=['name']),
|
|
state=dict(type='str', default='present', choices=['absent', 'present']),
|
|
data_type=dict(type='str', choices=['bigint', 'integer', 'smallint']),
|
|
increment=dict(type='int'),
|
|
minvalue=dict(type='int', aliases=['min']),
|
|
maxvalue=dict(type='int', aliases=['max']),
|
|
start=dict(type='int'),
|
|
cache=dict(type='int'),
|
|
cycle=dict(type='bool', default=False),
|
|
schema=dict(type='str', default='public'),
|
|
cascade=dict(type='bool', default=False),
|
|
rename_to=dict(type='str'),
|
|
owner=dict(type='str'),
|
|
newschema=dict(type='str'),
|
|
db=dict(type='str', default='', aliases=['login_db', 'database']),
|
|
session_role=dict(type='str'),
|
|
trust_input=dict(type="bool", default=True),
|
|
)
|
|
module = AnsibleModule(
|
|
argument_spec=argument_spec,
|
|
supports_check_mode=True,
|
|
mutually_exclusive=[
|
|
['rename_to', 'data_type'],
|
|
['rename_to', 'increment'],
|
|
['rename_to', 'minvalue'],
|
|
['rename_to', 'maxvalue'],
|
|
['rename_to', 'start'],
|
|
['rename_to', 'cache'],
|
|
['rename_to', 'cycle'],
|
|
['rename_to', 'cascade'],
|
|
['rename_to', 'owner'],
|
|
['rename_to', 'newschema'],
|
|
['cascade', 'data_type'],
|
|
['cascade', 'increment'],
|
|
['cascade', 'minvalue'],
|
|
['cascade', 'maxvalue'],
|
|
['cascade', 'start'],
|
|
['cascade', 'cache'],
|
|
['cascade', 'cycle'],
|
|
['cascade', 'owner'],
|
|
['cascade', 'newschema'],
|
|
]
|
|
)
|
|
|
|
if not module.params["trust_input"]:
|
|
check_input(
|
|
module,
|
|
module.params['sequence'],
|
|
module.params['schema'],
|
|
module.params['rename_to'],
|
|
module.params['owner'],
|
|
module.params['newschema'],
|
|
module.params['session_role'],
|
|
)
|
|
|
|
# Note: we don't need to check mutually exclusive params here, because they are
|
|
# checked automatically by AnsibleModule (mutually_exclusive=[] list above).
|
|
|
|
# Change autocommit to False if check_mode:
|
|
autocommit = not module.check_mode
|
|
# Connect to DB and make cursor object:
|
|
conn_params = get_conn_params(module, module.params)
|
|
db_connection = connect_to_db(module, conn_params, autocommit=autocommit)
|
|
cursor = db_connection.cursor(cursor_factory=DictCursor)
|
|
|
|
##############
|
|
# Create the object and do main job:
|
|
data = Sequence(module, cursor)
|
|
|
|
# Set defaults:
|
|
changed = False
|
|
|
|
# Create new sequence
|
|
if not data.exists and module.params['state'] == 'present':
|
|
if module.params.get('rename_to'):
|
|
module.fail_json(msg="Sequence '%s' does not exist, nothing to rename" % module.params['sequence'])
|
|
if module.params.get('newschema'):
|
|
module.fail_json(msg="Sequence '%s' does not exist, change of schema not possible" % module.params['sequence'])
|
|
|
|
changed = data.create()
|
|
|
|
# Drop non-existing sequence
|
|
elif not data.exists and module.params['state'] == 'absent':
|
|
# Nothing to do
|
|
changed = False
|
|
|
|
# Drop existing sequence
|
|
elif data.exists and module.params['state'] == 'absent':
|
|
changed = data.drop()
|
|
|
|
# Rename sequence
|
|
if data.exists and module.params.get('rename_to'):
|
|
if data.name != module.params['rename_to']:
|
|
changed = data.rename()
|
|
if changed:
|
|
data.new_name = module.params['rename_to']
|
|
|
|
# Refresh information
|
|
if module.params['state'] == 'present':
|
|
data.get_info()
|
|
|
|
# Change owner, schema and settings
|
|
if module.params['state'] == 'present' and data.exists:
|
|
# change owner
|
|
if module.params.get('owner'):
|
|
if data.owner != module.params['owner']:
|
|
changed = data.set_owner()
|
|
|
|
# Set schema
|
|
if module.params.get('newschema'):
|
|
if data.schema != module.params['newschema']:
|
|
changed = data.set_schema()
|
|
if changed:
|
|
data.new_schema = module.params['newschema']
|
|
|
|
# Rollback if it's possible and check_mode:
|
|
if module.check_mode:
|
|
db_connection.rollback()
|
|
else:
|
|
db_connection.commit()
|
|
|
|
cursor.close()
|
|
db_connection.close()
|
|
|
|
# Make return values:
|
|
kw = dict(
|
|
changed=changed,
|
|
state='present',
|
|
sequence=data.name,
|
|
queries=data.executed_queries,
|
|
schema=data.schema,
|
|
data_type=data.data_type,
|
|
increment=data.increment,
|
|
minvalue=data.minvalue,
|
|
maxvalue=data.maxvalue,
|
|
start=data.start_value,
|
|
cycle=data.cycle,
|
|
owner=data.owner,
|
|
)
|
|
|
|
if module.params['state'] == 'present':
|
|
if data.new_name:
|
|
kw['newname'] = data.new_name
|
|
if data.new_schema:
|
|
kw['newschema'] = data.new_schema
|
|
|
|
elif module.params['state'] == 'absent':
|
|
kw['state'] = 'absent'
|
|
|
|
module.exit_json(**kw)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|