mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-05-07 13:52:54 +00:00
Relocating extras into lib/ansible/modules/ after merge
This commit is contained in:
committed by
Matt Clay
parent
c65ba07d2c
commit
011ea55a8f
0
lib/ansible/modules/database/influxdb/__init__.py
Normal file
0
lib/ansible/modules/database/influxdb/__init__.py
Normal file
198
lib/ansible/modules/database/influxdb/influxdb_database.py
Normal file
198
lib/ansible/modules/database/influxdb/influxdb_database.py
Normal file
@@ -0,0 +1,198 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# (c) 2016, Kamil Szczygiel <kamil.szczygiel () intel.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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: influxdb_database
|
||||
short_description: Manage InfluxDB databases
|
||||
description:
|
||||
- Manage InfluxDB databases
|
||||
version_added: 2.1
|
||||
author: "Kamil Szczygiel (@kamsz)"
|
||||
requirements:
|
||||
- "python >= 2.6"
|
||||
- "influxdb >= 0.9"
|
||||
options:
|
||||
hostname:
|
||||
description:
|
||||
- The hostname or IP address on which InfluxDB server is listening
|
||||
required: true
|
||||
username:
|
||||
description:
|
||||
- Username that will be used to authenticate against InfluxDB server
|
||||
default: root
|
||||
required: false
|
||||
password:
|
||||
description:
|
||||
- Password that will be used to authenticate against InfluxDB server
|
||||
default: root
|
||||
required: false
|
||||
port:
|
||||
description:
|
||||
- The port on which InfluxDB server is listening
|
||||
default: 8086
|
||||
required: false
|
||||
database_name:
|
||||
description:
|
||||
- Name of the database that will be created/destroyed
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- Determines if the database should be created or destroyed
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
required: false
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Example influxdb_database command from Ansible Playbooks
|
||||
- name: Create database
|
||||
influxdb_database:
|
||||
hostname: "{{influxdb_ip_address}}"
|
||||
database_name: "{{influxdb_database_name}}"
|
||||
state: present
|
||||
|
||||
- name: Destroy database
|
||||
influxdb_database:
|
||||
hostname: "{{influxdb_ip_address}}"
|
||||
database_name: "{{influxdb_database_name}}"
|
||||
state: absent
|
||||
|
||||
- name: Create database using custom credentials
|
||||
influxdb_database:
|
||||
hostname: "{{influxdb_ip_address}}"
|
||||
username: "{{influxdb_username}}"
|
||||
password: "{{influxdb_password}}"
|
||||
database_name: "{{influxdb_database_name}}"
|
||||
state: present
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
#only defaults
|
||||
'''
|
||||
|
||||
try:
|
||||
import requests.exceptions
|
||||
from influxdb import InfluxDBClient
|
||||
from influxdb import exceptions
|
||||
HAS_INFLUXDB = True
|
||||
except ImportError:
|
||||
HAS_INFLUXDB = False
|
||||
|
||||
|
||||
def influxdb_argument_spec():
|
||||
return dict(
|
||||
hostname=dict(required=True, type='str'),
|
||||
port=dict(default=8086, type='int'),
|
||||
username=dict(default='root', type='str'),
|
||||
password=dict(default='root', type='str', no_log=True),
|
||||
database_name=dict(required=True, type='str')
|
||||
)
|
||||
|
||||
|
||||
def connect_to_influxdb(module):
|
||||
hostname = module.params['hostname']
|
||||
port = module.params['port']
|
||||
username = module.params['username']
|
||||
password = module.params['password']
|
||||
database_name = module.params['database_name']
|
||||
|
||||
client = InfluxDBClient(
|
||||
host=hostname,
|
||||
port=port,
|
||||
username=username,
|
||||
password=password,
|
||||
database=database_name
|
||||
)
|
||||
return client
|
||||
|
||||
|
||||
def find_database(module, client, database_name):
|
||||
database = None
|
||||
|
||||
try:
|
||||
databases = client.get_list_database()
|
||||
for db in databases:
|
||||
if db['name'] == database_name:
|
||||
database = db
|
||||
break
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
module.fail_json(msg=str(e))
|
||||
return database
|
||||
|
||||
|
||||
def create_database(module, client, database_name):
|
||||
if not module.check_mode:
|
||||
try:
|
||||
client.create_database(database_name)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
module.exit_json(changed=True)
|
||||
|
||||
|
||||
def drop_database(module, client, database_name):
|
||||
if not module.check_mode:
|
||||
try:
|
||||
client.drop_database(database_name)
|
||||
except exceptions.InfluxDBClientError as e:
|
||||
module.fail_json(msg=e.content)
|
||||
|
||||
module.exit_json(changed=True)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = influxdb_argument_spec()
|
||||
argument_spec.update(
|
||||
state=dict(default='present', type='str', choices=['present', 'absent'])
|
||||
)
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not HAS_INFLUXDB:
|
||||
module.fail_json(msg='influxdb python package is required for this module')
|
||||
|
||||
state = module.params['state']
|
||||
database_name = module.params['database_name']
|
||||
|
||||
client = connect_to_influxdb(module)
|
||||
database = find_database(module, client, database_name)
|
||||
|
||||
if state == 'present':
|
||||
if database:
|
||||
module.exit_json(changed=False)
|
||||
else:
|
||||
create_database(module, client, database_name)
|
||||
|
||||
if state == 'absent':
|
||||
if database:
|
||||
drop_database(module, client, database_name)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# (c) 2016, Kamil Szczygiel <kamil.szczygiel () intel.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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: influxdb_retention_policy
|
||||
short_description: Manage InfluxDB retention policies
|
||||
description:
|
||||
- Manage InfluxDB retention policies
|
||||
version_added: 2.1
|
||||
author: "Kamil Szczygiel (@kamsz)"
|
||||
requirements:
|
||||
- "python >= 2.6"
|
||||
- "influxdb >= 0.9"
|
||||
options:
|
||||
hostname:
|
||||
description:
|
||||
- The hostname or IP address on which InfluxDB server is listening
|
||||
required: true
|
||||
username:
|
||||
description:
|
||||
- Username that will be used to authenticate against InfluxDB server
|
||||
default: root
|
||||
required: false
|
||||
password:
|
||||
description:
|
||||
- Password that will be used to authenticate against InfluxDB server
|
||||
default: root
|
||||
required: false
|
||||
port:
|
||||
description:
|
||||
- The port on which InfluxDB server is listening
|
||||
default: 8086
|
||||
required: false
|
||||
database_name:
|
||||
description:
|
||||
- Name of the database where retention policy will be created
|
||||
required: true
|
||||
policy_name:
|
||||
description:
|
||||
- Name of the retention policy
|
||||
required: true
|
||||
duration:
|
||||
description:
|
||||
- Determines how long InfluxDB should keep the data
|
||||
required: true
|
||||
replication:
|
||||
description:
|
||||
- Determines how many independent copies of each point are stored in the cluster
|
||||
required: true
|
||||
default:
|
||||
description:
|
||||
- Sets the retention policy as default retention policy
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Example influxdb_retention_policy command from Ansible Playbooks
|
||||
- name: create 1 hour retention policy
|
||||
influxdb_retention_policy:
|
||||
hostname: "{{influxdb_ip_address}}"
|
||||
database_name: "{{influxdb_database_name}}"
|
||||
policy_name: test
|
||||
duration: 1h
|
||||
replication: 1
|
||||
|
||||
- name: create 1 day retention policy
|
||||
influxdb_retention_policy:
|
||||
hostname: "{{influxdb_ip_address}}"
|
||||
database_name: "{{influxdb_database_name}}"
|
||||
policy_name: test
|
||||
duration: 1d
|
||||
replication: 1
|
||||
|
||||
- name: create 1 week retention policy
|
||||
influxdb_retention_policy:
|
||||
hostname: "{{influxdb_ip_address}}"
|
||||
database_name: "{{influxdb_database_name}}"
|
||||
policy_name: test
|
||||
duration: 1w
|
||||
replication: 1
|
||||
|
||||
- name: create infinite retention policy
|
||||
influxdb_retention_policy:
|
||||
hostname: "{{influxdb_ip_address}}"
|
||||
database_name: "{{influxdb_database_name}}"
|
||||
policy_name: test
|
||||
duration: INF
|
||||
replication: 1
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
#only defaults
|
||||
'''
|
||||
|
||||
import re
|
||||
try:
|
||||
import requests.exceptions
|
||||
from influxdb import InfluxDBClient
|
||||
from influxdb import exceptions
|
||||
HAS_INFLUXDB = True
|
||||
except ImportError:
|
||||
HAS_INFLUXDB = False
|
||||
|
||||
|
||||
def influxdb_argument_spec():
|
||||
return dict(
|
||||
hostname=dict(required=True, type='str'),
|
||||
port=dict(default=8086, type='int'),
|
||||
username=dict(default='root', type='str'),
|
||||
password=dict(default='root', type='str', no_log=True),
|
||||
database_name=dict(required=True, type='str')
|
||||
)
|
||||
|
||||
|
||||
def connect_to_influxdb(module):
|
||||
hostname = module.params['hostname']
|
||||
port = module.params['port']
|
||||
username = module.params['username']
|
||||
password = module.params['password']
|
||||
database_name = module.params['database_name']
|
||||
|
||||
client = InfluxDBClient(
|
||||
host=hostname,
|
||||
port=port,
|
||||
username=username,
|
||||
password=password,
|
||||
database=database_name
|
||||
)
|
||||
return client
|
||||
|
||||
|
||||
def find_retention_policy(module, client):
|
||||
database_name = module.params['database_name']
|
||||
policy_name = module.params['policy_name']
|
||||
retention_policy = None
|
||||
|
||||
try:
|
||||
retention_policies = client.get_list_retention_policies(database=database_name)
|
||||
for policy in retention_policies:
|
||||
if policy['name'] == policy_name:
|
||||
retention_policy = policy
|
||||
break
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
module.fail_json(msg=str(e))
|
||||
return retention_policy
|
||||
|
||||
|
||||
def create_retention_policy(module, client):
|
||||
database_name = module.params['database_name']
|
||||
policy_name = module.params['policy_name']
|
||||
duration = module.params['duration']
|
||||
replication = module.params['replication']
|
||||
default = module.params['default']
|
||||
|
||||
if not module.check_mode:
|
||||
try:
|
||||
client.create_retention_policy(policy_name, duration, replication, database_name, default)
|
||||
except exceptions.InfluxDBClientError as e:
|
||||
module.fail_json(msg=e.content)
|
||||
module.exit_json(changed=True)
|
||||
|
||||
|
||||
def alter_retention_policy(module, client, retention_policy):
|
||||
database_name = module.params['database_name']
|
||||
policy_name = module.params['policy_name']
|
||||
duration = module.params['duration']
|
||||
replication = module.params['replication']
|
||||
default = module.params['default']
|
||||
duration_regexp = re.compile('(\d+)([hdw]{1})|(^INF$){1}')
|
||||
changed = False
|
||||
|
||||
duration_lookup = duration_regexp.search(duration)
|
||||
|
||||
if duration_lookup.group(2) == 'h':
|
||||
influxdb_duration_format = '%s0m0s' % duration
|
||||
elif duration_lookup.group(2) == 'd':
|
||||
influxdb_duration_format = '%sh0m0s' % (int(duration_lookup.group(1)) * 24)
|
||||
elif duration_lookup.group(2) == 'w':
|
||||
influxdb_duration_format = '%sh0m0s' % (int(duration_lookup.group(1)) * 24 * 7)
|
||||
elif duration == 'INF':
|
||||
influxdb_duration_format = '0'
|
||||
|
||||
if not retention_policy['duration'] == influxdb_duration_format or not retention_policy['replicaN'] == int(replication) or not retention_policy['default'] == default:
|
||||
if not module.check_mode:
|
||||
try:
|
||||
client.alter_retention_policy(policy_name, database_name, duration, replication, default)
|
||||
except exceptions.InfluxDBClientError as e:
|
||||
module.fail_json(msg=e.content)
|
||||
changed = True
|
||||
module.exit_json(changed=changed)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = influxdb_argument_spec()
|
||||
argument_spec.update(
|
||||
policy_name=dict(required=True, type='str'),
|
||||
duration=dict(required=True, type='str'),
|
||||
replication=dict(required=True, type='int'),
|
||||
default=dict(default=False, type='bool')
|
||||
)
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not HAS_INFLUXDB:
|
||||
module.fail_json(msg='influxdb python package is required for this module')
|
||||
|
||||
client = connect_to_influxdb(module)
|
||||
retention_policy = find_retention_policy(module, client)
|
||||
|
||||
if retention_policy:
|
||||
alter_retention_policy(module, client, retention_policy)
|
||||
else:
|
||||
create_retention_policy(module, client)
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
0
lib/ansible/modules/database/misc/__init__.py
Normal file
0
lib/ansible/modules/database/misc/__init__.py
Normal file
239
lib/ansible/modules/database/misc/mongodb_parameter.py
Normal file
239
lib/ansible/modules/database/misc/mongodb_parameter.py
Normal file
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
(c) 2016, Loic Blot <loic.blot@unix-experience.fr>
|
||||
Sponsored by Infopro Digital. http://www.infopro-digital.com/
|
||||
Sponsored by E.T.A.I. http://www.etai.fr/
|
||||
|
||||
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/>.
|
||||
"""
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: mongodb_parameter
|
||||
short_description: Change an administrative parameter on a MongoDB server.
|
||||
description:
|
||||
- Change an administrative parameter on a MongoDB server.
|
||||
version_added: "2.1"
|
||||
options:
|
||||
login_user:
|
||||
description:
|
||||
- The username used to authenticate with
|
||||
required: false
|
||||
default: null
|
||||
login_password:
|
||||
description:
|
||||
- The password used to authenticate with
|
||||
required: false
|
||||
default: null
|
||||
login_host:
|
||||
description:
|
||||
- The host running the database
|
||||
required: false
|
||||
default: localhost
|
||||
login_port:
|
||||
description:
|
||||
- The port to connect to
|
||||
required: false
|
||||
default: 27017
|
||||
login_database:
|
||||
description:
|
||||
- The database where login credentials are stored
|
||||
required: false
|
||||
default: null
|
||||
replica_set:
|
||||
description:
|
||||
- Replica set to connect to (automatically connects to primary for writes)
|
||||
required: false
|
||||
default: null
|
||||
database:
|
||||
description:
|
||||
- The name of the database to add/remove the user from
|
||||
required: true
|
||||
ssl:
|
||||
description:
|
||||
- Whether to use an SSL connection when connecting to the database
|
||||
required: false
|
||||
default: false
|
||||
param:
|
||||
description:
|
||||
- MongoDB administrative parameter to modify
|
||||
required: true
|
||||
value:
|
||||
description:
|
||||
- MongoDB administrative parameter value to set
|
||||
required: true
|
||||
param_type:
|
||||
description:
|
||||
- Define the parameter value (str, int)
|
||||
required: false
|
||||
default: str
|
||||
|
||||
notes:
|
||||
- Requires the pymongo Python package on the remote host, version 2.4.2+. This
|
||||
can be installed using pip or the OS package manager. @see http://api.mongodb.org/python/current/installation.html
|
||||
requirements: [ "pymongo" ]
|
||||
author: "Loic Blot (@nerzhul)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Set MongoDB syncdelay to 60 (this is an int)
|
||||
- mongodb_parameter:
|
||||
param: syncdelay
|
||||
value: 60
|
||||
param_type: int
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
before:
|
||||
description: value before modification
|
||||
returned: success
|
||||
type: string
|
||||
after:
|
||||
description: value after modification
|
||||
returned: success
|
||||
type: string
|
||||
'''
|
||||
|
||||
import ConfigParser
|
||||
|
||||
try:
|
||||
from pymongo.errors import ConnectionFailure
|
||||
from pymongo.errors import OperationFailure
|
||||
from pymongo import version as PyMongoVersion
|
||||
from pymongo import MongoClient
|
||||
except ImportError:
|
||||
try: # for older PyMongo 2.2
|
||||
from pymongo import Connection as MongoClient
|
||||
except ImportError:
|
||||
pymongo_found = False
|
||||
else:
|
||||
pymongo_found = True
|
||||
else:
|
||||
pymongo_found = True
|
||||
|
||||
|
||||
# =========================================
|
||||
# MongoDB module specific support methods.
|
||||
#
|
||||
|
||||
def load_mongocnf():
|
||||
config = ConfigParser.RawConfigParser()
|
||||
mongocnf = os.path.expanduser('~/.mongodb.cnf')
|
||||
|
||||
try:
|
||||
config.readfp(open(mongocnf))
|
||||
creds = dict(
|
||||
user=config.get('client', 'user'),
|
||||
password=config.get('client', 'pass')
|
||||
)
|
||||
except (ConfigParser.NoOptionError, IOError):
|
||||
return False
|
||||
|
||||
return creds
|
||||
|
||||
|
||||
# =========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
login_user=dict(default=None),
|
||||
login_password=dict(default=None, no_log=True),
|
||||
login_host=dict(default='localhost'),
|
||||
login_port=dict(default=27017, type='int'),
|
||||
login_database=dict(default=None),
|
||||
replica_set=dict(default=None),
|
||||
param=dict(default=None, required=True),
|
||||
value=dict(default=None, required=True),
|
||||
param_type=dict(default="str", choices=['str', 'int']),
|
||||
ssl=dict(default=False, type='bool'),
|
||||
)
|
||||
)
|
||||
|
||||
if not pymongo_found:
|
||||
module.fail_json(msg='the python pymongo module is required')
|
||||
|
||||
login_user = module.params['login_user']
|
||||
login_password = module.params['login_password']
|
||||
login_host = module.params['login_host']
|
||||
login_port = module.params['login_port']
|
||||
login_database = module.params['login_database']
|
||||
|
||||
replica_set = module.params['replica_set']
|
||||
ssl = module.params['ssl']
|
||||
|
||||
param = module.params['param']
|
||||
param_type = module.params['param_type']
|
||||
value = module.params['value']
|
||||
|
||||
# Verify parameter is coherent with specified type
|
||||
try:
|
||||
if param_type == 'int':
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="value '%s' is not %s" % (value, param_type))
|
||||
|
||||
try:
|
||||
if replica_set:
|
||||
client = MongoClient(login_host, int(login_port), replicaset=replica_set, ssl=ssl)
|
||||
else:
|
||||
client = MongoClient(login_host, int(login_port), ssl=ssl)
|
||||
|
||||
if login_user is None and login_password is None:
|
||||
mongocnf_creds = load_mongocnf()
|
||||
if mongocnf_creds is not False:
|
||||
login_user = mongocnf_creds['user']
|
||||
login_password = mongocnf_creds['password']
|
||||
elif login_password is None or login_user is None:
|
||||
module.fail_json(msg='when supplying login arguments, both login_user and login_password must be provided')
|
||||
|
||||
if login_user is not None and login_password is not None:
|
||||
client.admin.authenticate(login_user, login_password, source=login_database)
|
||||
|
||||
except ConnectionFailure:
|
||||
e = get_exception()
|
||||
module.fail_json(msg='unable to connect to database: %s' % str(e))
|
||||
|
||||
db = client.admin
|
||||
|
||||
try:
|
||||
after_value = db.command("setParameter", **{param: int(value)})
|
||||
except OperationFailure:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="unable to change parameter: %s" % str(e))
|
||||
|
||||
if "was" not in after_value:
|
||||
module.exit_json(changed=True, msg="Unable to determine old value, assume it changed.")
|
||||
else:
|
||||
module.exit_json(changed=(value != after_value["was"]), before=after_value["was"],
|
||||
after=value)
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.pycompat24 import get_exception
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
454
lib/ansible/modules/database/misc/mongodb_user.py
Normal file
454
lib/ansible/modules/database/misc/mongodb_user.py
Normal file
@@ -0,0 +1,454 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# (c) 2012, Elliott Foster <elliott@fourkitchens.com>
|
||||
# Sponsored by Four Kitchens http://fourkitchens.com.
|
||||
# (c) 2014, Epic Games, Inc.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: mongodb_user
|
||||
short_description: Adds or removes a user from a MongoDB database.
|
||||
description:
|
||||
- Adds or removes a user from a MongoDB database.
|
||||
version_added: "1.1"
|
||||
options:
|
||||
login_user:
|
||||
description:
|
||||
- The username used to authenticate with
|
||||
required: false
|
||||
default: null
|
||||
login_password:
|
||||
description:
|
||||
- The password used to authenticate with
|
||||
required: false
|
||||
default: null
|
||||
login_host:
|
||||
description:
|
||||
- The host running the database
|
||||
required: false
|
||||
default: localhost
|
||||
login_port:
|
||||
description:
|
||||
- The port to connect to
|
||||
required: false
|
||||
default: 27017
|
||||
login_database:
|
||||
version_added: "2.0"
|
||||
description:
|
||||
- The database where login credentials are stored
|
||||
required: false
|
||||
default: null
|
||||
replica_set:
|
||||
version_added: "1.6"
|
||||
description:
|
||||
- Replica set to connect to (automatically connects to primary for writes)
|
||||
required: false
|
||||
default: null
|
||||
database:
|
||||
description:
|
||||
- The name of the database to add/remove the user from
|
||||
required: true
|
||||
name:
|
||||
description:
|
||||
- The name of the user to add or remove
|
||||
required: true
|
||||
default: null
|
||||
aliases: [ 'user' ]
|
||||
password:
|
||||
description:
|
||||
- The password to use for the user
|
||||
required: false
|
||||
default: null
|
||||
ssl:
|
||||
version_added: "1.8"
|
||||
description:
|
||||
- Whether to use an SSL connection when connecting to the database
|
||||
default: False
|
||||
ssl_cert_reqs:
|
||||
version_added: "2.2"
|
||||
description:
|
||||
- Specifies whether a certificate is required from the other side of the connection, and whether it will be validated if provided.
|
||||
required: false
|
||||
default: "CERT_REQUIRED"
|
||||
choices: ["CERT_REQUIRED", "CERT_OPTIONAL", "CERT_NONE"]
|
||||
roles:
|
||||
version_added: "1.3"
|
||||
description:
|
||||
- "The database user roles valid values could either be one or more of the following strings: 'read', 'readWrite', 'dbAdmin', 'userAdmin', 'clusterAdmin', 'readAnyDatabase', 'readWriteAnyDatabase', 'userAdminAnyDatabase', 'dbAdminAnyDatabase'"
|
||||
- "Or the following dictionary '{ db: DATABASE_NAME, role: ROLE_NAME }'."
|
||||
- "This param requires pymongo 2.5+. If it is a string, mongodb 2.4+ is also required. If it is a dictionary, mongo 2.6+ is required."
|
||||
required: false
|
||||
default: "readWrite"
|
||||
state:
|
||||
state:
|
||||
description:
|
||||
- The database user state
|
||||
required: false
|
||||
default: present
|
||||
choices: [ "present", "absent" ]
|
||||
update_password:
|
||||
required: false
|
||||
default: always
|
||||
choices: ['always', 'on_create']
|
||||
version_added: "2.1"
|
||||
description:
|
||||
- C(always) will update passwords if they differ. C(on_create) will only set the password for newly created users.
|
||||
|
||||
notes:
|
||||
- Requires the pymongo Python package on the remote host, version 2.4.2+. This
|
||||
can be installed using pip or the OS package manager. @see http://api.mongodb.org/python/current/installation.html
|
||||
requirements: [ "pymongo" ]
|
||||
author: "Elliott Foster (@elliotttf)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create 'burgers' database user with name 'bob' and password '12345'.
|
||||
- mongodb_user:
|
||||
database: burgers
|
||||
name: bob
|
||||
password: 12345
|
||||
state: present
|
||||
|
||||
# Create a database user via SSL (MongoDB must be compiled with the SSL option and configured properly)
|
||||
- mongodb_user:
|
||||
database: burgers
|
||||
name: bob
|
||||
password: 12345
|
||||
state: present
|
||||
ssl: True
|
||||
|
||||
# Delete 'burgers' database user with name 'bob'.
|
||||
- mongodb_user:
|
||||
database: burgers
|
||||
name: bob
|
||||
state: absent
|
||||
|
||||
# Define more users with various specific roles (if not defined, no roles is assigned, and the user will be added via pre mongo 2.2 style)
|
||||
- mongodb_user:
|
||||
database: burgers
|
||||
name: ben
|
||||
password: 12345
|
||||
roles: read
|
||||
state: present
|
||||
- mongodb_user:
|
||||
database: burgers
|
||||
name: jim
|
||||
password: 12345
|
||||
roles: readWrite,dbAdmin,userAdmin
|
||||
state: present
|
||||
- mongodb_user:
|
||||
database: burgers
|
||||
name: joe
|
||||
password: 12345
|
||||
roles: readWriteAnyDatabase
|
||||
state: present
|
||||
|
||||
# add a user to database in a replica set, the primary server is automatically discovered and written to
|
||||
- mongodb_user:
|
||||
database: burgers
|
||||
name: bob
|
||||
replica_set: belcher
|
||||
password: 12345
|
||||
roles: readWriteAnyDatabase
|
||||
state: present
|
||||
|
||||
# add a user 'oplog_reader' with read only access to the 'local' database on the replica_set 'belcher'. This is usefull for oplog access (MONGO_OPLOG_URL).
|
||||
# please notice the credentials must be added to the 'admin' database because the 'local' database is not syncronized and can't receive user credentials
|
||||
# To login with such user, the connection string should be MONGO_OPLOG_URL="mongodb://oplog_reader:oplog_reader_password@server1,server2/local?authSource=admin"
|
||||
# This syntax requires mongodb 2.6+ and pymongo 2.5+
|
||||
- mongodb_user:
|
||||
login_user: root
|
||||
login_password: root_password
|
||||
database: admin
|
||||
user: oplog_reader
|
||||
password: oplog_reader_password
|
||||
state: present
|
||||
replica_set: belcher
|
||||
roles:
|
||||
- db: local
|
||||
role: read
|
||||
|
||||
'''
|
||||
|
||||
import ssl as ssl_lib
|
||||
import ConfigParser
|
||||
from distutils.version import LooseVersion
|
||||
try:
|
||||
from pymongo.errors import ConnectionFailure
|
||||
from pymongo.errors import OperationFailure
|
||||
from pymongo import version as PyMongoVersion
|
||||
from pymongo import MongoClient
|
||||
except ImportError:
|
||||
try: # for older PyMongo 2.2
|
||||
from pymongo import Connection as MongoClient
|
||||
except ImportError:
|
||||
pymongo_found = False
|
||||
else:
|
||||
pymongo_found = True
|
||||
else:
|
||||
pymongo_found = True
|
||||
|
||||
# =========================================
|
||||
# MongoDB module specific support methods.
|
||||
#
|
||||
|
||||
def check_compatibility(module, client):
|
||||
"""Check the compatibility between the driver and the database.
|
||||
|
||||
See: https://docs.mongodb.com/ecosystem/drivers/driver-compatibility-reference/#python-driver-compatibility
|
||||
|
||||
Args:
|
||||
module: Ansible module.
|
||||
client (cursor): Mongodb cursor on admin database.
|
||||
"""
|
||||
loose_srv_version = LooseVersion(client.server_info()['version'])
|
||||
loose_driver_version = LooseVersion(PyMongoVersion)
|
||||
|
||||
if loose_srv_version >= LooseVersion('3.2') and loose_driver_version < LooseVersion('3.2'):
|
||||
module.fail_json(msg=' (Note: you must use pymongo 3.2+ with MongoDB >= 3.2)')
|
||||
|
||||
elif loose_srv_version >= LooseVersion('3.0') and loose_driver_version <= LooseVersion('2.8'):
|
||||
module.fail_json(msg=' (Note: you must use pymongo 2.8+ with MongoDB 3.0)')
|
||||
|
||||
elif loose_srv_version >= LooseVersion('2.6') and loose_driver_version <= LooseVersion('2.7'):
|
||||
module.fail_json(msg=' (Note: you must use pymongo 2.7+ with MongoDB 2.6)')
|
||||
|
||||
elif LooseVersion(PyMongoVersion) <= LooseVersion('2.5'):
|
||||
module.fail_json(msg=' (Note: you must be on mongodb 2.4+ and pymongo 2.5+ to use the roles param)')
|
||||
|
||||
|
||||
def user_find(client, user, db_name):
|
||||
"""Check if the user exists.
|
||||
|
||||
Args:
|
||||
client (cursor): Mongodb cursor on admin database.
|
||||
user (str): User to check.
|
||||
db_name (str): User's database.
|
||||
|
||||
Returns:
|
||||
dict: when user exists, False otherwise.
|
||||
"""
|
||||
for mongo_user in client["admin"].system.users.find():
|
||||
if mongo_user['user'] == user:
|
||||
# NOTE: there is no 'db' field in mongo 2.4.
|
||||
if 'db' not in mongo_user:
|
||||
return mongo_user
|
||||
|
||||
if mongo_user["db"] == db_name:
|
||||
return mongo_user
|
||||
return False
|
||||
|
||||
|
||||
def user_add(module, client, db_name, user, password, roles):
|
||||
#pymongo's user_add is a _create_or_update_user so we won't know if it was changed or updated
|
||||
#without reproducing a lot of the logic in database.py of pymongo
|
||||
db = client[db_name]
|
||||
|
||||
if roles is None:
|
||||
db.add_user(user, password, False)
|
||||
else:
|
||||
db.add_user(user, password, None, roles=roles)
|
||||
|
||||
def user_remove(module, client, db_name, user):
|
||||
exists = user_find(client, user, db_name)
|
||||
if exists:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, user=user)
|
||||
db = client[db_name]
|
||||
db.remove_user(user)
|
||||
else:
|
||||
module.exit_json(changed=False, user=user)
|
||||
|
||||
def load_mongocnf():
|
||||
config = ConfigParser.RawConfigParser()
|
||||
mongocnf = os.path.expanduser('~/.mongodb.cnf')
|
||||
|
||||
try:
|
||||
config.readfp(open(mongocnf))
|
||||
creds = dict(
|
||||
user=config.get('client', 'user'),
|
||||
password=config.get('client', 'pass')
|
||||
)
|
||||
except (ConfigParser.NoOptionError, IOError):
|
||||
return False
|
||||
|
||||
return creds
|
||||
|
||||
|
||||
|
||||
def check_if_roles_changed(uinfo, roles, db_name):
|
||||
# We must be aware of users which can read the oplog on a replicaset
|
||||
# Such users must have access to the local DB, but since this DB does not store users credentials
|
||||
# and is not synchronized among replica sets, the user must be stored on the admin db
|
||||
# Therefore their structure is the following :
|
||||
# {
|
||||
# "_id" : "admin.oplog_reader",
|
||||
# "user" : "oplog_reader",
|
||||
# "db" : "admin", # <-- admin DB
|
||||
# "roles" : [
|
||||
# {
|
||||
# "role" : "read",
|
||||
# "db" : "local" # <-- local DB
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
|
||||
def make_sure_roles_are_a_list_of_dict(roles, db_name):
|
||||
output = list()
|
||||
for role in roles:
|
||||
if isinstance(role, basestring):
|
||||
new_role = { "role": role, "db": db_name }
|
||||
output.append(new_role)
|
||||
else:
|
||||
output.append(role)
|
||||
return output
|
||||
|
||||
roles_as_list_of_dict = make_sure_roles_are_a_list_of_dict(roles, db_name)
|
||||
uinfo_roles = uinfo.get('roles', [])
|
||||
|
||||
if sorted(roles_as_list_of_dict) == sorted(uinfo_roles):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
# =========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
login_user=dict(default=None),
|
||||
login_password=dict(default=None),
|
||||
login_host=dict(default='localhost'),
|
||||
login_port=dict(default='27017'),
|
||||
login_database=dict(default=None),
|
||||
replica_set=dict(default=None),
|
||||
database=dict(required=True, aliases=['db']),
|
||||
name=dict(required=True, aliases=['user']),
|
||||
password=dict(aliases=['pass']),
|
||||
ssl=dict(default=False, type='bool'),
|
||||
roles=dict(default=None, type='list'),
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
update_password=dict(default="always", choices=["always", "on_create"]),
|
||||
ssl_cert_reqs=dict(default='CERT_REQUIRED', choices=['CERT_NONE', 'CERT_OPTIONAL', 'CERT_REQUIRED']),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not pymongo_found:
|
||||
module.fail_json(msg='the python pymongo module is required')
|
||||
|
||||
login_user = module.params['login_user']
|
||||
login_password = module.params['login_password']
|
||||
login_host = module.params['login_host']
|
||||
login_port = module.params['login_port']
|
||||
login_database = module.params['login_database']
|
||||
|
||||
replica_set = module.params['replica_set']
|
||||
db_name = module.params['database']
|
||||
user = module.params['name']
|
||||
password = module.params['password']
|
||||
ssl = module.params['ssl']
|
||||
ssl_cert_reqs = None
|
||||
roles = module.params['roles'] or []
|
||||
state = module.params['state']
|
||||
update_password = module.params['update_password']
|
||||
|
||||
try:
|
||||
connection_params = {
|
||||
"host": login_host,
|
||||
"port": int(login_port),
|
||||
}
|
||||
|
||||
if replica_set:
|
||||
connection_params["replicaset"] = replica_set
|
||||
|
||||
if ssl:
|
||||
connection_params["ssl"] = ssl
|
||||
connection_params["ssl_cert_reqs"] = getattr(ssl_lib, module.params['ssl_cert_reqs'])
|
||||
|
||||
client = MongoClient(**connection_params)
|
||||
|
||||
# NOTE: this check must be done ASAP.
|
||||
# We doesn't need to be authenticated.
|
||||
check_compatibility(module, client)
|
||||
|
||||
if login_user is None and login_password is None:
|
||||
mongocnf_creds = load_mongocnf()
|
||||
if mongocnf_creds is not False:
|
||||
login_user = mongocnf_creds['user']
|
||||
login_password = mongocnf_creds['password']
|
||||
elif login_password is None or login_user is None:
|
||||
module.fail_json(msg='when supplying login arguments, both login_user and login_password must be provided')
|
||||
|
||||
if login_user is not None and login_password is not None:
|
||||
client.admin.authenticate(login_user, login_password, source=login_database)
|
||||
elif LooseVersion(PyMongoVersion) >= LooseVersion('3.0'):
|
||||
if db_name != "admin":
|
||||
module.fail_json(msg='The localhost login exception only allows the first admin account to be created')
|
||||
#else: this has to be the first admin user added
|
||||
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg='unable to connect to database: %s' % str(e))
|
||||
|
||||
if state == 'present':
|
||||
if password is None and update_password == 'always':
|
||||
module.fail_json(msg='password parameter required when adding a user unless update_password is set to on_create')
|
||||
|
||||
try:
|
||||
uinfo = user_find(client, user, db_name)
|
||||
if update_password != 'always' and uinfo:
|
||||
password = None
|
||||
if not check_if_roles_changed(uinfo, roles, db_name):
|
||||
module.exit_json(changed=False, user=user)
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, user=user)
|
||||
|
||||
user_add(module, client, db_name, user, password, roles)
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg='Unable to add or update user: %s' % str(e))
|
||||
|
||||
# Here we can check password change if mongo provide a query for that : https://jira.mongodb.org/browse/SERVER-22848
|
||||
#newuinfo = user_find(client, user, db_name)
|
||||
#if uinfo['role'] == newuinfo['role'] and CheckPasswordHere:
|
||||
# module.exit_json(changed=False, user=user)
|
||||
|
||||
elif state == 'absent':
|
||||
try:
|
||||
user_remove(module, client, db_name, user)
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg='Unable to remove user: %s' % str(e))
|
||||
|
||||
module.exit_json(changed=True, user=user)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.pycompat24 import get_exception
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
350
lib/ansible/modules/database/misc/redis.py
Normal file
350
lib/ansible/modules/database/misc/redis.py
Normal file
@@ -0,0 +1,350 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: redis
|
||||
short_description: Various redis commands, slave and flush
|
||||
description:
|
||||
- Unified utility to interact with redis instances.
|
||||
'slave' sets a redis instance in slave or master mode.
|
||||
'flush' flushes all the instance or a specified db.
|
||||
'config' (new in 1.6), ensures a configuration setting on an instance.
|
||||
version_added: "1.3"
|
||||
options:
|
||||
command:
|
||||
description:
|
||||
- The selected redis command
|
||||
required: true
|
||||
default: null
|
||||
choices: [ "slave", "flush", "config" ]
|
||||
login_password:
|
||||
description:
|
||||
- The password used to authenticate with (usually not used)
|
||||
required: false
|
||||
default: null
|
||||
login_host:
|
||||
description:
|
||||
- The host running the database
|
||||
required: false
|
||||
default: localhost
|
||||
login_port:
|
||||
description:
|
||||
- The port to connect to
|
||||
required: false
|
||||
default: 6379
|
||||
master_host:
|
||||
description:
|
||||
- The host of the master instance [slave command]
|
||||
required: false
|
||||
default: null
|
||||
master_port:
|
||||
description:
|
||||
- The port of the master instance [slave command]
|
||||
required: false
|
||||
default: null
|
||||
slave_mode:
|
||||
description:
|
||||
- the mode of the redis instance [slave command]
|
||||
required: false
|
||||
default: slave
|
||||
choices: [ "master", "slave" ]
|
||||
db:
|
||||
description:
|
||||
- The database to flush (used in db mode) [flush command]
|
||||
required: false
|
||||
default: null
|
||||
flush_mode:
|
||||
description:
|
||||
- Type of flush (all the dbs in a redis instance or a specific one)
|
||||
[flush command]
|
||||
required: false
|
||||
default: all
|
||||
choices: [ "all", "db" ]
|
||||
name:
|
||||
version_added: 1.6
|
||||
description:
|
||||
- A redis config key.
|
||||
required: false
|
||||
default: null
|
||||
value:
|
||||
version_added: 1.6
|
||||
description:
|
||||
- A redis config value.
|
||||
required: false
|
||||
default: null
|
||||
|
||||
|
||||
notes:
|
||||
- Requires the redis-py Python package on the remote host. You can
|
||||
install it with pip (pip install redis) or with a package manager.
|
||||
https://github.com/andymccurdy/redis-py
|
||||
- If the redis master instance we are making slave of is password protected
|
||||
this needs to be in the redis.conf in the masterauth variable
|
||||
|
||||
requirements: [ redis ]
|
||||
author: "Xabier Larrakoetxea (@slok)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Set local redis instance to be slave of melee.island on port 6377
|
||||
- redis:
|
||||
command: slave
|
||||
master_host: melee.island
|
||||
master_port: 6377
|
||||
|
||||
# Deactivate slave mode
|
||||
- redis:
|
||||
command: slave
|
||||
slave_mode: master
|
||||
|
||||
# Flush all the redis db
|
||||
- redis:
|
||||
command: flush
|
||||
flush_mode: all
|
||||
|
||||
# Flush only one db in a redis instance
|
||||
- redis:
|
||||
command: flush
|
||||
db: 1
|
||||
flush_mode: db
|
||||
|
||||
# Configure local redis to have 10000 max clients
|
||||
- redis:
|
||||
command: config
|
||||
name: maxclients
|
||||
value: 10000
|
||||
|
||||
# Configure local redis to have lua time limit of 100 ms
|
||||
- redis:
|
||||
command: config
|
||||
name: lua-time-limit
|
||||
value: 100
|
||||
'''
|
||||
|
||||
try:
|
||||
import redis
|
||||
except ImportError:
|
||||
redis_found = False
|
||||
else:
|
||||
redis_found = True
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Redis module specific support methods.
|
||||
#
|
||||
|
||||
def set_slave_mode(client, master_host, master_port):
|
||||
try:
|
||||
return client.slaveof(master_host, master_port)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def set_master_mode(client):
|
||||
try:
|
||||
return client.slaveof()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def flush(client, db=None):
|
||||
try:
|
||||
if not isinstance(db, int):
|
||||
return client.flushall()
|
||||
else:
|
||||
# The passed client has been connected to the database already
|
||||
return client.flushdb()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
command=dict(default=None, choices=['slave', 'flush', 'config']),
|
||||
login_password=dict(default=None, no_log=True),
|
||||
login_host=dict(default='localhost'),
|
||||
login_port=dict(default=6379, type='int'),
|
||||
master_host=dict(default=None),
|
||||
master_port=dict(default=None, type='int'),
|
||||
slave_mode=dict(default='slave', choices=['master', 'slave']),
|
||||
db=dict(default=None, type='int'),
|
||||
flush_mode=dict(default='all', choices=['all', 'db']),
|
||||
name=dict(default=None),
|
||||
value=dict(default=None)
|
||||
),
|
||||
supports_check_mode = True
|
||||
)
|
||||
|
||||
if not redis_found:
|
||||
module.fail_json(msg="python redis module is required")
|
||||
|
||||
login_password = module.params['login_password']
|
||||
login_host = module.params['login_host']
|
||||
login_port = module.params['login_port']
|
||||
command = module.params['command']
|
||||
|
||||
# Slave Command section -----------
|
||||
if command == "slave":
|
||||
master_host = module.params['master_host']
|
||||
master_port = module.params['master_port']
|
||||
mode = module.params['slave_mode']
|
||||
|
||||
#Check if we have all the data
|
||||
if mode == "slave": # Only need data if we want to be slave
|
||||
if not master_host:
|
||||
module.fail_json(
|
||||
msg='In slave mode master host must be provided')
|
||||
|
||||
if not master_port:
|
||||
module.fail_json(
|
||||
msg='In slave mode master port must be provided')
|
||||
|
||||
#Connect and check
|
||||
r = redis.StrictRedis(host=login_host,
|
||||
port=login_port,
|
||||
password=login_password)
|
||||
try:
|
||||
r.ping()
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="unable to connect to database: %s" % e)
|
||||
|
||||
#Check if we are already in the mode that we want
|
||||
info = r.info()
|
||||
if mode == "master" and info["role"] == "master":
|
||||
module.exit_json(changed=False, mode=mode)
|
||||
|
||||
elif mode == "slave" and\
|
||||
info["role"] == "slave" and\
|
||||
info["master_host"] == master_host and\
|
||||
info["master_port"] == master_port:
|
||||
status = {
|
||||
'status': mode,
|
||||
'master_host': master_host,
|
||||
'master_port': master_port,
|
||||
}
|
||||
module.exit_json(changed=False, mode=status)
|
||||
else:
|
||||
# Do the stuff
|
||||
# (Check Check_mode before commands so the commands aren't evaluated
|
||||
# if not necessary)
|
||||
if mode == "slave":
|
||||
if module.check_mode or\
|
||||
set_slave_mode(r, master_host, master_port):
|
||||
info = r.info()
|
||||
status = {
|
||||
'status': mode,
|
||||
'master_host': master_host,
|
||||
'master_port': master_port,
|
||||
}
|
||||
module.exit_json(changed=True, mode=status)
|
||||
else:
|
||||
module.fail_json(msg='Unable to set slave mode')
|
||||
|
||||
else:
|
||||
if module.check_mode or set_master_mode(r):
|
||||
module.exit_json(changed=True, mode=mode)
|
||||
else:
|
||||
module.fail_json(msg='Unable to set master mode')
|
||||
|
||||
# flush Command section -----------
|
||||
elif command == "flush":
|
||||
db = module.params['db']
|
||||
mode = module.params['flush_mode']
|
||||
|
||||
#Check if we have all the data
|
||||
if mode == "db":
|
||||
if db is None:
|
||||
module.fail_json(
|
||||
msg="In db mode the db number must be provided")
|
||||
|
||||
#Connect and check
|
||||
r = redis.StrictRedis(host=login_host,
|
||||
port=login_port,
|
||||
password=login_password,
|
||||
db=db)
|
||||
try:
|
||||
r.ping()
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="unable to connect to database: %s" % e)
|
||||
|
||||
# Do the stuff
|
||||
# (Check Check_mode before commands so the commands aren't evaluated
|
||||
# if not necessary)
|
||||
if mode == "all":
|
||||
if module.check_mode or flush(r):
|
||||
module.exit_json(changed=True, flushed=True)
|
||||
else: # Flush never fails :)
|
||||
module.fail_json(msg="Unable to flush all databases")
|
||||
|
||||
else:
|
||||
if module.check_mode or flush(r, db):
|
||||
module.exit_json(changed=True, flushed=True, db=db)
|
||||
else: # Flush never fails :)
|
||||
module.fail_json(msg="Unable to flush '%d' database" % db)
|
||||
elif command == 'config':
|
||||
name = module.params['name']
|
||||
value = module.params['value']
|
||||
|
||||
r = redis.StrictRedis(host=login_host,
|
||||
port=login_port,
|
||||
password=login_password)
|
||||
|
||||
try:
|
||||
r.ping()
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="unable to connect to database: %s" % e)
|
||||
|
||||
|
||||
try:
|
||||
old_value = r.config_get(name)[name]
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="unable to read config: %s" % e)
|
||||
changed = old_value != value
|
||||
|
||||
if module.check_mode or not changed:
|
||||
module.exit_json(changed=changed, name=name, value=value)
|
||||
else:
|
||||
try:
|
||||
r.config_set(name, value)
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="unable to write config: %s" % e)
|
||||
module.exit_json(changed=changed, name=name, value=value)
|
||||
else:
|
||||
module.fail_json(msg='A valid command must be provided')
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.pycompat24 import get_exception
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
270
lib/ansible/modules/database/misc/riak.py
Normal file
270
lib/ansible/modules/database/misc/riak.py
Normal file
@@ -0,0 +1,270 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, James Martin <jmartin@basho.com>, Drew Kerrigan <dkerrigan@basho.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/>.
|
||||
#
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: riak
|
||||
short_description: This module handles some common Riak operations
|
||||
description:
|
||||
- This module can be used to join nodes to a cluster, check
|
||||
the status of the cluster.
|
||||
version_added: "1.2"
|
||||
author:
|
||||
- "James Martin (@jsmartin)"
|
||||
- "Drew Kerrigan (@drewkerrigan)"
|
||||
options:
|
||||
command:
|
||||
description:
|
||||
- The command you would like to perform against the cluster.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
choices: ['ping', 'kv_test', 'join', 'plan', 'commit']
|
||||
config_dir:
|
||||
description:
|
||||
- The path to the riak configuration directory
|
||||
required: false
|
||||
default: /etc/riak
|
||||
aliases: []
|
||||
http_conn:
|
||||
description:
|
||||
- The ip address and port that is listening for Riak HTTP queries
|
||||
required: false
|
||||
default: 127.0.0.1:8098
|
||||
aliases: []
|
||||
target_node:
|
||||
description:
|
||||
- The target node for certain operations (join, ping)
|
||||
required: false
|
||||
default: riak@127.0.0.1
|
||||
aliases: []
|
||||
wait_for_handoffs:
|
||||
description:
|
||||
- Number of seconds to wait for handoffs to complete.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
type: 'int'
|
||||
wait_for_ring:
|
||||
description:
|
||||
- Number of seconds to wait for all nodes to agree on the ring.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
type: 'int'
|
||||
wait_for_service:
|
||||
description:
|
||||
- Waits for a riak service to come online before continuing.
|
||||
required: false
|
||||
default: None
|
||||
aliases: []
|
||||
choices: ['kv']
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
version_added: 1.5.1
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Join's a Riak node to another node
|
||||
- riak:
|
||||
command: join
|
||||
target_node: riak@10.1.1.1
|
||||
|
||||
# Wait for handoffs to finish. Use with async and poll.
|
||||
- riak:
|
||||
wait_for_handoffs: yes
|
||||
|
||||
# Wait for riak_kv service to startup
|
||||
- riak:
|
||||
wait_for_service: kv
|
||||
'''
|
||||
|
||||
import time
|
||||
import socket
|
||||
import sys
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
# Let snippet from module_utils/basic.py return a proper error in this case
|
||||
pass
|
||||
|
||||
|
||||
def ring_check(module, riak_admin_bin):
|
||||
cmd = '%s ringready' % riak_admin_bin
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0 and 'TRUE All nodes agree on the ring' in out:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
command=dict(required=False, default=None, choices=[
|
||||
'ping', 'kv_test', 'join', 'plan', 'commit']),
|
||||
config_dir=dict(default='/etc/riak', type='path'),
|
||||
http_conn=dict(required=False, default='127.0.0.1:8098'),
|
||||
target_node=dict(default='riak@127.0.0.1', required=False),
|
||||
wait_for_handoffs=dict(default=False, type='int'),
|
||||
wait_for_ring=dict(default=False, type='int'),
|
||||
wait_for_service=dict(
|
||||
required=False, default=None, choices=['kv']),
|
||||
validate_certs = dict(default='yes', type='bool'))
|
||||
)
|
||||
|
||||
|
||||
command = module.params.get('command')
|
||||
config_dir = module.params.get('config_dir')
|
||||
http_conn = module.params.get('http_conn')
|
||||
target_node = module.params.get('target_node')
|
||||
wait_for_handoffs = module.params.get('wait_for_handoffs')
|
||||
wait_for_ring = module.params.get('wait_for_ring')
|
||||
wait_for_service = module.params.get('wait_for_service')
|
||||
validate_certs = module.params.get('validate_certs')
|
||||
|
||||
|
||||
#make sure riak commands are on the path
|
||||
riak_bin = module.get_bin_path('riak')
|
||||
riak_admin_bin = module.get_bin_path('riak-admin')
|
||||
|
||||
timeout = time.time() + 120
|
||||
while True:
|
||||
if time.time() > timeout:
|
||||
module.fail_json(msg='Timeout, could not fetch Riak stats.')
|
||||
(response, info) = fetch_url(module, 'http://%s/stats' % (http_conn), force=True, timeout=5)
|
||||
if info['status'] == 200:
|
||||
stats_raw = response.read()
|
||||
break
|
||||
time.sleep(5)
|
||||
|
||||
# here we attempt to load those stats,
|
||||
try:
|
||||
stats = json.loads(stats_raw)
|
||||
except:
|
||||
module.fail_json(msg='Could not parse Riak stats.')
|
||||
|
||||
node_name = stats['nodename']
|
||||
nodes = stats['ring_members']
|
||||
ring_size = stats['ring_creation_size']
|
||||
rc, out, err = module.run_command([riak_bin, 'version'] )
|
||||
version = out.strip()
|
||||
|
||||
result = dict(node_name=node_name,
|
||||
nodes=nodes,
|
||||
ring_size=ring_size,
|
||||
version=version)
|
||||
|
||||
if command == 'ping':
|
||||
cmd = '%s ping %s' % ( riak_bin, target_node )
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0:
|
||||
result['ping'] = out
|
||||
else:
|
||||
module.fail_json(msg=out)
|
||||
|
||||
elif command == 'kv_test':
|
||||
cmd = '%s test' % riak_admin_bin
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0:
|
||||
result['kv_test'] = out
|
||||
else:
|
||||
module.fail_json(msg=out)
|
||||
|
||||
elif command == 'join':
|
||||
if nodes.count(node_name) == 1 and len(nodes) > 1:
|
||||
result['join'] = 'Node is already in cluster or staged to be in cluster.'
|
||||
else:
|
||||
cmd = '%s cluster join %s' % (riak_admin_bin, target_node)
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0:
|
||||
result['join'] = out
|
||||
result['changed'] = True
|
||||
else:
|
||||
module.fail_json(msg=out)
|
||||
|
||||
elif command == 'plan':
|
||||
cmd = '%s cluster plan' % riak_admin_bin
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0:
|
||||
result['plan'] = out
|
||||
if 'Staged Changes' in out:
|
||||
result['changed'] = True
|
||||
else:
|
||||
module.fail_json(msg=out)
|
||||
|
||||
elif command == 'commit':
|
||||
cmd = '%s cluster commit' % riak_admin_bin
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0:
|
||||
result['commit'] = out
|
||||
result['changed'] = True
|
||||
else:
|
||||
module.fail_json(msg=out)
|
||||
|
||||
# this could take a while, recommend to run in async mode
|
||||
if wait_for_handoffs:
|
||||
timeout = time.time() + wait_for_handoffs
|
||||
while True:
|
||||
cmd = '%s transfers' % riak_admin_bin
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if 'No transfers active' in out:
|
||||
result['handoffs'] = 'No transfers active.'
|
||||
break
|
||||
time.sleep(10)
|
||||
if time.time() > timeout:
|
||||
module.fail_json(msg='Timeout waiting for handoffs.')
|
||||
|
||||
if wait_for_service:
|
||||
cmd = [riak_admin_bin, 'wait_for_service', 'riak_%s' % wait_for_service, node_name ]
|
||||
rc, out, err = module.run_command(cmd)
|
||||
result['service'] = out
|
||||
|
||||
if wait_for_ring:
|
||||
timeout = time.time() + wait_for_ring
|
||||
while True:
|
||||
if ring_check(module, riak_admin_bin):
|
||||
break
|
||||
time.sleep(10)
|
||||
if time.time() > timeout:
|
||||
module.fail_json(msg='Timeout waiting for nodes to agree on ring.')
|
||||
|
||||
result['ring_ready'] = ring_check(module, riak_admin_bin)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
0
lib/ansible/modules/database/mssql/__init__.py
Normal file
0
lib/ansible/modules/database/mssql/__init__.py
Normal file
245
lib/ansible/modules/database/mssql/mssql_db.py
Normal file
245
lib/ansible/modules/database/mssql/mssql_db.py
Normal file
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Ansible module to manage mssql databases
|
||||
# (c) 2014, Vedit Firat Arig <firatarig@gmail.com>
|
||||
# Outline and parts are reused from Mark Theunissen's mysql_db module
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: mssql_db
|
||||
short_description: Add or remove MSSQL databases from a remote host.
|
||||
description:
|
||||
- Add or remove MSSQL databases from a remote host.
|
||||
version_added: "2.2"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- name of the database to add or remove
|
||||
required: true
|
||||
default: null
|
||||
aliases: [ db ]
|
||||
login_user:
|
||||
description:
|
||||
- The username used to authenticate with
|
||||
required: false
|
||||
default: null
|
||||
login_password:
|
||||
description:
|
||||
- The password used to authenticate with
|
||||
required: false
|
||||
default: null
|
||||
login_host:
|
||||
description:
|
||||
- Host running the database
|
||||
required: false
|
||||
login_port:
|
||||
description:
|
||||
- Port of the MSSQL server. Requires login_host be defined as other then localhost if login_port is used
|
||||
required: false
|
||||
default: 1433
|
||||
state:
|
||||
description:
|
||||
- The database state
|
||||
required: false
|
||||
default: present
|
||||
choices: [ "present", "absent", "import" ]
|
||||
target:
|
||||
description:
|
||||
- Location, on the remote host, of the dump file to read from or write to. Uncompressed SQL
|
||||
files (C(.sql)) files are supported.
|
||||
required: false
|
||||
autocommit:
|
||||
description:
|
||||
- Automatically commit the change only if the import succeed. Sometimes it is necessary to use autocommit=true, since some content can't be changed within a transaction.
|
||||
required: false
|
||||
default: false
|
||||
choices: [ "false", "true" ]
|
||||
notes:
|
||||
- Requires the pymssql Python package on the remote host. For Ubuntu, this
|
||||
is as easy as pip install pymssql (See M(pip).)
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- pymssql
|
||||
author: Vedit Firat Arig
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a new database with name 'jackdata'
|
||||
- mssql_db:
|
||||
name: jackdata
|
||||
state: present
|
||||
|
||||
# Copy database dump file to remote host and restore it to database 'my_db'
|
||||
- copy:
|
||||
src: dump.sql
|
||||
dest: /tmp
|
||||
|
||||
- mssql_db:
|
||||
name: my_db
|
||||
state: import
|
||||
target: /tmp/dump.sql
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
#
|
||||
'''
|
||||
|
||||
import os
|
||||
try:
|
||||
import pymssql
|
||||
except ImportError:
|
||||
mssql_found = False
|
||||
else:
|
||||
mssql_found = True
|
||||
|
||||
|
||||
def db_exists(conn, cursor, db):
|
||||
cursor.execute("SELECT name FROM master.sys.databases WHERE name = %s", db)
|
||||
conn.commit()
|
||||
return bool(cursor.rowcount)
|
||||
|
||||
|
||||
def db_create(conn, cursor, db):
|
||||
cursor.execute("CREATE DATABASE [%s]" % db)
|
||||
return db_exists(conn, cursor, db)
|
||||
|
||||
|
||||
def db_delete(conn, cursor, db):
|
||||
try:
|
||||
cursor.execute("ALTER DATABASE [%s] SET single_user WITH ROLLBACK IMMEDIATE" % db)
|
||||
except:
|
||||
pass
|
||||
cursor.execute("DROP DATABASE [%s]" % db)
|
||||
return not db_exists(conn, cursor, db)
|
||||
|
||||
def db_import(conn, cursor, module, db, target):
|
||||
if os.path.isfile(target):
|
||||
backup = open(target, 'r')
|
||||
try:
|
||||
sqlQuery = "USE [%s]\n" % db
|
||||
for line in backup:
|
||||
if line is None:
|
||||
break
|
||||
elif line.startswith('GO'):
|
||||
cursor.execute(sqlQuery)
|
||||
sqlQuery = "USE [%s]\n" % db
|
||||
else:
|
||||
sqlQuery += line
|
||||
cursor.execute(sqlQuery)
|
||||
conn.commit()
|
||||
finally:
|
||||
backup.close()
|
||||
return 0, "import successful", ""
|
||||
else:
|
||||
return 1, "cannot find target file", "cannot find target file"
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(required=True, aliases=['db']),
|
||||
login_user=dict(default=''),
|
||||
login_password=dict(default=''),
|
||||
login_host=dict(required=True),
|
||||
login_port=dict(default='1433'),
|
||||
target=dict(default=None),
|
||||
autocommit=dict(type='bool', default=False),
|
||||
state=dict(
|
||||
default='present', choices=['present', 'absent', 'import'])
|
||||
)
|
||||
)
|
||||
|
||||
if not mssql_found:
|
||||
module.fail_json(msg="pymssql python module is required")
|
||||
|
||||
db = module.params['name']
|
||||
state = module.params['state']
|
||||
autocommit = module.params['autocommit']
|
||||
target = module.params["target"]
|
||||
|
||||
login_user = module.params['login_user']
|
||||
login_password = module.params['login_password']
|
||||
login_host = module.params['login_host']
|
||||
login_port = module.params['login_port']
|
||||
|
||||
login_querystring = login_host
|
||||
if login_port != "1433":
|
||||
login_querystring = "%s:%s" % (login_host, login_port)
|
||||
|
||||
if login_user != "" and login_password == "":
|
||||
module.fail_json(msg="when supplying login_user arguments login_password must be provided")
|
||||
|
||||
try:
|
||||
conn = pymssql.connect(user=login_user, password=login_password, host=login_querystring, database='master')
|
||||
cursor = conn.cursor()
|
||||
except Exception as e:
|
||||
if "Unknown database" in str(e):
|
||||
errno, errstr = e.args
|
||||
module.fail_json(msg="ERROR: %s %s" % (errno, errstr))
|
||||
else:
|
||||
module.fail_json(msg="unable to connect, check login_user and login_password are correct, or alternatively check your @sysconfdir@/freetds.conf / ${HOME}/.freetds.conf")
|
||||
|
||||
conn.autocommit(True)
|
||||
changed = False
|
||||
|
||||
if db_exists(conn, cursor, db):
|
||||
if state == "absent":
|
||||
try:
|
||||
changed = db_delete(conn, cursor, db)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="error deleting database: " + str(e))
|
||||
elif state == "import":
|
||||
conn.autocommit(autocommit)
|
||||
rc, stdout, stderr = db_import(conn, cursor, module, db, target)
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(msg="%s" % stderr)
|
||||
else:
|
||||
module.exit_json(changed=True, db=db, msg=stdout)
|
||||
else:
|
||||
if state == "present":
|
||||
try:
|
||||
changed = db_create(conn, cursor, db)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="error creating database: " + str(e))
|
||||
elif state == "import":
|
||||
try:
|
||||
changed = db_create(conn, cursor, db)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="error creating database: " + str(e))
|
||||
|
||||
conn.autocommit(autocommit)
|
||||
rc, stdout, stderr = db_import(conn, cursor, module, db, target)
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(msg="%s" % stderr)
|
||||
else:
|
||||
module.exit_json(changed=True, db=db, msg=stdout)
|
||||
|
||||
module.exit_json(changed=changed, db=db)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
377
lib/ansible/modules/database/mysql/mysql_replication.py
Normal file
377
lib/ansible/modules/database/mysql/mysql_replication.py
Normal file
@@ -0,0 +1,377 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
|
||||
Ansible module to manage mysql replication
|
||||
(c) 2013, Balazs Pocze <banyek@gawker.com>
|
||||
Certain parts are taken from Mark Theunissen's mysqldb module
|
||||
|
||||
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/>.
|
||||
"""
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: mysql_replication
|
||||
|
||||
short_description: Manage MySQL replication
|
||||
description:
|
||||
- Manages MySQL server replication, slave, master status get and change master host.
|
||||
version_added: "1.3"
|
||||
author: "Balazs Pocze (@banyek)"
|
||||
options:
|
||||
mode:
|
||||
description:
|
||||
- module operating mode. Could be getslave (SHOW SLAVE STATUS), getmaster (SHOW MASTER STATUS), changemaster (CHANGE MASTER TO), startslave (START SLAVE), stopslave (STOP SLAVE), resetslave (RESET SLAVE), resetslaveall (RESET SLAVE ALL)
|
||||
required: False
|
||||
choices:
|
||||
- getslave
|
||||
- getmaster
|
||||
- changemaster
|
||||
- stopslave
|
||||
- startslave
|
||||
- resetslave
|
||||
- resetslaveall
|
||||
default: getslave
|
||||
master_host:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_user:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_password:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_port:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_connect_retry:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_log_file:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_log_pos:
|
||||
description:
|
||||
- same as mysql variable
|
||||
relay_log_file:
|
||||
description:
|
||||
- same as mysql variable
|
||||
relay_log_pos:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_ssl:
|
||||
description:
|
||||
- same as mysql variable
|
||||
choices: [ 0, 1 ]
|
||||
master_ssl_ca:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_ssl_capath:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_ssl_cert:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_ssl_key:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_ssl_cipher:
|
||||
description:
|
||||
- same as mysql variable
|
||||
master_auto_position:
|
||||
description:
|
||||
- does the host uses GTID based replication or not
|
||||
required: false
|
||||
default: null
|
||||
version_added: "2.0"
|
||||
|
||||
extends_documentation_fragment: mysql
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Stop mysql slave thread
|
||||
- mysql_replication:
|
||||
mode: stopslave
|
||||
|
||||
# Get master binlog file name and binlog position
|
||||
- mysql_replication:
|
||||
mode: getmaster
|
||||
|
||||
# Change master to master server 192.0.2.1 and use binary log 'mysql-bin.000009' with position 4578
|
||||
- mysql_replication:
|
||||
mode: changemaster
|
||||
master_host: 192.0.2.1
|
||||
master_log_file: mysql-bin.000009
|
||||
master_log_pos: 4578
|
||||
|
||||
# Check slave status using port 3308
|
||||
- mysql_replication:
|
||||
mode: getslave
|
||||
login_host: ansible.example.com
|
||||
login_port: 3308
|
||||
'''
|
||||
|
||||
import os
|
||||
import warnings
|
||||
|
||||
try:
|
||||
import MySQLdb
|
||||
except ImportError:
|
||||
mysqldb_found = False
|
||||
else:
|
||||
mysqldb_found = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.mysql import mysql_connect
|
||||
from ansible.module_utils.pycompat24 import get_exception
|
||||
|
||||
|
||||
def get_master_status(cursor):
|
||||
cursor.execute("SHOW MASTER STATUS")
|
||||
masterstatus = cursor.fetchone()
|
||||
return masterstatus
|
||||
|
||||
|
||||
def get_slave_status(cursor):
|
||||
cursor.execute("SHOW SLAVE STATUS")
|
||||
slavestatus = cursor.fetchone()
|
||||
return slavestatus
|
||||
|
||||
|
||||
def stop_slave(cursor):
|
||||
try:
|
||||
cursor.execute("STOP SLAVE")
|
||||
stopped = True
|
||||
except:
|
||||
stopped = False
|
||||
return stopped
|
||||
|
||||
|
||||
def reset_slave(cursor):
|
||||
try:
|
||||
cursor.execute("RESET SLAVE")
|
||||
reset = True
|
||||
except:
|
||||
reset = False
|
||||
return reset
|
||||
|
||||
|
||||
def reset_slave_all(cursor):
|
||||
try:
|
||||
cursor.execute("RESET SLAVE ALL")
|
||||
reset = True
|
||||
except:
|
||||
reset = False
|
||||
return reset
|
||||
|
||||
|
||||
def start_slave(cursor):
|
||||
try:
|
||||
cursor.execute("START SLAVE")
|
||||
started = True
|
||||
except:
|
||||
started = False
|
||||
return started
|
||||
|
||||
|
||||
def changemaster(cursor, chm, chm_params):
|
||||
sql_param = ",".join(chm)
|
||||
query = 'CHANGE MASTER TO %s' % sql_param
|
||||
cursor.execute(query, chm_params)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
login_user=dict(default=None),
|
||||
login_password=dict(default=None, no_log=True),
|
||||
login_host=dict(default="localhost"),
|
||||
login_port=dict(default=3306, type='int'),
|
||||
login_unix_socket=dict(default=None),
|
||||
mode=dict(default="getslave", choices=["getmaster", "getslave", "changemaster", "stopslave", "startslave", "resetslave", "resetslaveall"]),
|
||||
master_auto_position=dict(default=False, type='bool'),
|
||||
master_host=dict(default=None),
|
||||
master_user=dict(default=None),
|
||||
master_password=dict(default=None, no_log=True),
|
||||
master_port=dict(default=None, type='int'),
|
||||
master_connect_retry=dict(default=None, type='int'),
|
||||
master_log_file=dict(default=None),
|
||||
master_log_pos=dict(default=None, type='int'),
|
||||
relay_log_file=dict(default=None),
|
||||
relay_log_pos=dict(default=None, type='int'),
|
||||
master_ssl=dict(default=False, type='bool'),
|
||||
master_ssl_ca=dict(default=None),
|
||||
master_ssl_capath=dict(default=None),
|
||||
master_ssl_cert=dict(default=None),
|
||||
master_ssl_key=dict(default=None),
|
||||
master_ssl_cipher=dict(default=None),
|
||||
connect_timeout=dict(default=30, type='int'),
|
||||
config_file=dict(default="~/.my.cnf", type='path'),
|
||||
ssl_cert=dict(default=None),
|
||||
ssl_key=dict(default=None),
|
||||
ssl_ca=dict(default=None),
|
||||
)
|
||||
)
|
||||
mode = module.params["mode"]
|
||||
master_host = module.params["master_host"]
|
||||
master_user = module.params["master_user"]
|
||||
master_password = module.params["master_password"]
|
||||
master_port = module.params["master_port"]
|
||||
master_connect_retry = module.params["master_connect_retry"]
|
||||
master_log_file = module.params["master_log_file"]
|
||||
master_log_pos = module.params["master_log_pos"]
|
||||
relay_log_file = module.params["relay_log_file"]
|
||||
relay_log_pos = module.params["relay_log_pos"]
|
||||
master_ssl = module.params["master_ssl"]
|
||||
master_ssl_ca = module.params["master_ssl_ca"]
|
||||
master_ssl_capath = module.params["master_ssl_capath"]
|
||||
master_ssl_cert = module.params["master_ssl_cert"]
|
||||
master_ssl_key = module.params["master_ssl_key"]
|
||||
master_ssl_cipher = module.params["master_ssl_cipher"]
|
||||
master_auto_position = module.params["master_auto_position"]
|
||||
ssl_cert = module.params["ssl_cert"]
|
||||
ssl_key = module.params["ssl_key"]
|
||||
ssl_ca = module.params["ssl_ca"]
|
||||
connect_timeout = module.params['connect_timeout']
|
||||
config_file = module.params['config_file']
|
||||
|
||||
if not mysqldb_found:
|
||||
module.fail_json(msg="the python mysqldb module is required")
|
||||
else:
|
||||
warnings.filterwarnings('error', category=MySQLdb.Warning)
|
||||
|
||||
login_password = module.params["login_password"]
|
||||
login_user = module.params["login_user"]
|
||||
|
||||
try:
|
||||
cursor = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca, None, 'MySQLdb.cursors.DictCursor',
|
||||
connect_timeout=connect_timeout)
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
if os.path.exists(config_file):
|
||||
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. Exception message: %s" % (config_file, e))
|
||||
else:
|
||||
module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, e))
|
||||
|
||||
if mode in "getmaster":
|
||||
status = get_master_status(cursor)
|
||||
if not isinstance(status, dict):
|
||||
status = dict(Is_Master=False, msg="Server is not configured as mysql master")
|
||||
else:
|
||||
status['Is_Master'] = True
|
||||
module.exit_json(**status)
|
||||
|
||||
elif mode in "getslave":
|
||||
status = get_slave_status(cursor)
|
||||
if not isinstance(status, dict):
|
||||
status = dict(Is_Slave=False, msg="Server is not configured as mysql slave")
|
||||
else:
|
||||
status['Is_Slave'] = True
|
||||
module.exit_json(**status)
|
||||
|
||||
elif mode in "changemaster":
|
||||
chm=[]
|
||||
chm_params = {}
|
||||
result = {}
|
||||
if master_host:
|
||||
chm.append("MASTER_HOST=%(master_host)s")
|
||||
chm_params['master_host'] = master_host
|
||||
if master_user:
|
||||
chm.append("MASTER_USER=%(master_user)s")
|
||||
chm_params['master_user'] = master_user
|
||||
if master_password:
|
||||
chm.append("MASTER_PASSWORD=%(master_password)s")
|
||||
chm_params['master_password'] = master_password
|
||||
if master_port is not None:
|
||||
chm.append("MASTER_PORT=%(master_port)s")
|
||||
chm_params['master_port'] = master_port
|
||||
if master_connect_retry is not None:
|
||||
chm.append("MASTER_CONNECT_RETRY=%(master_connect_retry)s")
|
||||
chm_params['master_connect_retry'] = master_connect_retry
|
||||
if master_log_file:
|
||||
chm.append("MASTER_LOG_FILE=%(master_log_file)s")
|
||||
chm_params['master_log_file'] = master_log_file
|
||||
if master_log_pos is not None:
|
||||
chm.append("MASTER_LOG_POS=%(master_log_pos)s")
|
||||
chm_params['master_log_pos'] = master_log_pos
|
||||
if relay_log_file:
|
||||
chm.append("RELAY_LOG_FILE=%(relay_log_file)s")
|
||||
chm_params['relay_log_file'] = relay_log_file
|
||||
if relay_log_pos is not None:
|
||||
chm.append("RELAY_LOG_POS=%(relay_log_pos)s")
|
||||
chm_params['relay_log_pos'] = relay_log_pos
|
||||
if master_ssl:
|
||||
chm.append("MASTER_SSL=1")
|
||||
if master_ssl_ca:
|
||||
chm.append("MASTER_SSL_CA=%(master_ssl_ca)s")
|
||||
chm_params['master_ssl_ca'] = master_ssl_ca
|
||||
if master_ssl_capath:
|
||||
chm.append("MASTER_SSL_CAPATH=%(master_ssl_capath)s")
|
||||
chm_params['master_ssl_capath'] = master_ssl_capath
|
||||
if master_ssl_cert:
|
||||
chm.append("MASTER_SSL_CERT=%(master_ssl_cert)s")
|
||||
chm_params['master_ssl_cert'] = master_ssl_cert
|
||||
if master_ssl_key:
|
||||
chm.append("MASTER_SSL_KEY=%(master_ssl_key)s")
|
||||
chm_params['master_ssl_key'] = master_ssl_key
|
||||
if master_ssl_cipher:
|
||||
chm.append("MASTER_SSL_CIPHER=%(master_ssl_cipher)s")
|
||||
chm_params['master_ssl_cipher'] = master_ssl_cipher
|
||||
if master_auto_position:
|
||||
chm.append("MASTER_AUTO_POSITION = 1")
|
||||
try:
|
||||
changemaster(cursor, chm, chm_params)
|
||||
except MySQLdb.Warning:
|
||||
e = get_exception()
|
||||
result['warning'] = str(e)
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg='%s. Query == CHANGE MASTER TO %s' % (e, chm))
|
||||
result['changed']=True
|
||||
module.exit_json(**result)
|
||||
elif mode in "startslave":
|
||||
started = start_slave(cursor)
|
||||
if started is True:
|
||||
module.exit_json(msg="Slave started ", changed=True)
|
||||
else:
|
||||
module.exit_json(msg="Slave already started (Or cannot be started)", changed=False)
|
||||
elif mode in "stopslave":
|
||||
stopped = stop_slave(cursor)
|
||||
if stopped is True:
|
||||
module.exit_json(msg="Slave stopped", changed=True)
|
||||
else:
|
||||
module.exit_json(msg="Slave already stopped", changed=False)
|
||||
elif mode in "resetslave":
|
||||
reset = reset_slave(cursor)
|
||||
if reset is True:
|
||||
module.exit_json(msg="Slave reset", changed=True)
|
||||
else:
|
||||
module.exit_json(msg="Slave already reset", changed=False)
|
||||
elif mode in "resetslaveall":
|
||||
reset = reset_slave_all(cursor)
|
||||
if reset is True:
|
||||
module.exit_json(msg="Slave reset", changed=True)
|
||||
else:
|
||||
module.exit_json(msg="Slave already reset", changed=False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
warnings.simplefilter("ignore")
|
||||
198
lib/ansible/modules/database/postgresql/postgresql_ext.py
Normal file
198
lib/ansible/modules/database/postgresql/postgresql_ext.py
Normal file
@@ -0,0 +1,198 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: postgresql_ext
|
||||
short_description: Add or remove PostgreSQL extensions from a database.
|
||||
description:
|
||||
- Add or remove PostgreSQL extensions from a database.
|
||||
version_added: "1.9"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- name of the extension to add or remove
|
||||
required: true
|
||||
default: null
|
||||
db:
|
||||
description:
|
||||
- name of the database to add or remove the extension to/from
|
||||
required: true
|
||||
default: null
|
||||
login_user:
|
||||
description:
|
||||
- The username used to authenticate with
|
||||
required: false
|
||||
default: null
|
||||
login_password:
|
||||
description:
|
||||
- The password used to authenticate with
|
||||
required: false
|
||||
default: null
|
||||
login_host:
|
||||
description:
|
||||
- Host running the database
|
||||
required: false
|
||||
default: localhost
|
||||
port:
|
||||
description:
|
||||
- Database port to connect to.
|
||||
required: false
|
||||
default: 5432
|
||||
state:
|
||||
description:
|
||||
- The database extension state
|
||||
required: false
|
||||
default: present
|
||||
choices: [ "present", "absent" ]
|
||||
notes:
|
||||
- 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
|
||||
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 C(postgresql), C(libpq-dev), and C(python-psycopg2) packages on the remote host before using this module.
|
||||
requirements: [ psycopg2 ]
|
||||
author: "Daniel Schep (@dschep)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Adds postgis to the database "acme"
|
||||
- postgresql_ext:
|
||||
name: postgis
|
||||
db: acme
|
||||
'''
|
||||
|
||||
try:
|
||||
import psycopg2
|
||||
import psycopg2.extras
|
||||
except ImportError:
|
||||
postgresqldb_found = False
|
||||
else:
|
||||
postgresqldb_found = True
|
||||
|
||||
class NotSupportedError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# ===========================================
|
||||
# PostgreSQL module specific support methods.
|
||||
#
|
||||
|
||||
def ext_exists(cursor, ext):
|
||||
query = "SELECT * FROM pg_extension WHERE extname=%(ext)s"
|
||||
cursor.execute(query, {'ext': ext})
|
||||
return cursor.rowcount == 1
|
||||
|
||||
def ext_delete(cursor, ext):
|
||||
if ext_exists(cursor, ext):
|
||||
query = "DROP EXTENSION \"%s\"" % ext
|
||||
cursor.execute(query)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def ext_create(cursor, ext):
|
||||
if not ext_exists(cursor, ext):
|
||||
query = 'CREATE EXTENSION "%s"' % ext
|
||||
cursor.execute(query)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
login_user=dict(default="postgres"),
|
||||
login_password=dict(default="", no_log=True),
|
||||
login_host=dict(default=""),
|
||||
port=dict(default="5432"),
|
||||
db=dict(required=True),
|
||||
ext=dict(required=True, aliases=['name']),
|
||||
state=dict(default="present", choices=["absent", "present"]),
|
||||
),
|
||||
supports_check_mode = True
|
||||
)
|
||||
|
||||
if not postgresqldb_found:
|
||||
module.fail_json(msg="the python psycopg2 module is required")
|
||||
|
||||
db = module.params["db"]
|
||||
ext = module.params["ext"]
|
||||
port = module.params["port"]
|
||||
state = module.params["state"]
|
||||
changed = False
|
||||
|
||||
# To use defaults values, keyword arguments must be absent, so
|
||||
# check which values are empty and don't include in the **kw
|
||||
# dictionary
|
||||
params_map = {
|
||||
"login_host":"host",
|
||||
"login_user":"user",
|
||||
"login_password":"password",
|
||||
"port":"port"
|
||||
}
|
||||
kw = dict( (params_map[k], v) for (k, v) in module.params.iteritems()
|
||||
if k in params_map and v != '' )
|
||||
try:
|
||||
db_connection = psycopg2.connect(database=db, **kw)
|
||||
# Enable autocommit so we can create databases
|
||||
if psycopg2.__version__ >= '2.4.2':
|
||||
db_connection.autocommit = True
|
||||
else:
|
||||
db_connection.set_isolation_level(psycopg2
|
||||
.extensions
|
||||
.ISOLATION_LEVEL_AUTOCOMMIT)
|
||||
cursor = db_connection.cursor(
|
||||
cursor_factory=psycopg2.extras.DictCursor)
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="unable to connect to database: %s" % e)
|
||||
|
||||
try:
|
||||
if module.check_mode:
|
||||
if state == "present":
|
||||
changed = not ext_exists(cursor, ext)
|
||||
elif state == "absent":
|
||||
changed = ext_exists(cursor, ext)
|
||||
else:
|
||||
if state == "absent":
|
||||
changed = ext_delete(cursor, ext)
|
||||
|
||||
elif state == "present":
|
||||
changed = ext_create(cursor, ext)
|
||||
except NotSupportedError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e))
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="Database query failed: %s" % e)
|
||||
|
||||
module.exit_json(changed=changed, db=db, ext=ext)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.pycompat24 import get_exception
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
294
lib/ansible/modules/database/postgresql/postgresql_lang.py
Normal file
294
lib/ansible/modules/database/postgresql/postgresql_lang.py
Normal file
@@ -0,0 +1,294 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2014, Jens Depuydt <http://www.jensd.be>
|
||||
#
|
||||
# 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/>.
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: postgresql_lang
|
||||
short_description: Adds, removes or changes procedural languages with a PostgreSQL database.
|
||||
description:
|
||||
- Adds, removes or changes procedural languages with a PostgreSQL database.
|
||||
- This module allows you to add a language, remote a language or change the trust
|
||||
relationship with a PostgreSQL database. The module can be used on the machine
|
||||
where executed or on a remote host.
|
||||
- When removing a language from a database, it is possible that dependencies prevent
|
||||
the database from being removed. In that case, you can specify casade to
|
||||
automatically drop objects that depend on the language (such as functions in the
|
||||
language). In case the language can't be deleted because it is required by the
|
||||
database system, you can specify fail_on_drop=no to ignore the error.
|
||||
- Be carefull when marking a language as trusted since this could be a potential
|
||||
security breach. Untrusted languages allow only users with the PostgreSQL superuser
|
||||
privilege to use this language to create new functions.
|
||||
version_added: "1.7"
|
||||
options:
|
||||
lang:
|
||||
description:
|
||||
- name of the procedural language to add, remove or change
|
||||
required: true
|
||||
default: null
|
||||
trust:
|
||||
description:
|
||||
- make this language trusted for the selected db
|
||||
required: false
|
||||
default: no
|
||||
choices: [ "yes", "no" ]
|
||||
db:
|
||||
description:
|
||||
- name of database where the language will be added, removed or changed
|
||||
required: false
|
||||
default: null
|
||||
force_trust:
|
||||
description:
|
||||
- marks the language as trusted, even if it's marked as untrusted in pg_pltemplate.
|
||||
- use with care!
|
||||
required: false
|
||||
default: no
|
||||
choices: [ "yes", "no" ]
|
||||
fail_on_drop:
|
||||
description:
|
||||
- if C(yes), fail when removing a language. Otherwise just log and continue
|
||||
- in some cases, it is not possible to remove a language (used by the db-system). When dependencies block the removal, consider using C(cascade).
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: [ "yes", "no" ]
|
||||
cascade:
|
||||
description:
|
||||
- when dropping a language, also delete object that depend on this language.
|
||||
- only used when C(state=absent).
|
||||
required: false
|
||||
default: no
|
||||
choices: [ "yes", "no" ]
|
||||
port:
|
||||
description:
|
||||
- Database port to connect to.
|
||||
required: false
|
||||
default: 5432
|
||||
login_user:
|
||||
description:
|
||||
- User used to authenticate with PostgreSQL
|
||||
required: false
|
||||
default: postgres
|
||||
login_password:
|
||||
description:
|
||||
- Password used to authenticate with PostgreSQL (must match C(login_user))
|
||||
required: false
|
||||
default: null
|
||||
login_host:
|
||||
description:
|
||||
- Host running PostgreSQL where you want to execute the actions.
|
||||
required: false
|
||||
default: localhost
|
||||
state:
|
||||
description:
|
||||
- The state of the language for the selected database
|
||||
required: false
|
||||
default: present
|
||||
choices: [ "present", "absent" ]
|
||||
notes:
|
||||
- The default authentication assumes that you are either logging in as or
|
||||
sudo'ing to the postgres account on the host.
|
||||
- This module uses psycopg2, a Python PostgreSQL database adapter. You must
|
||||
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.
|
||||
requirements: [ psycopg2 ]
|
||||
author: "Jens Depuydt (@jensdepuydt)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Add language pltclu to database testdb if it doesn't exist:
|
||||
- postgresql_lang db=testdb lang=pltclu state=present
|
||||
|
||||
# Add language pltclu to database testdb if it doesn't exist and mark it as trusted:
|
||||
# Marks the language as trusted if it exists but isn't trusted yet
|
||||
# force_trust makes sure that the language will be marked as trusted
|
||||
- postgresql_lang:
|
||||
db: testdb
|
||||
lang: pltclu
|
||||
state: present
|
||||
trust: yes
|
||||
force_trust: yes
|
||||
|
||||
# Remove language pltclu from database testdb:
|
||||
- postgresql_lang:
|
||||
db: testdb
|
||||
lang: pltclu
|
||||
state: absent
|
||||
|
||||
# Remove language pltclu from database testdb and remove all dependencies:
|
||||
- postgresql_lang:
|
||||
db: testdb
|
||||
lang: pltclu
|
||||
state: absent
|
||||
cascade: yes
|
||||
|
||||
# Remove language c from database testdb but ignore errors if something prevents the removal:
|
||||
- postgresql_lang:
|
||||
db: testdb
|
||||
lang: pltclu
|
||||
state: absent
|
||||
fail_on_drop: no
|
||||
'''
|
||||
|
||||
try:
|
||||
import psycopg2
|
||||
except ImportError:
|
||||
postgresqldb_found = False
|
||||
else:
|
||||
postgresqldb_found = True
|
||||
|
||||
def lang_exists(cursor, lang):
|
||||
"""Checks if language exists for db"""
|
||||
query = "SELECT lanname FROM pg_language WHERE lanname='%s'" % lang
|
||||
cursor.execute(query)
|
||||
return cursor.rowcount > 0
|
||||
|
||||
def lang_istrusted(cursor, lang):
|
||||
"""Checks if language is trusted for db"""
|
||||
query = "SELECT lanpltrusted FROM pg_language WHERE lanname='%s'" % lang
|
||||
cursor.execute(query)
|
||||
return cursor.fetchone()[0]
|
||||
|
||||
def lang_altertrust(cursor, lang, trust):
|
||||
"""Changes if language is trusted for db"""
|
||||
query = "UPDATE pg_language SET lanpltrusted = %s WHERE lanname=%s"
|
||||
cursor.execute(query, (trust, lang))
|
||||
return True
|
||||
|
||||
def lang_add(cursor, lang, trust):
|
||||
"""Adds language for db"""
|
||||
if trust:
|
||||
query = 'CREATE TRUSTED LANGUAGE "%s"' % lang
|
||||
else:
|
||||
query = 'CREATE LANGUAGE "%s"' % lang
|
||||
cursor.execute(query)
|
||||
return True
|
||||
|
||||
def lang_drop(cursor, lang, cascade):
|
||||
"""Drops language for db"""
|
||||
cursor.execute("SAVEPOINT ansible_pgsql_lang_drop")
|
||||
try:
|
||||
if cascade:
|
||||
cursor.execute("DROP LANGUAGE \"%s\" CASCADE" % lang)
|
||||
else:
|
||||
cursor.execute("DROP LANGUAGE \"%s\"" % lang)
|
||||
except:
|
||||
cursor.execute("ROLLBACK TO SAVEPOINT ansible_pgsql_lang_drop")
|
||||
cursor.execute("RELEASE SAVEPOINT ansible_pgsql_lang_drop")
|
||||
return False
|
||||
cursor.execute("RELEASE SAVEPOINT ansible_pgsql_lang_drop")
|
||||
return True
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
login_user=dict(default="postgres"),
|
||||
login_password=dict(default="", no_log=True),
|
||||
login_host=dict(default=""),
|
||||
db=dict(required=True),
|
||||
port=dict(default='5432'),
|
||||
lang=dict(required=True),
|
||||
state=dict(default="present", choices=["absent", "present"]),
|
||||
trust=dict(type='bool', default='no'),
|
||||
force_trust=dict(type='bool', default='no'),
|
||||
cascade=dict(type='bool', default='no'),
|
||||
fail_on_drop=dict(type='bool', default='yes'),
|
||||
),
|
||||
supports_check_mode = True
|
||||
)
|
||||
|
||||
db = module.params["db"]
|
||||
port = module.params["port"]
|
||||
lang = module.params["lang"]
|
||||
state = module.params["state"]
|
||||
trust = module.params["trust"]
|
||||
force_trust = module.params["force_trust"]
|
||||
cascade = module.params["cascade"]
|
||||
fail_on_drop = module.params["fail_on_drop"]
|
||||
|
||||
if not postgresqldb_found:
|
||||
module.fail_json(msg="the python psycopg2 module is required")
|
||||
|
||||
params_map = {
|
||||
"login_host":"host",
|
||||
"login_user":"user",
|
||||
"login_password":"password",
|
||||
"port":"port",
|
||||
"db":"database"
|
||||
}
|
||||
kw = dict( (params_map[k], v) for (k, v) in module.params.iteritems()
|
||||
if k in params_map and v != "" )
|
||||
try:
|
||||
db_connection = psycopg2.connect(**kw)
|
||||
cursor = db_connection.cursor()
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="unable to connect to database: %s" % e)
|
||||
changed = False
|
||||
lang_dropped = False
|
||||
kw = dict(db=db,lang=lang,trust=trust)
|
||||
|
||||
if state == "present":
|
||||
if lang_exists(cursor, lang):
|
||||
lang_trusted = lang_istrusted(cursor, lang)
|
||||
if (lang_trusted and not trust) or (not lang_trusted and trust):
|
||||
if module.check_mode:
|
||||
changed = True
|
||||
else:
|
||||
changed = lang_altertrust(cursor, lang, trust)
|
||||
else:
|
||||
if module.check_mode:
|
||||
changed = True
|
||||
else:
|
||||
changed = lang_add(cursor, lang, trust)
|
||||
if force_trust:
|
||||
changed = lang_altertrust(cursor, lang, trust)
|
||||
|
||||
else:
|
||||
if lang_exists(cursor, lang):
|
||||
if module.check_mode:
|
||||
changed = True
|
||||
kw['lang_dropped'] = True
|
||||
else:
|
||||
changed = lang_drop(cursor, lang, cascade)
|
||||
if fail_on_drop and not changed:
|
||||
msg = "unable to drop language, use cascade to delete dependencies or fail_on_drop=no to ignore"
|
||||
module.fail_json(msg=msg)
|
||||
kw['lang_dropped'] = changed
|
||||
|
||||
if changed:
|
||||
if module.check_mode:
|
||||
db_connection.rollback()
|
||||
else:
|
||||
db_connection.commit()
|
||||
|
||||
kw['changed'] = changed
|
||||
module.exit_json(**kw)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.pycompat24 import get_exception
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
274
lib/ansible/modules/database/postgresql/postgresql_schema.py
Normal file
274
lib/ansible/modules/database/postgresql/postgresql_schema.py
Normal file
@@ -0,0 +1,274 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': 'preview',
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: postgresql_schema
|
||||
short_description: Add or remove PostgreSQL schema from a remote host
|
||||
description:
|
||||
- Add or remove PostgreSQL schema from a remote host.
|
||||
version_added: "2.3"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the schema to add or remove.
|
||||
required: true
|
||||
default: null
|
||||
database:
|
||||
description:
|
||||
- Name of the database to connect to.
|
||||
required: false
|
||||
default: postgres
|
||||
login_user:
|
||||
description:
|
||||
- The username used to authenticate with.
|
||||
required: false
|
||||
default: null
|
||||
login_password:
|
||||
description:
|
||||
- The password used to authenticate with.
|
||||
required: false
|
||||
default: null
|
||||
login_host:
|
||||
description:
|
||||
- Host running the database.
|
||||
required: false
|
||||
default: localhost
|
||||
login_unix_socket:
|
||||
description:
|
||||
- Path to a Unix domain socket for local connections.
|
||||
required: false
|
||||
default: null
|
||||
owner:
|
||||
description:
|
||||
- Name of the role to set as owner of the schema.
|
||||
required: false
|
||||
default: null
|
||||
port:
|
||||
description:
|
||||
- Database port to connect to.
|
||||
required: false
|
||||
default: 5432
|
||||
state:
|
||||
description:
|
||||
- The schema state.
|
||||
required: false
|
||||
default: present
|
||||
choices: [ "present", "absent" ]
|
||||
notes:
|
||||
- This module uses I(psycopg2), a Python PostgreSQL database adapter. You must 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 C(postgresql), C(libpq-dev), and C(python-psycopg2) packages on the remote host before using this module.
|
||||
requirements: [ psycopg2 ]
|
||||
author: "Flavien Chantelot <contact@flavien.io>"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a new schema with name "acme"
|
||||
- postgresql_schema:
|
||||
name: acme
|
||||
|
||||
# Create a new schema "acme" with a user "bob" who will own it
|
||||
- postgresql_schema:
|
||||
name: acme
|
||||
owner: bob
|
||||
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
schema:
|
||||
description: Name of the schema
|
||||
returned: success, changed
|
||||
type: string
|
||||
sample: "acme"
|
||||
'''
|
||||
|
||||
|
||||
try:
|
||||
import psycopg2
|
||||
import psycopg2.extras
|
||||
except ImportError:
|
||||
postgresqldb_found = False
|
||||
else:
|
||||
postgresqldb_found = True
|
||||
|
||||
class NotSupportedError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# ===========================================
|
||||
# PostgreSQL module specific support methods.
|
||||
#
|
||||
|
||||
def set_owner(cursor, schema, owner):
|
||||
query = "ALTER SCHEMA %s OWNER TO %s" % (
|
||||
pg_quote_identifier(schema, 'schema'),
|
||||
pg_quote_identifier(owner, 'role'))
|
||||
cursor.execute(query)
|
||||
return True
|
||||
|
||||
def get_schema_info(cursor, schema):
|
||||
query = """
|
||||
SELECT schema_owner AS owner
|
||||
FROM information_schema.schemata
|
||||
WHERE schema_name = %(schema)s
|
||||
"""
|
||||
cursor.execute(query, {'schema': schema})
|
||||
return cursor.fetchone()
|
||||
|
||||
def schema_exists(cursor, schema):
|
||||
query = "SELECT schema_name FROM information_schema.schemata WHERE schema_name = %(schema)s"
|
||||
cursor.execute(query, {'schema': schema})
|
||||
return cursor.rowcount == 1
|
||||
|
||||
def schema_delete(cursor, schema):
|
||||
if schema_exists(cursor, schema):
|
||||
query = "DROP SCHEMA %s" % pg_quote_identifier(schema, 'schema')
|
||||
cursor.execute(query)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def schema_create(cursor, schema, owner):
|
||||
if not schema_exists(cursor, schema):
|
||||
query_fragments = ['CREATE SCHEMA %s' % pg_quote_identifier(schema, 'schema')]
|
||||
if owner:
|
||||
query_fragments.append('AUTHORIZATION %s' % pg_quote_identifier(owner, 'role'))
|
||||
query = ' '.join(query_fragments)
|
||||
cursor.execute(query)
|
||||
return True
|
||||
else:
|
||||
schema_info = get_schema_info(cursor, schema)
|
||||
if owner and owner != schema_info['owner']:
|
||||
return set_owner(cursor, schema, owner)
|
||||
else:
|
||||
return False
|
||||
|
||||
def schema_matches(cursor, schema, owner):
|
||||
if not schema_exists(cursor, schema):
|
||||
return False
|
||||
else:
|
||||
schema_info = get_schema_info(cursor, schema)
|
||||
if owner and owner != schema_info['owner']:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
login_user=dict(default="postgres"),
|
||||
login_password=dict(default=""),
|
||||
login_host=dict(default=""),
|
||||
login_unix_socket=dict(default=""),
|
||||
port=dict(default="5432"),
|
||||
schema=dict(required=True, aliases=['name']),
|
||||
owner=dict(default=""),
|
||||
database=dict(default="postgres"),
|
||||
state=dict(default="present", choices=["absent", "present"]),
|
||||
),
|
||||
supports_check_mode = True
|
||||
)
|
||||
|
||||
if not postgresqldb_found:
|
||||
module.fail_json(msg="the python psycopg2 module is required")
|
||||
|
||||
schema = module.params["schema"]
|
||||
owner = module.params["owner"]
|
||||
state = module.params["state"]
|
||||
database = module.params["database"]
|
||||
changed = False
|
||||
|
||||
# To use defaults values, keyword arguments must be absent, so
|
||||
# check which values are empty and don't include in the **kw
|
||||
# dictionary
|
||||
params_map = {
|
||||
"login_host":"host",
|
||||
"login_user":"user",
|
||||
"login_password":"password",
|
||||
"port":"port"
|
||||
}
|
||||
kw = dict( (params_map[k], v) for (k, v) in module.params.iteritems()
|
||||
if k in params_map and v != '' )
|
||||
|
||||
# If a login_unix_socket is specified, incorporate it here.
|
||||
is_localhost = "host" not in kw or kw["host"] == "" or kw["host"] == "localhost"
|
||||
if is_localhost and module.params["login_unix_socket"] != "":
|
||||
kw["host"] = module.params["login_unix_socket"]
|
||||
|
||||
try:
|
||||
db_connection = psycopg2.connect(database=database, **kw)
|
||||
# Enable autocommit so we can create databases
|
||||
if psycopg2.__version__ >= '2.4.2':
|
||||
db_connection.autocommit = True
|
||||
else:
|
||||
db_connection.set_isolation_level(psycopg2
|
||||
.extensions
|
||||
.ISOLATION_LEVEL_AUTOCOMMIT)
|
||||
cursor = db_connection.cursor(
|
||||
cursor_factory=psycopg2.extras.DictCursor)
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="unable to connect to database: %s" %(text, str(e)))
|
||||
|
||||
try:
|
||||
if module.check_mode:
|
||||
if state == "absent":
|
||||
changed = not schema_exists(cursor, schema)
|
||||
elif state == "present":
|
||||
changed = not schema_matches(cursor, schema, owner)
|
||||
module.exit_json(changed=changed, schema=schema)
|
||||
|
||||
if state == "absent":
|
||||
try:
|
||||
changed = schema_delete(cursor, schema)
|
||||
except SQLParseError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
elif state == "present":
|
||||
try:
|
||||
changed = schema_create(cursor, schema, owner)
|
||||
except SQLParseError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e))
|
||||
except NotSupportedError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e))
|
||||
except SystemExit:
|
||||
# Avoid catching this on Python 2.4
|
||||
raise
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="Database query failed: %s" %(text, str(e)))
|
||||
|
||||
module.exit_json(changed=changed, schema=schema)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.database import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
0
lib/ansible/modules/database/vertica/__init__.py
Normal file
0
lib/ansible/modules/database/vertica/__init__.py
Normal file
206
lib/ansible/modules/database/vertica/vertica_configuration.py
Normal file
206
lib/ansible/modules/database/vertica/vertica_configuration.py
Normal file
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: vertica_configuration
|
||||
version_added: '2.0'
|
||||
short_description: Updates Vertica configuration parameters.
|
||||
description:
|
||||
- Updates Vertica configuration parameters.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the parameter to update.
|
||||
required: true
|
||||
value:
|
||||
description:
|
||||
- Value of the parameter to be set.
|
||||
required: true
|
||||
db:
|
||||
description:
|
||||
- Name of the Vertica database.
|
||||
required: false
|
||||
default: null
|
||||
cluster:
|
||||
description:
|
||||
- Name of the Vertica cluster.
|
||||
required: false
|
||||
default: localhost
|
||||
port:
|
||||
description:
|
||||
- Vertica cluster port to connect to.
|
||||
required: false
|
||||
default: 5433
|
||||
login_user:
|
||||
description:
|
||||
- The username used to authenticate with.
|
||||
required: false
|
||||
default: dbadmin
|
||||
login_password:
|
||||
description:
|
||||
- The password used to authenticate with.
|
||||
required: false
|
||||
default: null
|
||||
notes:
|
||||
- The default authentication assumes that you are either logging in as or sudo'ing
|
||||
to the C(dbadmin) account on the host.
|
||||
- This module uses C(pyodbc), a Python ODBC database adapter. You must ensure
|
||||
that C(unixODBC) and C(pyodbc) is installed on the host and properly configured.
|
||||
- Configuring C(unixODBC) for Vertica requires C(Driver = /opt/vertica/lib64/libverticaodbc.so)
|
||||
to be added to the C(Vertica) section of either C(/etc/odbcinst.ini) or C($HOME/.odbcinst.ini)
|
||||
and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16)
|
||||
to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini).
|
||||
requirements: [ 'unixODBC', 'pyodbc' ]
|
||||
author: "Dariusz Owczarek (@dareko)"
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: updating load_balance_policy
|
||||
vertica_configuration: name=failovertostandbyafter value='8 hours'
|
||||
"""
|
||||
|
||||
try:
|
||||
import pyodbc
|
||||
except ImportError:
|
||||
pyodbc_found = False
|
||||
else:
|
||||
pyodbc_found = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.pycompat24 import get_exception
|
||||
|
||||
|
||||
class NotSupportedError(Exception):
|
||||
pass
|
||||
|
||||
class CannotDropError(Exception):
|
||||
pass
|
||||
|
||||
# module specific functions
|
||||
|
||||
def get_configuration_facts(cursor, parameter_name=''):
|
||||
facts = {}
|
||||
cursor.execute("""
|
||||
select c.parameter_name, c.current_value, c.default_value
|
||||
from configuration_parameters c
|
||||
where c.node_name = 'ALL'
|
||||
and (? = '' or c.parameter_name ilike ?)
|
||||
""", parameter_name, parameter_name)
|
||||
while True:
|
||||
rows = cursor.fetchmany(100)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
facts[row.parameter_name.lower()] = {
|
||||
'parameter_name': row.parameter_name,
|
||||
'current_value': row.current_value,
|
||||
'default_value': row.default_value}
|
||||
return facts
|
||||
|
||||
def check(configuration_facts, parameter_name, current_value):
|
||||
parameter_key = parameter_name.lower()
|
||||
if current_value and current_value.lower() != configuration_facts[parameter_key]['current_value'].lower():
|
||||
return False
|
||||
return True
|
||||
|
||||
def present(configuration_facts, cursor, parameter_name, current_value):
|
||||
parameter_key = parameter_name.lower()
|
||||
changed = False
|
||||
if current_value and current_value.lower() != configuration_facts[parameter_key]['current_value'].lower():
|
||||
cursor.execute("select set_config_parameter('{0}', '{1}')".format(parameter_name, current_value))
|
||||
changed = True
|
||||
if changed:
|
||||
configuration_facts.update(get_configuration_facts(cursor, parameter_name))
|
||||
return changed
|
||||
|
||||
# module logic
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
parameter=dict(required=True, aliases=['name']),
|
||||
value=dict(default=None),
|
||||
db=dict(default=None),
|
||||
cluster=dict(default='localhost'),
|
||||
port=dict(default='5433'),
|
||||
login_user=dict(default='dbadmin'),
|
||||
login_password=dict(default=None),
|
||||
), supports_check_mode = True)
|
||||
|
||||
if not pyodbc_found:
|
||||
module.fail_json(msg="The python pyodbc module is required.")
|
||||
|
||||
parameter_name = module.params['parameter']
|
||||
current_value = module.params['value']
|
||||
db = ''
|
||||
if module.params['db']:
|
||||
db = module.params['db']
|
||||
|
||||
changed = False
|
||||
|
||||
try:
|
||||
dsn = (
|
||||
"Driver=Vertica;"
|
||||
"Server={0};"
|
||||
"Port={1};"
|
||||
"Database={2};"
|
||||
"User={3};"
|
||||
"Password={4};"
|
||||
"ConnectionLoadBalance={5}"
|
||||
).format(module.params['cluster'], module.params['port'], db,
|
||||
module.params['login_user'], module.params['login_password'], 'true')
|
||||
db_conn = pyodbc.connect(dsn, autocommit=True)
|
||||
cursor = db_conn.cursor()
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="Unable to connect to database: {0}.".format(e))
|
||||
|
||||
try:
|
||||
configuration_facts = get_configuration_facts(cursor)
|
||||
if module.check_mode:
|
||||
changed = not check(configuration_facts, parameter_name, current_value)
|
||||
else:
|
||||
try:
|
||||
changed = present(configuration_facts, cursor, parameter_name, current_value)
|
||||
except pyodbc.Error:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e))
|
||||
except NotSupportedError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e), ansible_facts={'vertica_configuration': configuration_facts})
|
||||
except CannotDropError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e), ansible_facts={'vertica_configuration': configuration_facts})
|
||||
except SystemExit:
|
||||
# avoid catching this on python 2.4
|
||||
raise
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=e)
|
||||
|
||||
module.exit_json(changed=changed, parameter=parameter_name, ansible_facts={'vertica_configuration': configuration_facts})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
284
lib/ansible/modules/database/vertica/vertica_facts.py
Normal file
284
lib/ansible/modules/database/vertica/vertica_facts.py
Normal file
@@ -0,0 +1,284 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: vertica_facts
|
||||
version_added: '2.0'
|
||||
short_description: Gathers Vertica database facts.
|
||||
description:
|
||||
- Gathers Vertica database facts.
|
||||
options:
|
||||
cluster:
|
||||
description:
|
||||
- Name of the cluster running the schema.
|
||||
required: false
|
||||
default: localhost
|
||||
port:
|
||||
description:
|
||||
Database port to connect to.
|
||||
required: false
|
||||
default: 5433
|
||||
db:
|
||||
description:
|
||||
- Name of the database running the schema.
|
||||
required: false
|
||||
default: null
|
||||
login_user:
|
||||
description:
|
||||
- The username used to authenticate with.
|
||||
required: false
|
||||
default: dbadmin
|
||||
login_password:
|
||||
description:
|
||||
- The password used to authenticate with.
|
||||
required: false
|
||||
default: null
|
||||
notes:
|
||||
- The default authentication assumes that you are either logging in as or sudo'ing
|
||||
to the C(dbadmin) account on the host.
|
||||
- This module uses C(pyodbc), a Python ODBC database adapter. You must ensure
|
||||
that C(unixODBC) and C(pyodbc) is installed on the host and properly configured.
|
||||
- Configuring C(unixODBC) for Vertica requires C(Driver = /opt/vertica/lib64/libverticaodbc.so)
|
||||
to be added to the C(Vertica) section of either C(/etc/odbcinst.ini) or C($HOME/.odbcinst.ini)
|
||||
and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16)
|
||||
to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini).
|
||||
requirements: [ 'unixODBC', 'pyodbc' ]
|
||||
author: "Dariusz Owczarek (@dareko)"
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: gathering vertica facts
|
||||
vertica_facts: db=db_name
|
||||
"""
|
||||
|
||||
try:
|
||||
import pyodbc
|
||||
except ImportError:
|
||||
pyodbc_found = False
|
||||
else:
|
||||
pyodbc_found = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.pycompat24 import get_exception
|
||||
|
||||
|
||||
class NotSupportedError(Exception):
|
||||
pass
|
||||
|
||||
# module specific functions
|
||||
|
||||
def get_schema_facts(cursor, schema=''):
|
||||
facts = {}
|
||||
cursor.execute("""
|
||||
select schema_name, schema_owner, create_time
|
||||
from schemata
|
||||
where not is_system_schema and schema_name not in ('public')
|
||||
and (? = '' or schema_name ilike ?)
|
||||
""", schema, schema)
|
||||
while True:
|
||||
rows = cursor.fetchmany(100)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
facts[row.schema_name.lower()] = {
|
||||
'name': row.schema_name,
|
||||
'owner': row.schema_owner,
|
||||
'create_time': str(row.create_time),
|
||||
'usage_roles': [],
|
||||
'create_roles': []}
|
||||
cursor.execute("""
|
||||
select g.object_name as schema_name, r.name as role_name,
|
||||
lower(g.privileges_description) privileges_description
|
||||
from roles r join grants g
|
||||
on g.grantee = r.name and g.object_type='SCHEMA'
|
||||
and g.privileges_description like '%USAGE%'
|
||||
and g.grantee not in ('public', 'dbadmin')
|
||||
and (? = '' or g.object_name ilike ?)
|
||||
""", schema, schema)
|
||||
while True:
|
||||
rows = cursor.fetchmany(100)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
schema_key = row.schema_name.lower()
|
||||
if 'create' in row.privileges_description:
|
||||
facts[schema_key]['create_roles'].append(row.role_name)
|
||||
else:
|
||||
facts[schema_key]['usage_roles'].append(row.role_name)
|
||||
return facts
|
||||
|
||||
def get_user_facts(cursor, user=''):
|
||||
facts = {}
|
||||
cursor.execute("""
|
||||
select u.user_name, u.is_locked, u.lock_time,
|
||||
p.password, p.acctexpired as is_expired,
|
||||
u.profile_name, u.resource_pool,
|
||||
u.all_roles, u.default_roles
|
||||
from users u join password_auditor p on p.user_id = u.user_id
|
||||
where not u.is_super_user
|
||||
and (? = '' or u.user_name ilike ?)
|
||||
""", user, user)
|
||||
while True:
|
||||
rows = cursor.fetchmany(100)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
user_key = row.user_name.lower()
|
||||
facts[user_key] = {
|
||||
'name': row.user_name,
|
||||
'locked': str(row.is_locked),
|
||||
'password': row.password,
|
||||
'expired': str(row.is_expired),
|
||||
'profile': row.profile_name,
|
||||
'resource_pool': row.resource_pool,
|
||||
'roles': [],
|
||||
'default_roles': []}
|
||||
if row.is_locked:
|
||||
facts[user_key]['locked_time'] = str(row.lock_time)
|
||||
if row.all_roles:
|
||||
facts[user_key]['roles'] = row.all_roles.replace(' ', '').split(',')
|
||||
if row.default_roles:
|
||||
facts[user_key]['default_roles'] = row.default_roles.replace(' ', '').split(',')
|
||||
return facts
|
||||
|
||||
def get_role_facts(cursor, role=''):
|
||||
facts = {}
|
||||
cursor.execute("""
|
||||
select r.name, r.assigned_roles
|
||||
from roles r
|
||||
where (? = '' or r.name ilike ?)
|
||||
""", role, role)
|
||||
while True:
|
||||
rows = cursor.fetchmany(100)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
role_key = row.name.lower()
|
||||
facts[role_key] = {
|
||||
'name': row.name,
|
||||
'assigned_roles': []}
|
||||
if row.assigned_roles:
|
||||
facts[role_key]['assigned_roles'] = row.assigned_roles.replace(' ', '').split(',')
|
||||
return facts
|
||||
|
||||
def get_configuration_facts(cursor, parameter=''):
|
||||
facts = {}
|
||||
cursor.execute("""
|
||||
select c.parameter_name, c.current_value, c.default_value
|
||||
from configuration_parameters c
|
||||
where c.node_name = 'ALL'
|
||||
and (? = '' or c.parameter_name ilike ?)
|
||||
""", parameter, parameter)
|
||||
while True:
|
||||
rows = cursor.fetchmany(100)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
facts[row.parameter_name.lower()] = {
|
||||
'parameter_name': row.parameter_name,
|
||||
'current_value': row.current_value,
|
||||
'default_value': row.default_value}
|
||||
return facts
|
||||
|
||||
def get_node_facts(cursor, schema=''):
|
||||
facts = {}
|
||||
cursor.execute("""
|
||||
select node_name, node_address, export_address, node_state, node_type,
|
||||
catalog_path
|
||||
from nodes
|
||||
""")
|
||||
while True:
|
||||
rows = cursor.fetchmany(100)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
facts[row.node_address] = {
|
||||
'node_name': row.node_name,
|
||||
'export_address': row.export_address,
|
||||
'node_state': row.node_state,
|
||||
'node_type': row.node_type,
|
||||
'catalog_path': row.catalog_path}
|
||||
return facts
|
||||
|
||||
# module logic
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
cluster=dict(default='localhost'),
|
||||
port=dict(default='5433'),
|
||||
db=dict(default=None),
|
||||
login_user=dict(default='dbadmin'),
|
||||
login_password=dict(default=None),
|
||||
), supports_check_mode = True)
|
||||
|
||||
if not pyodbc_found:
|
||||
module.fail_json(msg="The python pyodbc module is required.")
|
||||
|
||||
db = ''
|
||||
if module.params['db']:
|
||||
db = module.params['db']
|
||||
|
||||
try:
|
||||
dsn = (
|
||||
"Driver=Vertica;"
|
||||
"Server=%s;"
|
||||
"Port=%s;"
|
||||
"Database=%s;"
|
||||
"User=%s;"
|
||||
"Password=%s;"
|
||||
"ConnectionLoadBalance=%s"
|
||||
) % (module.params['cluster'], module.params['port'], db,
|
||||
module.params['login_user'], module.params['login_password'], 'true')
|
||||
db_conn = pyodbc.connect(dsn, autocommit=True)
|
||||
cursor = db_conn.cursor()
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="Unable to connect to database: %s." % str(e))
|
||||
|
||||
try:
|
||||
schema_facts = get_schema_facts(cursor)
|
||||
user_facts = get_user_facts(cursor)
|
||||
role_facts = get_role_facts(cursor)
|
||||
configuration_facts = get_configuration_facts(cursor)
|
||||
node_facts = get_node_facts(cursor)
|
||||
module.exit_json(changed=False,
|
||||
ansible_facts={'vertica_schemas': schema_facts,
|
||||
'vertica_users': user_facts,
|
||||
'vertica_roles': role_facts,
|
||||
'vertica_configuration': configuration_facts,
|
||||
'vertica_nodes': node_facts})
|
||||
except NotSupportedError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e))
|
||||
except SystemExit:
|
||||
# avoid catching this on python 2.4
|
||||
raise
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
256
lib/ansible/modules/database/vertica/vertica_role.py
Normal file
256
lib/ansible/modules/database/vertica/vertica_role.py
Normal file
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: vertica_role
|
||||
version_added: '2.0'
|
||||
short_description: Adds or removes Vertica database roles and assigns roles to them.
|
||||
description:
|
||||
- Adds or removes Vertica database role and, optionally, assign other roles.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the role to add or remove.
|
||||
required: true
|
||||
assigned_roles:
|
||||
description:
|
||||
- Comma separated list of roles to assign to the role.
|
||||
aliases: ['assigned_role']
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- Whether to create C(present), drop C(absent) or lock C(locked) a role.
|
||||
required: false
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
db:
|
||||
description:
|
||||
- Name of the Vertica database.
|
||||
required: false
|
||||
default: null
|
||||
cluster:
|
||||
description:
|
||||
- Name of the Vertica cluster.
|
||||
required: false
|
||||
default: localhost
|
||||
port:
|
||||
description:
|
||||
- Vertica cluster port to connect to.
|
||||
required: false
|
||||
default: 5433
|
||||
login_user:
|
||||
description:
|
||||
- The username used to authenticate with.
|
||||
required: false
|
||||
default: dbadmin
|
||||
login_password:
|
||||
description:
|
||||
- The password used to authenticate with.
|
||||
required: false
|
||||
default: null
|
||||
notes:
|
||||
- The default authentication assumes that you are either logging in as or sudo'ing
|
||||
to the C(dbadmin) account on the host.
|
||||
- This module uses C(pyodbc), a Python ODBC database adapter. You must ensure
|
||||
that C(unixODBC) and C(pyodbc) is installed on the host and properly configured.
|
||||
- Configuring C(unixODBC) for Vertica requires C(Driver = /opt/vertica/lib64/libverticaodbc.so)
|
||||
to be added to the C(Vertica) section of either C(/etc/odbcinst.ini) or C($HOME/.odbcinst.ini)
|
||||
and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16)
|
||||
to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini).
|
||||
requirements: [ 'unixODBC', 'pyodbc' ]
|
||||
author: "Dariusz Owczarek (@dareko)"
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: creating a new vertica role
|
||||
vertica_role: name=role_name db=db_name state=present
|
||||
|
||||
- name: creating a new vertica role with other role assigned
|
||||
vertica_role: name=role_name assigned_role=other_role_name state=present
|
||||
"""
|
||||
|
||||
try:
|
||||
import pyodbc
|
||||
except ImportError:
|
||||
pyodbc_found = False
|
||||
else:
|
||||
pyodbc_found = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.pycompat24 import get_exception
|
||||
|
||||
|
||||
class NotSupportedError(Exception):
|
||||
pass
|
||||
|
||||
class CannotDropError(Exception):
|
||||
pass
|
||||
|
||||
# module specific functions
|
||||
|
||||
def get_role_facts(cursor, role=''):
|
||||
facts = {}
|
||||
cursor.execute("""
|
||||
select r.name, r.assigned_roles
|
||||
from roles r
|
||||
where (? = '' or r.name ilike ?)
|
||||
""", role, role)
|
||||
while True:
|
||||
rows = cursor.fetchmany(100)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
role_key = row.name.lower()
|
||||
facts[role_key] = {
|
||||
'name': row.name,
|
||||
'assigned_roles': []}
|
||||
if row.assigned_roles:
|
||||
facts[role_key]['assigned_roles'] = row.assigned_roles.replace(' ', '').split(',')
|
||||
return facts
|
||||
|
||||
def update_roles(role_facts, cursor, role,
|
||||
existing, required):
|
||||
for assigned_role in set(existing) - set(required):
|
||||
cursor.execute("revoke {0} from {1}".format(assigned_role, role))
|
||||
for assigned_role in set(required) - set(existing):
|
||||
cursor.execute("grant {0} to {1}".format(assigned_role, role))
|
||||
|
||||
def check(role_facts, role, assigned_roles):
|
||||
role_key = role.lower()
|
||||
if role_key not in role_facts:
|
||||
return False
|
||||
if assigned_roles and cmp(sorted(assigned_roles), sorted(role_facts[role_key]['assigned_roles'])) != 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
def present(role_facts, cursor, role, assigned_roles):
|
||||
role_key = role.lower()
|
||||
if role_key not in role_facts:
|
||||
cursor.execute("create role {0}".format(role))
|
||||
update_roles(role_facts, cursor, role, [], assigned_roles)
|
||||
role_facts.update(get_role_facts(cursor, role))
|
||||
return True
|
||||
else:
|
||||
changed = False
|
||||
if assigned_roles and cmp(sorted(assigned_roles), sorted(role_facts[role_key]['assigned_roles'])) != 0:
|
||||
update_roles(role_facts, cursor, role,
|
||||
role_facts[role_key]['assigned_roles'], assigned_roles)
|
||||
changed = True
|
||||
if changed:
|
||||
role_facts.update(get_role_facts(cursor, role))
|
||||
return changed
|
||||
|
||||
def absent(role_facts, cursor, role, assigned_roles):
|
||||
role_key = role.lower()
|
||||
if role_key in role_facts:
|
||||
update_roles(role_facts, cursor, role,
|
||||
role_facts[role_key]['assigned_roles'], [])
|
||||
cursor.execute("drop role {0} cascade".format(role_facts[role_key]['name']))
|
||||
del role_facts[role_key]
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# module logic
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
role=dict(required=True, aliases=['name']),
|
||||
assigned_roles=dict(default=None, aliases=['assigned_role']),
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
db=dict(default=None),
|
||||
cluster=dict(default='localhost'),
|
||||
port=dict(default='5433'),
|
||||
login_user=dict(default='dbadmin'),
|
||||
login_password=dict(default=None),
|
||||
), supports_check_mode = True)
|
||||
|
||||
if not pyodbc_found:
|
||||
module.fail_json(msg="The python pyodbc module is required.")
|
||||
|
||||
role = module.params['role']
|
||||
assigned_roles = []
|
||||
if module.params['assigned_roles']:
|
||||
assigned_roles = module.params['assigned_roles'].split(',')
|
||||
assigned_roles = filter(None, assigned_roles)
|
||||
state = module.params['state']
|
||||
db = ''
|
||||
if module.params['db']:
|
||||
db = module.params['db']
|
||||
|
||||
changed = False
|
||||
|
||||
try:
|
||||
dsn = (
|
||||
"Driver=Vertica;"
|
||||
"Server={0};"
|
||||
"Port={1};"
|
||||
"Database={2};"
|
||||
"User={3};"
|
||||
"Password={4};"
|
||||
"ConnectionLoadBalance={5}"
|
||||
).format(module.params['cluster'], module.params['port'], db,
|
||||
module.params['login_user'], module.params['login_password'], 'true')
|
||||
db_conn = pyodbc.connect(dsn, autocommit=True)
|
||||
cursor = db_conn.cursor()
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="Unable to connect to database: {0}.".format(e))
|
||||
|
||||
try:
|
||||
role_facts = get_role_facts(cursor)
|
||||
if module.check_mode:
|
||||
changed = not check(role_facts, role, assigned_roles)
|
||||
elif state == 'absent':
|
||||
try:
|
||||
changed = absent(role_facts, cursor, role, assigned_roles)
|
||||
except pyodbc.Error:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e))
|
||||
elif state == 'present':
|
||||
try:
|
||||
changed = present(role_facts, cursor, role, assigned_roles)
|
||||
except pyodbc.Error:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e))
|
||||
except NotSupportedError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e), ansible_facts={'vertica_roles': role_facts})
|
||||
except CannotDropError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e), ansible_facts={'vertica_roles': role_facts})
|
||||
except SystemExit:
|
||||
# avoid catching this on python 2.4
|
||||
raise
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=e)
|
||||
|
||||
module.exit_json(changed=changed, role=role, ansible_facts={'vertica_roles': role_facts})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
330
lib/ansible/modules/database/vertica/vertica_schema.py
Normal file
330
lib/ansible/modules/database/vertica/vertica_schema.py
Normal file
@@ -0,0 +1,330 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: vertica_schema
|
||||
version_added: '2.0'
|
||||
short_description: Adds or removes Vertica database schema and roles.
|
||||
description:
|
||||
- Adds or removes Vertica database schema and, optionally, roles
|
||||
with schema access privileges.
|
||||
- A schema will not be removed until all the objects have been dropped.
|
||||
- In such a situation, if the module tries to remove the schema it
|
||||
will fail and only remove roles created for the schema if they have
|
||||
no dependencies.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the schema to add or remove.
|
||||
required: true
|
||||
usage_roles:
|
||||
description:
|
||||
- Comma separated list of roles to create and grant usage access to the schema.
|
||||
aliases: ['usage_role']
|
||||
required: false
|
||||
default: null
|
||||
create_roles:
|
||||
description:
|
||||
- Comma separated list of roles to create and grant usage and create access to the schema.
|
||||
aliases: ['create_role']
|
||||
required: false
|
||||
default: null
|
||||
owner:
|
||||
description:
|
||||
- Name of the user to set as owner of the schema.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- Whether to create C(present), or drop C(absent) a schema.
|
||||
required: false
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
db:
|
||||
description:
|
||||
- Name of the Vertica database.
|
||||
required: false
|
||||
default: null
|
||||
cluster:
|
||||
description:
|
||||
- Name of the Vertica cluster.
|
||||
required: false
|
||||
default: localhost
|
||||
port:
|
||||
description:
|
||||
- Vertica cluster port to connect to.
|
||||
required: false
|
||||
default: 5433
|
||||
login_user:
|
||||
description:
|
||||
- The username used to authenticate with.
|
||||
required: false
|
||||
default: dbadmin
|
||||
login_password:
|
||||
description:
|
||||
- The password used to authenticate with.
|
||||
required: false
|
||||
default: null
|
||||
notes:
|
||||
- The default authentication assumes that you are either logging in as or sudo'ing
|
||||
to the C(dbadmin) account on the host.
|
||||
- This module uses C(pyodbc), a Python ODBC database adapter. You must ensure
|
||||
that C(unixODBC) and C(pyodbc) is installed on the host and properly configured.
|
||||
- Configuring C(unixODBC) for Vertica requires C(Driver = /opt/vertica/lib64/libverticaodbc.so)
|
||||
to be added to the C(Vertica) section of either C(/etc/odbcinst.ini) or C($HOME/.odbcinst.ini)
|
||||
and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16)
|
||||
to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini).
|
||||
requirements: [ 'unixODBC', 'pyodbc' ]
|
||||
author: "Dariusz Owczarek (@dareko)"
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: creating a new vertica schema
|
||||
vertica_schema: name=schema_name db=db_name state=present
|
||||
|
||||
- name: creating a new schema with specific schema owner
|
||||
vertica_schema: name=schema_name owner=dbowner db=db_name state=present
|
||||
|
||||
- name: creating a new schema with roles
|
||||
vertica_schema:
|
||||
name=schema_name
|
||||
create_roles=schema_name_all
|
||||
usage_roles=schema_name_ro,schema_name_rw
|
||||
db=db_name
|
||||
state=present
|
||||
"""
|
||||
|
||||
try:
|
||||
import pyodbc
|
||||
except ImportError:
|
||||
pyodbc_found = False
|
||||
else:
|
||||
pyodbc_found = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.pycompat24 import get_exception
|
||||
|
||||
|
||||
class NotSupportedError(Exception):
|
||||
pass
|
||||
|
||||
class CannotDropError(Exception):
|
||||
pass
|
||||
|
||||
# module specific functions
|
||||
|
||||
def get_schema_facts(cursor, schema=''):
|
||||
facts = {}
|
||||
cursor.execute("""
|
||||
select schema_name, schema_owner, create_time
|
||||
from schemata
|
||||
where not is_system_schema and schema_name not in ('public', 'TxtIndex')
|
||||
and (? = '' or schema_name ilike ?)
|
||||
""", schema, schema)
|
||||
while True:
|
||||
rows = cursor.fetchmany(100)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
facts[row.schema_name.lower()] = {
|
||||
'name': row.schema_name,
|
||||
'owner': row.schema_owner,
|
||||
'create_time': str(row.create_time),
|
||||
'usage_roles': [],
|
||||
'create_roles': []}
|
||||
cursor.execute("""
|
||||
select g.object_name as schema_name, r.name as role_name,
|
||||
lower(g.privileges_description) privileges_description
|
||||
from roles r join grants g
|
||||
on g.grantee_id = r.role_id and g.object_type='SCHEMA'
|
||||
and g.privileges_description like '%USAGE%'
|
||||
and g.grantee not in ('public', 'dbadmin')
|
||||
and (? = '' or g.object_name ilike ?)
|
||||
""", schema, schema)
|
||||
while True:
|
||||
rows = cursor.fetchmany(100)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
schema_key = row.schema_name.lower()
|
||||
if 'create' in row.privileges_description:
|
||||
facts[schema_key]['create_roles'].append(row.role_name)
|
||||
else:
|
||||
facts[schema_key]['usage_roles'].append(row.role_name)
|
||||
return facts
|
||||
|
||||
def update_roles(schema_facts, cursor, schema,
|
||||
existing, required,
|
||||
create_existing, create_required):
|
||||
for role in set(existing + create_existing) - set(required + create_required):
|
||||
cursor.execute("drop role {0} cascade".format(role))
|
||||
for role in set(create_existing) - set(create_required):
|
||||
cursor.execute("revoke create on schema {0} from {1}".format(schema, role))
|
||||
for role in set(required + create_required) - set(existing + create_existing):
|
||||
cursor.execute("create role {0}".format(role))
|
||||
cursor.execute("grant usage on schema {0} to {1}".format(schema, role))
|
||||
for role in set(create_required) - set(create_existing):
|
||||
cursor.execute("grant create on schema {0} to {1}".format(schema, role))
|
||||
|
||||
def check(schema_facts, schema, usage_roles, create_roles, owner):
|
||||
schema_key = schema.lower()
|
||||
if schema_key not in schema_facts:
|
||||
return False
|
||||
if owner and owner.lower() == schema_facts[schema_key]['owner'].lower():
|
||||
return False
|
||||
if cmp(sorted(usage_roles), sorted(schema_facts[schema_key]['usage_roles'])) != 0:
|
||||
return False
|
||||
if cmp(sorted(create_roles), sorted(schema_facts[schema_key]['create_roles'])) != 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
def present(schema_facts, cursor, schema, usage_roles, create_roles, owner):
|
||||
schema_key = schema.lower()
|
||||
if schema_key not in schema_facts:
|
||||
query_fragments = ["create schema {0}".format(schema)]
|
||||
if owner:
|
||||
query_fragments.append("authorization {0}".format(owner))
|
||||
cursor.execute(' '.join(query_fragments))
|
||||
update_roles(schema_facts, cursor, schema, [], usage_roles, [], create_roles)
|
||||
schema_facts.update(get_schema_facts(cursor, schema))
|
||||
return True
|
||||
else:
|
||||
changed = False
|
||||
if owner and owner.lower() != schema_facts[schema_key]['owner'].lower():
|
||||
raise NotSupportedError((
|
||||
"Changing schema owner is not supported. "
|
||||
"Current owner: {0}."
|
||||
).format(schema_facts[schema_key]['owner']))
|
||||
if cmp(sorted(usage_roles), sorted(schema_facts[schema_key]['usage_roles'])) != 0 or \
|
||||
cmp(sorted(create_roles), sorted(schema_facts[schema_key]['create_roles'])) != 0:
|
||||
update_roles(schema_facts, cursor, schema,
|
||||
schema_facts[schema_key]['usage_roles'], usage_roles,
|
||||
schema_facts[schema_key]['create_roles'], create_roles)
|
||||
changed = True
|
||||
if changed:
|
||||
schema_facts.update(get_schema_facts(cursor, schema))
|
||||
return changed
|
||||
|
||||
def absent(schema_facts, cursor, schema, usage_roles, create_roles):
|
||||
schema_key = schema.lower()
|
||||
if schema_key in schema_facts:
|
||||
update_roles(schema_facts, cursor, schema,
|
||||
schema_facts[schema_key]['usage_roles'], [], schema_facts[schema_key]['create_roles'], [])
|
||||
try:
|
||||
cursor.execute("drop schema {0} restrict".format(schema_facts[schema_key]['name']))
|
||||
except pyodbc.Error:
|
||||
raise CannotDropError("Dropping schema failed due to dependencies.")
|
||||
del schema_facts[schema_key]
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# module logic
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
schema=dict(required=True, aliases=['name']),
|
||||
usage_roles=dict(default=None, aliases=['usage_role']),
|
||||
create_roles=dict(default=None, aliases=['create_role']),
|
||||
owner=dict(default=None),
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
db=dict(default=None),
|
||||
cluster=dict(default='localhost'),
|
||||
port=dict(default='5433'),
|
||||
login_user=dict(default='dbadmin'),
|
||||
login_password=dict(default=None),
|
||||
), supports_check_mode = True)
|
||||
|
||||
if not pyodbc_found:
|
||||
module.fail_json(msg="The python pyodbc module is required.")
|
||||
|
||||
schema = module.params['schema']
|
||||
usage_roles = []
|
||||
if module.params['usage_roles']:
|
||||
usage_roles = module.params['usage_roles'].split(',')
|
||||
usage_roles = filter(None, usage_roles)
|
||||
create_roles = []
|
||||
if module.params['create_roles']:
|
||||
create_roles = module.params['create_roles'].split(',')
|
||||
create_roles = filter(None, create_roles)
|
||||
owner = module.params['owner']
|
||||
state = module.params['state']
|
||||
db = ''
|
||||
if module.params['db']:
|
||||
db = module.params['db']
|
||||
|
||||
changed = False
|
||||
|
||||
try:
|
||||
dsn = (
|
||||
"Driver=Vertica;"
|
||||
"Server={0};"
|
||||
"Port={1};"
|
||||
"Database={2};"
|
||||
"User={3};"
|
||||
"Password={4};"
|
||||
"ConnectionLoadBalance={5}"
|
||||
).format(module.params['cluster'], module.params['port'], db,
|
||||
module.params['login_user'], module.params['login_password'], 'true')
|
||||
db_conn = pyodbc.connect(dsn, autocommit=True)
|
||||
cursor = db_conn.cursor()
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="Unable to connect to database: {0}.".format(e))
|
||||
|
||||
try:
|
||||
schema_facts = get_schema_facts(cursor)
|
||||
if module.check_mode:
|
||||
changed = not check(schema_facts, schema, usage_roles, create_roles, owner)
|
||||
elif state == 'absent':
|
||||
try:
|
||||
changed = absent(schema_facts, cursor, schema, usage_roles, create_roles)
|
||||
except pyodbc.Error:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e))
|
||||
elif state == 'present':
|
||||
try:
|
||||
changed = present(schema_facts, cursor, schema, usage_roles, create_roles, owner)
|
||||
except pyodbc.Error:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e))
|
||||
except NotSupportedError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e), ansible_facts={'vertica_schemas': schema_facts})
|
||||
except CannotDropError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e), ansible_facts={'vertica_schemas': schema_facts})
|
||||
except SystemExit:
|
||||
# avoid catching this on python 2.4
|
||||
raise
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=e)
|
||||
|
||||
module.exit_json(changed=changed, schema=schema, ansible_facts={'vertica_schemas': schema_facts})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
401
lib/ansible/modules/database/vertica/vertica_user.py
Normal file
401
lib/ansible/modules/database/vertica/vertica_user.py
Normal file
@@ -0,0 +1,401 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: vertica_user
|
||||
version_added: '2.0'
|
||||
short_description: Adds or removes Vertica database users and assigns roles.
|
||||
description:
|
||||
- Adds or removes Vertica database user and, optionally, assigns roles.
|
||||
- A user will not be removed until all the dependencies have been dropped.
|
||||
- In such a situation, if the module tries to remove the user it
|
||||
will fail and only remove roles granted to the user.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the user to add or remove.
|
||||
required: true
|
||||
profile:
|
||||
description:
|
||||
- Sets the user's profile.
|
||||
required: false
|
||||
default: null
|
||||
resource_pool:
|
||||
description:
|
||||
- Sets the user's resource pool.
|
||||
required: false
|
||||
default: null
|
||||
password:
|
||||
description:
|
||||
- The user's password encrypted by the MD5 algorithm.
|
||||
- The password must be generated with the format C("md5" + md5[password + username]),
|
||||
resulting in a total of 35 characters. An easy way to do this is by querying
|
||||
the Vertica database with select 'md5'||md5('<user_password><user_name>').
|
||||
required: false
|
||||
default: null
|
||||
expired:
|
||||
description:
|
||||
- Sets the user's password expiration.
|
||||
required: false
|
||||
default: null
|
||||
ldap:
|
||||
description:
|
||||
- Set to true if users are authenticated via LDAP.
|
||||
- The user will be created with password expired and set to I($ldap$).
|
||||
required: false
|
||||
default: null
|
||||
roles:
|
||||
description:
|
||||
- Comma separated list of roles to assign to the user.
|
||||
aliases: ['role']
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- Whether to create C(present), drop C(absent) or lock C(locked) a user.
|
||||
required: false
|
||||
choices: ['present', 'absent', 'locked']
|
||||
default: present
|
||||
db:
|
||||
description:
|
||||
- Name of the Vertica database.
|
||||
required: false
|
||||
default: null
|
||||
cluster:
|
||||
description:
|
||||
- Name of the Vertica cluster.
|
||||
required: false
|
||||
default: localhost
|
||||
port:
|
||||
description:
|
||||
- Vertica cluster port to connect to.
|
||||
required: false
|
||||
default: 5433
|
||||
login_user:
|
||||
description:
|
||||
- The username used to authenticate with.
|
||||
required: false
|
||||
default: dbadmin
|
||||
login_password:
|
||||
description:
|
||||
- The password used to authenticate with.
|
||||
required: false
|
||||
default: null
|
||||
notes:
|
||||
- The default authentication assumes that you are either logging in as or sudo'ing
|
||||
to the C(dbadmin) account on the host.
|
||||
- This module uses C(pyodbc), a Python ODBC database adapter. You must ensure
|
||||
that C(unixODBC) and C(pyodbc) is installed on the host and properly configured.
|
||||
- Configuring C(unixODBC) for Vertica requires C(Driver = /opt/vertica/lib64/libverticaodbc.so)
|
||||
to be added to the C(Vertica) section of either C(/etc/odbcinst.ini) or C($HOME/.odbcinst.ini)
|
||||
and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16)
|
||||
to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini).
|
||||
requirements: [ 'unixODBC', 'pyodbc' ]
|
||||
author: "Dariusz Owczarek (@dareko)"
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: creating a new vertica user with password
|
||||
vertica_user: name=user_name password=md5<encrypted_password> db=db_name state=present
|
||||
|
||||
- name: creating a new vertica user authenticated via ldap with roles assigned
|
||||
vertica_user:
|
||||
name=user_name
|
||||
ldap=true
|
||||
db=db_name
|
||||
roles=schema_name_ro
|
||||
state=present
|
||||
"""
|
||||
|
||||
try:
|
||||
import pyodbc
|
||||
except ImportError:
|
||||
pyodbc_found = False
|
||||
else:
|
||||
pyodbc_found = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.pycompat24 import get_exception
|
||||
|
||||
|
||||
class NotSupportedError(Exception):
|
||||
pass
|
||||
|
||||
class CannotDropError(Exception):
|
||||
pass
|
||||
|
||||
# module specific functions
|
||||
|
||||
def get_user_facts(cursor, user=''):
|
||||
facts = {}
|
||||
cursor.execute("""
|
||||
select u.user_name, u.is_locked, u.lock_time,
|
||||
p.password, p.acctexpired as is_expired,
|
||||
u.profile_name, u.resource_pool,
|
||||
u.all_roles, u.default_roles
|
||||
from users u join password_auditor p on p.user_id = u.user_id
|
||||
where not u.is_super_user
|
||||
and (? = '' or u.user_name ilike ?)
|
||||
""", user, user)
|
||||
while True:
|
||||
rows = cursor.fetchmany(100)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
user_key = row.user_name.lower()
|
||||
facts[user_key] = {
|
||||
'name': row.user_name,
|
||||
'locked': str(row.is_locked),
|
||||
'password': row.password,
|
||||
'expired': str(row.is_expired),
|
||||
'profile': row.profile_name,
|
||||
'resource_pool': row.resource_pool,
|
||||
'roles': [],
|
||||
'default_roles': []}
|
||||
if row.is_locked:
|
||||
facts[user_key]['locked_time'] = str(row.lock_time)
|
||||
if row.all_roles:
|
||||
facts[user_key]['roles'] = row.all_roles.replace(' ', '').split(',')
|
||||
if row.default_roles:
|
||||
facts[user_key]['default_roles'] = row.default_roles.replace(' ', '').split(',')
|
||||
return facts
|
||||
|
||||
def update_roles(user_facts, cursor, user,
|
||||
existing_all, existing_default, required):
|
||||
del_roles = list(set(existing_all) - set(required))
|
||||
if del_roles:
|
||||
cursor.execute("revoke {0} from {1}".format(','.join(del_roles), user))
|
||||
new_roles = list(set(required) - set(existing_all))
|
||||
if new_roles:
|
||||
cursor.execute("grant {0} to {1}".format(','.join(new_roles), user))
|
||||
if required:
|
||||
cursor.execute("alter user {0} default role {1}".format(user, ','.join(required)))
|
||||
|
||||
def check(user_facts, user, profile, resource_pool,
|
||||
locked, password, expired, ldap, roles):
|
||||
user_key = user.lower()
|
||||
if user_key not in user_facts:
|
||||
return False
|
||||
if profile and profile != user_facts[user_key]['profile']:
|
||||
return False
|
||||
if resource_pool and resource_pool != user_facts[user_key]['resource_pool']:
|
||||
return False
|
||||
if locked != (user_facts[user_key]['locked'] == 'True'):
|
||||
return False
|
||||
if password and password != user_facts[user_key]['password']:
|
||||
return False
|
||||
if expired is not None and expired != (user_facts[user_key]['expired'] == 'True') or \
|
||||
ldap is not None and ldap != (user_facts[user_key]['expired'] == 'True'):
|
||||
return False
|
||||
if roles and (cmp(sorted(roles), sorted(user_facts[user_key]['roles'])) != 0 or \
|
||||
cmp(sorted(roles), sorted(user_facts[user_key]['default_roles'])) != 0):
|
||||
return False
|
||||
return True
|
||||
|
||||
def present(user_facts, cursor, user, profile, resource_pool,
|
||||
locked, password, expired, ldap, roles):
|
||||
user_key = user.lower()
|
||||
if user_key not in user_facts:
|
||||
query_fragments = ["create user {0}".format(user)]
|
||||
if locked:
|
||||
query_fragments.append("account lock")
|
||||
if password or ldap:
|
||||
if password:
|
||||
query_fragments.append("identified by '{0}'".format(password))
|
||||
else:
|
||||
query_fragments.append("identified by '$ldap$'")
|
||||
if expired or ldap:
|
||||
query_fragments.append("password expire")
|
||||
if profile:
|
||||
query_fragments.append("profile {0}".format(profile))
|
||||
if resource_pool:
|
||||
query_fragments.append("resource pool {0}".format(resource_pool))
|
||||
cursor.execute(' '.join(query_fragments))
|
||||
if resource_pool and resource_pool != 'general':
|
||||
cursor.execute("grant usage on resource pool {0} to {1}".format(
|
||||
resource_pool, user))
|
||||
update_roles(user_facts, cursor, user, [], [], roles)
|
||||
user_facts.update(get_user_facts(cursor, user))
|
||||
return True
|
||||
else:
|
||||
changed = False
|
||||
query_fragments = ["alter user {0}".format(user)]
|
||||
if locked is not None and locked != (user_facts[user_key]['locked'] == 'True'):
|
||||
if locked:
|
||||
state = 'lock'
|
||||
else:
|
||||
state = 'unlock'
|
||||
query_fragments.append("account {0}".format(state))
|
||||
changed = True
|
||||
if password and password != user_facts[user_key]['password']:
|
||||
query_fragments.append("identified by '{0}'".format(password))
|
||||
changed = True
|
||||
if ldap:
|
||||
if ldap != (user_facts[user_key]['expired'] == 'True'):
|
||||
query_fragments.append("password expire")
|
||||
changed = True
|
||||
elif expired is not None and expired != (user_facts[user_key]['expired'] == 'True'):
|
||||
if expired:
|
||||
query_fragments.append("password expire")
|
||||
changed = True
|
||||
else:
|
||||
raise NotSupportedError("Unexpiring user password is not supported.")
|
||||
if profile and profile != user_facts[user_key]['profile']:
|
||||
query_fragments.append("profile {0}".format(profile))
|
||||
changed = True
|
||||
if resource_pool and resource_pool != user_facts[user_key]['resource_pool']:
|
||||
query_fragments.append("resource pool {0}".format(resource_pool))
|
||||
if user_facts[user_key]['resource_pool'] != 'general':
|
||||
cursor.execute("revoke usage on resource pool {0} from {1}".format(
|
||||
user_facts[user_key]['resource_pool'], user))
|
||||
if resource_pool != 'general':
|
||||
cursor.execute("grant usage on resource pool {0} to {1}".format(
|
||||
resource_pool, user))
|
||||
changed = True
|
||||
if changed:
|
||||
cursor.execute(' '.join(query_fragments))
|
||||
if roles and (cmp(sorted(roles), sorted(user_facts[user_key]['roles'])) != 0 or \
|
||||
cmp(sorted(roles), sorted(user_facts[user_key]['default_roles'])) != 0):
|
||||
update_roles(user_facts, cursor, user,
|
||||
user_facts[user_key]['roles'], user_facts[user_key]['default_roles'], roles)
|
||||
changed = True
|
||||
if changed:
|
||||
user_facts.update(get_user_facts(cursor, user))
|
||||
return changed
|
||||
|
||||
def absent(user_facts, cursor, user, roles):
|
||||
user_key = user.lower()
|
||||
if user_key in user_facts:
|
||||
update_roles(user_facts, cursor, user,
|
||||
user_facts[user_key]['roles'], user_facts[user_key]['default_roles'], [])
|
||||
try:
|
||||
cursor.execute("drop user {0}".format(user_facts[user_key]['name']))
|
||||
except pyodbc.Error:
|
||||
raise CannotDropError("Dropping user failed due to dependencies.")
|
||||
del user_facts[user_key]
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# module logic
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
user=dict(required=True, aliases=['name']),
|
||||
profile=dict(default=None),
|
||||
resource_pool=dict(default=None),
|
||||
password=dict(default=None),
|
||||
expired=dict(type='bool', default=None),
|
||||
ldap=dict(type='bool', default=None),
|
||||
roles=dict(default=None, aliases=['role']),
|
||||
state=dict(default='present', choices=['absent', 'present', 'locked']),
|
||||
db=dict(default=None),
|
||||
cluster=dict(default='localhost'),
|
||||
port=dict(default='5433'),
|
||||
login_user=dict(default='dbadmin'),
|
||||
login_password=dict(default=None),
|
||||
), supports_check_mode = True)
|
||||
|
||||
if not pyodbc_found:
|
||||
module.fail_json(msg="The python pyodbc module is required.")
|
||||
|
||||
user = module.params['user']
|
||||
profile = module.params['profile']
|
||||
if profile:
|
||||
profile = profile.lower()
|
||||
resource_pool = module.params['resource_pool']
|
||||
if resource_pool:
|
||||
resource_pool = resource_pool.lower()
|
||||
password = module.params['password']
|
||||
expired = module.params['expired']
|
||||
ldap = module.params['ldap']
|
||||
roles = []
|
||||
if module.params['roles']:
|
||||
roles = module.params['roles'].split(',')
|
||||
roles = filter(None, roles)
|
||||
state = module.params['state']
|
||||
if state == 'locked':
|
||||
locked = True
|
||||
else:
|
||||
locked = False
|
||||
db = ''
|
||||
if module.params['db']:
|
||||
db = module.params['db']
|
||||
|
||||
changed = False
|
||||
|
||||
try:
|
||||
dsn = (
|
||||
"Driver=Vertica;"
|
||||
"Server={0};"
|
||||
"Port={1};"
|
||||
"Database={2};"
|
||||
"User={3};"
|
||||
"Password={4};"
|
||||
"ConnectionLoadBalance={5}"
|
||||
).format(module.params['cluster'], module.params['port'], db,
|
||||
module.params['login_user'], module.params['login_password'], 'true')
|
||||
db_conn = pyodbc.connect(dsn, autocommit=True)
|
||||
cursor = db_conn.cursor()
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg="Unable to connect to database: {0}.".format(e))
|
||||
|
||||
try:
|
||||
user_facts = get_user_facts(cursor)
|
||||
if module.check_mode:
|
||||
changed = not check(user_facts, user, profile, resource_pool,
|
||||
locked, password, expired, ldap, roles)
|
||||
elif state == 'absent':
|
||||
try:
|
||||
changed = absent(user_facts, cursor, user, roles)
|
||||
except pyodbc.Error:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e))
|
||||
elif state in ['present', 'locked']:
|
||||
try:
|
||||
changed = present(user_facts, cursor, user, profile, resource_pool,
|
||||
locked, password, expired, ldap, roles)
|
||||
except pyodbc.Error:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e))
|
||||
except NotSupportedError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e), ansible_facts={'vertica_users': user_facts})
|
||||
except CannotDropError:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=str(e), ansible_facts={'vertica_users': user_facts})
|
||||
except SystemExit:
|
||||
# avoid catching this on python 2.4
|
||||
raise
|
||||
except Exception:
|
||||
e = get_exception()
|
||||
module.fail_json(msg=e)
|
||||
|
||||
module.exit_json(changed=changed, user=user, ansible_facts={'vertica_users': user_facts})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user