mirror of https://github.com/ansible/ansible.git
commit
bef4eee0aa
@ -0,0 +1,506 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# (c) 2015, Steve Gargan <steve.gargan@gmail.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/>.
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
module: consul
|
||||||
|
short_description: "Add, modify & delete services within a consul cluster.
|
||||||
|
See http://conul.io for more details."
|
||||||
|
description:
|
||||||
|
- registers services and checks for an agent with a consul cluster. A service
|
||||||
|
is some process running on the agent node that should be advertised by
|
||||||
|
consul's discovery mechanism. It may optionally supply a check definition,
|
||||||
|
a periodic service test to notify the consul cluster of service's health.
|
||||||
|
Checks may also be registered per node e.g. disk usage, or cpu usage and
|
||||||
|
notify the health of the entire node to the cluster.
|
||||||
|
Service level checks do not require a check name or id as these are derived
|
||||||
|
by Consul from the Service name and id respectively by appending 'service:'.
|
||||||
|
Node level checks require a check_name and optionally a check_id.
|
||||||
|
Currently, there is no complete way to retrieve the script, interval or ttl
|
||||||
|
metadata for a registered check. Without this metadata it is not possible to
|
||||||
|
tell if the data supplied with ansible represents a change to a check. As a
|
||||||
|
result this does not attempt to determine changes and will always report a
|
||||||
|
changed occurred. An api method is planned to supply this metadata so at that
|
||||||
|
stage change management will be added.
|
||||||
|
requirements:
|
||||||
|
- python-consul
|
||||||
|
- requests
|
||||||
|
version_added: "1.9"
|
||||||
|
author: Steve Gargan (steve.gargan@gmail.com)
|
||||||
|
options:
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- register or deregister the consul service, defaults to present
|
||||||
|
required: true
|
||||||
|
choices: ['present', 'absent']
|
||||||
|
service_name:
|
||||||
|
desciption:
|
||||||
|
- Unique name for the service on a node, must be unique per node,
|
||||||
|
required if registering a service. May be ommitted if registering
|
||||||
|
a node level check
|
||||||
|
required: false
|
||||||
|
service_id:
|
||||||
|
description:
|
||||||
|
- the ID for the service, must be unique per node, defaults to the
|
||||||
|
service name if the service name is supplied
|
||||||
|
required: false
|
||||||
|
default: service_name if supplied
|
||||||
|
host:
|
||||||
|
description:
|
||||||
|
- host of the consul agent defaults to localhost
|
||||||
|
required: false
|
||||||
|
default: localhost
|
||||||
|
port:
|
||||||
|
description:
|
||||||
|
- the port on which the consul agent is running
|
||||||
|
required: false
|
||||||
|
default: 8500
|
||||||
|
notes:
|
||||||
|
description:
|
||||||
|
- Notes to attach to check when registering it.
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
service_port:
|
||||||
|
description:
|
||||||
|
- the port on which the service is listening required for
|
||||||
|
registration of a service, i.e. if service_name or service_id is set
|
||||||
|
required: false
|
||||||
|
tags:
|
||||||
|
description:
|
||||||
|
- a list of tags that will be attached to the service registration.
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
script:
|
||||||
|
description:
|
||||||
|
- the script/command that will be run periodically to check the health
|
||||||
|
of the service. Scripts require an interval and vise versa
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
interval:
|
||||||
|
description:
|
||||||
|
- the interval at which the service check will be run. This is a number
|
||||||
|
with a s or m suffix to signify the units of seconds or minutes e.g
|
||||||
|
15s or 1m. If no suffix is supplied, m will be used by default e.g.
|
||||||
|
1 will be 1m. Required if the script param is specified.
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
check_id:
|
||||||
|
description:
|
||||||
|
- an ID for the service check, defaults to the check name, ignored if
|
||||||
|
part of a service definition.
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
check_name:
|
||||||
|
description:
|
||||||
|
- a name for the service check, defaults to the check id. required if
|
||||||
|
standalone, ignored if part of service definition.
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
ttl:
|
||||||
|
description:
|
||||||
|
- checks can be registered with a ttl instead of a script and interval
|
||||||
|
this means that the service will check in with the agent before the
|
||||||
|
ttl expires. If it doesn't the check will be considered failed.
|
||||||
|
Required if registering a check and the script an interval are missing
|
||||||
|
Similar to the interval this is a number with a s or m suffix to
|
||||||
|
signify the units of seconds or minutes e.g 15s or 1m. If no suffix
|
||||||
|
is supplied, m will be used by default e.g. 1 will be 1m
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
token:
|
||||||
|
description:
|
||||||
|
- the token key indentifying an ACL rule set. May be required to
|
||||||
|
register services.
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: register nginx service with the local consul agent
|
||||||
|
consul:
|
||||||
|
name: nginx
|
||||||
|
service_port: 80
|
||||||
|
|
||||||
|
- name: register nginx service with curl check
|
||||||
|
consul:
|
||||||
|
name: nginx
|
||||||
|
service_port: 80
|
||||||
|
script: "curl http://localhost"
|
||||||
|
interval: 60s
|
||||||
|
|
||||||
|
- name: register nginx with some service tags
|
||||||
|
consul:
|
||||||
|
name: nginx
|
||||||
|
service_port: 80
|
||||||
|
tags:
|
||||||
|
- prod
|
||||||
|
- webservers
|
||||||
|
|
||||||
|
- name: remove nginx service
|
||||||
|
consul:
|
||||||
|
name: nginx
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: create a node level check to test disk usage
|
||||||
|
consul:
|
||||||
|
check_name: Disk usage
|
||||||
|
check_id: disk_usage
|
||||||
|
script: "/opt/disk_usage.py"
|
||||||
|
interval: 5m
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
|
try:
|
||||||
|
import consul
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
python_consul_installed = True
|
||||||
|
except ImportError, e:
|
||||||
|
python_consul_installed = False
|
||||||
|
|
||||||
|
def register_with_consul(module):
|
||||||
|
|
||||||
|
state = module.params.get('state')
|
||||||
|
|
||||||
|
if state == 'present':
|
||||||
|
add(module)
|
||||||
|
else:
|
||||||
|
remove(module)
|
||||||
|
|
||||||
|
|
||||||
|
def add(module):
|
||||||
|
''' adds a service or a check depending on supplied configuration'''
|
||||||
|
check = parse_check(module)
|
||||||
|
service = parse_service(module)
|
||||||
|
|
||||||
|
if not service and not check:
|
||||||
|
module.fail_json(msg='a name and port are required to register a service')
|
||||||
|
|
||||||
|
if service:
|
||||||
|
if check:
|
||||||
|
service.add_check(check)
|
||||||
|
add_service(module, service)
|
||||||
|
elif check:
|
||||||
|
add_check(module, check)
|
||||||
|
|
||||||
|
|
||||||
|
def remove(module):
|
||||||
|
''' removes a service or a check '''
|
||||||
|
service_id = module.params.get('service_id') or module.params.get('service_name')
|
||||||
|
check_id = module.params.get('check_id') or module.params.get('check_name')
|
||||||
|
if not (service_id or check_id):
|
||||||
|
module.fail_json(msg='services and checks are removed by id or name.'\
|
||||||
|
' please supply a service id/name or a check id/name')
|
||||||
|
if service_id:
|
||||||
|
remove_service(module, service_id)
|
||||||
|
else:
|
||||||
|
remove_check(module, check_id)
|
||||||
|
|
||||||
|
|
||||||
|
def add_check(module, check):
|
||||||
|
''' registers a check with the given agent. currently there is no way
|
||||||
|
retrieve the full metadata of an existing check through the consul api.
|
||||||
|
Without this we can't compare to the supplied check and so we must assume
|
||||||
|
a change. '''
|
||||||
|
if not check.name:
|
||||||
|
module.fail_json(msg='a check name is required for a node level check,'\
|
||||||
|
' one not attached to a service')
|
||||||
|
|
||||||
|
consul_api = get_consul_api(module)
|
||||||
|
check.register(consul_api)
|
||||||
|
|
||||||
|
module.exit_json(changed=True,
|
||||||
|
check_id=check.check_id,
|
||||||
|
check_name=check.name,
|
||||||
|
script=check.script,
|
||||||
|
interval=check.interval,
|
||||||
|
ttl=check.ttl)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_check(module, check_id):
|
||||||
|
''' removes a check using its id '''
|
||||||
|
consul_api = get_consul_api(module)
|
||||||
|
|
||||||
|
if check_id in consul_api.agent.checks():
|
||||||
|
consul_api.agent.check.deregister(check_id)
|
||||||
|
module.exit_json(changed=True, id=check_id)
|
||||||
|
|
||||||
|
module.exit_json(changed=False, id=check_id)
|
||||||
|
|
||||||
|
|
||||||
|
def add_service(module, service):
|
||||||
|
''' registers a service with the the current agent '''
|
||||||
|
result = service
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
consul_api = get_consul_api(module)
|
||||||
|
existing = get_service_by_id(consul_api, service.id)
|
||||||
|
|
||||||
|
# there is no way to retreive the details of checks so if a check is present
|
||||||
|
# in the service it must be reregistered
|
||||||
|
if service.has_checks() or not existing or not existing == service:
|
||||||
|
|
||||||
|
service.register(consul_api)
|
||||||
|
# check that it registered correctly
|
||||||
|
registered = get_service_by_id(consul_api, service.id)
|
||||||
|
if registered:
|
||||||
|
result = registered
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
module.exit_json(changed=changed,
|
||||||
|
service_id=result.id,
|
||||||
|
service_name=result.name,
|
||||||
|
service_port=result.port,
|
||||||
|
checks=map(lambda x: x.to_dict(), service.checks),
|
||||||
|
tags=result.tags)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_service(module, service_id):
|
||||||
|
''' deregister a service from the given agent using its service id '''
|
||||||
|
consul_api = get_consul_api(module)
|
||||||
|
service = get_service_by_id(consul_api, service_id)
|
||||||
|
if service:
|
||||||
|
consul_api.agent.service.deregister(service_id)
|
||||||
|
module.exit_json(changed=True, id=service_id)
|
||||||
|
|
||||||
|
module.exit_json(changed=False, id=service_id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_consul_api(module, token=None):
|
||||||
|
return consul.Consul(host=module.params.get('host'),
|
||||||
|
port=module.params.get('port'),
|
||||||
|
token=module.params.get('token'))
|
||||||
|
|
||||||
|
|
||||||
|
def get_service_by_id(consul_api, service_id):
|
||||||
|
''' iterate the registered services and find one with the given id '''
|
||||||
|
for name, service in consul_api.agent.services().iteritems():
|
||||||
|
if service['ID'] == service_id:
|
||||||
|
return ConsulService(loaded=service)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_check(module):
|
||||||
|
|
||||||
|
if module.params.get('script') and module.params.get('ttl'):
|
||||||
|
module.fail_json(
|
||||||
|
msg='check are either script or ttl driven, supplying both does'\
|
||||||
|
' not make sense')
|
||||||
|
|
||||||
|
if module.params.get('check_id') or module.params.get('script') or module.params.get('ttl'):
|
||||||
|
|
||||||
|
return ConsulCheck(
|
||||||
|
module.params.get('check_id'),
|
||||||
|
module.params.get('check_name'),
|
||||||
|
module.params.get('check_node'),
|
||||||
|
module.params.get('check_host'),
|
||||||
|
module.params.get('script'),
|
||||||
|
module.params.get('interval'),
|
||||||
|
module.params.get('ttl'),
|
||||||
|
module.params.get('notes')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_service(module):
|
||||||
|
|
||||||
|
if module.params.get('service_name') and module.params.get('service_port'):
|
||||||
|
return ConsulService(
|
||||||
|
module.params.get('service_id'),
|
||||||
|
module.params.get('service_name'),
|
||||||
|
module.params.get('service_port'),
|
||||||
|
module.params.get('tags'),
|
||||||
|
)
|
||||||
|
elif module.params.get('service_name') and not module.params.get('service_port'):
|
||||||
|
|
||||||
|
module.fail_json(
|
||||||
|
msg="service_name supplied but no service_port, a port is required"\
|
||||||
|
" to configure a service. Did you configure the 'port' "\
|
||||||
|
"argument meaning 'service_port'?")
|
||||||
|
|
||||||
|
|
||||||
|
class ConsulService():
|
||||||
|
|
||||||
|
def __init__(self, service_id=None, name=None, port=-1,
|
||||||
|
tags=None, loaded=None):
|
||||||
|
self.id = self.name = name
|
||||||
|
if service_id:
|
||||||
|
self.id = service_id
|
||||||
|
self.port = port
|
||||||
|
self.tags = tags
|
||||||
|
self.checks = []
|
||||||
|
if loaded:
|
||||||
|
self.id = loaded['ID']
|
||||||
|
self.name = loaded['Service']
|
||||||
|
self.port = loaded['Port']
|
||||||
|
self.tags = loaded['Tags']
|
||||||
|
|
||||||
|
def register(self, consul_api):
|
||||||
|
if len(self.checks) > 0:
|
||||||
|
check = self.checks[0]
|
||||||
|
consul_api.agent.service.register(
|
||||||
|
self.name,
|
||||||
|
service_id=self.id,
|
||||||
|
port=self.port,
|
||||||
|
tags=self.tags,
|
||||||
|
script=check.script,
|
||||||
|
interval=check.interval,
|
||||||
|
ttl=check.ttl)
|
||||||
|
else:
|
||||||
|
consul_api.agent.service.register(
|
||||||
|
self.name,
|
||||||
|
service_id=self.id,
|
||||||
|
port=self.port,
|
||||||
|
tags=self.tags)
|
||||||
|
|
||||||
|
def add_check(self, check):
|
||||||
|
self.checks.append(check)
|
||||||
|
|
||||||
|
def checks(self):
|
||||||
|
return self.checks
|
||||||
|
|
||||||
|
def has_checks(self):
|
||||||
|
return len(self.checks) > 0
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return (isinstance(other, self.__class__)
|
||||||
|
and self.id == other.id
|
||||||
|
and self.name == other.name
|
||||||
|
and self.port == other.port
|
||||||
|
and self.tags == other.tags)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
data = {'id': self.id, "name": self.name}
|
||||||
|
if self.port:
|
||||||
|
data['port'] = self.port
|
||||||
|
if self.tags and len(self.tags) > 0:
|
||||||
|
data['tags'] = self.tags
|
||||||
|
if len(self.checks) > 0:
|
||||||
|
data['check'] = self.checks[0].to_dict()
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class ConsulCheck():
|
||||||
|
|
||||||
|
def __init__(self, check_id, name, node=None, host='localhost',
|
||||||
|
script=None, interval=None, ttl=None, notes=None):
|
||||||
|
self.check_id = self.name = name
|
||||||
|
if check_id:
|
||||||
|
self.check_id = check_id
|
||||||
|
self.script = script
|
||||||
|
self.interval = self.validate_duration('interval', interval)
|
||||||
|
self.ttl = self.validate_duration('ttl', ttl)
|
||||||
|
self.notes = notes
|
||||||
|
self.node = node
|
||||||
|
self.host = host
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def validate_duration(self, name, duration):
|
||||||
|
if duration:
|
||||||
|
duration_units = ['ns', 'us', 'ms', 's', 'm', 'h']
|
||||||
|
if not any((duration.endswith(suffix) for suffix in duration_units)):
|
||||||
|
raise Exception('Invalid %s %s you must specify units (%s)' %
|
||||||
|
(name, duration, ', '.join(duration_units)))
|
||||||
|
return duration
|
||||||
|
|
||||||
|
def register(self, consul_api):
|
||||||
|
consul_api.agent.check.register(self.name, check_id=self.check_id,
|
||||||
|
script=self.script,
|
||||||
|
interval=self.interval,
|
||||||
|
ttl=self.ttl, notes=self.notes)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return (isinstance(other, self.__class__)
|
||||||
|
and self.check_id == other.check_id
|
||||||
|
and self.name == other.name
|
||||||
|
and self.script == script
|
||||||
|
and self.interval == interval)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
data = {}
|
||||||
|
self._add(data, 'id', attr='check_id')
|
||||||
|
self._add(data, 'name', attr='check_name')
|
||||||
|
self._add(data, 'script')
|
||||||
|
self._add(data, 'node')
|
||||||
|
self._add(data, 'notes')
|
||||||
|
self._add(data, 'host')
|
||||||
|
self._add(data, 'interval')
|
||||||
|
self._add(data, 'ttl')
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _add(self, data, key, attr=None):
|
||||||
|
try:
|
||||||
|
if attr == None:
|
||||||
|
attr = key
|
||||||
|
data[key] = getattr(self, attr)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_dependencies(module):
|
||||||
|
if not python_consul_installed:
|
||||||
|
module.fail_json(msg="python-consul required for this module. "\
|
||||||
|
"see http://python-consul.readthedocs.org/en/latest/#installation")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=dict(
|
||||||
|
host=dict(default='localhost'),
|
||||||
|
port=dict(default=8500, type='int'),
|
||||||
|
check_id=dict(required=False),
|
||||||
|
check_name=dict(required=False),
|
||||||
|
check_node=dict(required=False),
|
||||||
|
check_host=dict(required=False),
|
||||||
|
notes=dict(required=False),
|
||||||
|
script=dict(required=False),
|
||||||
|
service_id=dict(required=False),
|
||||||
|
service_name=dict(required=False),
|
||||||
|
service_port=dict(required=False, type='int'),
|
||||||
|
state=dict(default='present', choices=['present', 'absent']),
|
||||||
|
interval=dict(required=False, type='str'),
|
||||||
|
ttl=dict(required=False, type='str'),
|
||||||
|
tags=dict(required=False, type='list'),
|
||||||
|
token=dict(required=False)
|
||||||
|
),
|
||||||
|
supports_check_mode=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
test_dependencies(module)
|
||||||
|
|
||||||
|
try:
|
||||||
|
register_with_consul(module)
|
||||||
|
except ConnectionError, e:
|
||||||
|
module.fail_json(msg='Could not connect to consul agent at %s:%s, error was %s' % (
|
||||||
|
module.params.get('host'), module.params.get('port'), str(e)))
|
||||||
|
except Exception, e:
|
||||||
|
module.fail_json(msg=str(e))
|
||||||
|
|
||||||
|
# import module snippets
|
||||||
|
from ansible.module_utils.basic import *
|
||||||
|
main()
|
@ -0,0 +1,320 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# (c) 2015, Steve Gargan <steve.gargan@gmail.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/>.
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
module: consul_acl
|
||||||
|
short_description: "manipulate consul acl keys and rules"
|
||||||
|
description:
|
||||||
|
- allows the addition, modification and deletion of ACL keys and associated
|
||||||
|
rules in a consul cluster via the agent. For more details on using and
|
||||||
|
configuring ACLs, see https://www.consul.io/docs/internals/acl.html.
|
||||||
|
requirements:
|
||||||
|
- python-consul
|
||||||
|
- pyhcl
|
||||||
|
- requests
|
||||||
|
version_added: "1.9"
|
||||||
|
author: Steve Gargan (steve.gargan@gmail.com)
|
||||||
|
options:
|
||||||
|
mgmt_token:
|
||||||
|
description:
|
||||||
|
- a management token is required to manipulate the acl lists
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- whether the ACL pair should be present or absent, defaults to present
|
||||||
|
required: false
|
||||||
|
choices: ['present', 'absent']
|
||||||
|
type:
|
||||||
|
description:
|
||||||
|
- the type of token that should be created, either management or
|
||||||
|
client, defaults to client
|
||||||
|
choices: ['client', 'management']
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- the name that should be associated with the acl key, this is opaque
|
||||||
|
to Consul
|
||||||
|
required: false
|
||||||
|
token:
|
||||||
|
description:
|
||||||
|
- the token key indentifying an ACL rule set. If generated by consul
|
||||||
|
this will be a UUID.
|
||||||
|
required: false
|
||||||
|
rules:
|
||||||
|
description:
|
||||||
|
- an list of the rules that should be associated with a given key/token.
|
||||||
|
required: false
|
||||||
|
host:
|
||||||
|
description:
|
||||||
|
- host of the consul agent defaults to localhost
|
||||||
|
required: false
|
||||||
|
default: localhost
|
||||||
|
port:
|
||||||
|
description:
|
||||||
|
- the port on which the consul agent is running
|
||||||
|
required: false
|
||||||
|
default: 8500
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: create an acl token with rules
|
||||||
|
consul_acl:
|
||||||
|
mgmt_token: 'some_management_acl'
|
||||||
|
host: 'consul1.mycluster.io'
|
||||||
|
name: 'Foo access'
|
||||||
|
rules:
|
||||||
|
- key: 'foo'
|
||||||
|
policy: read
|
||||||
|
- key: 'private/foo'
|
||||||
|
policy: deny
|
||||||
|
|
||||||
|
- name: remove a token
|
||||||
|
consul_acl:
|
||||||
|
mgmt_token: 'some_management_acl'
|
||||||
|
host: 'consul1.mycluster.io'
|
||||||
|
token: '172bd5c8-9fe9-11e4-b1b0-3c15c2c9fd5e'
|
||||||
|
state: absent
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
try:
|
||||||
|
import consul
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
python_consul_installed = True
|
||||||
|
except ImportError, e:
|
||||||
|
python_consul_installed = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
import hcl
|
||||||
|
pyhcl_installed = True
|
||||||
|
except ImportError:
|
||||||
|
pyhcl_installed = False
|
||||||
|
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
|
||||||
|
def execute(module):
|
||||||
|
|
||||||
|
state = module.params.get('state')
|
||||||
|
|
||||||
|
if state == 'present':
|
||||||
|
update_acl(module)
|
||||||
|
else:
|
||||||
|
remove_acl(module)
|
||||||
|
|
||||||
|
|
||||||
|
def update_acl(module):
|
||||||
|
|
||||||
|
rules = module.params.get('rules')
|
||||||
|
state = module.params.get('state')
|
||||||
|
token = module.params.get('token')
|
||||||
|
token_type = module.params.get('token_type')
|
||||||
|
mgmt = module.params.get('mgmt_token')
|
||||||
|
name = module.params.get('name')
|
||||||
|
consul = get_consul_api(module, mgmt)
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
if token:
|
||||||
|
existing_rules = load_rules_for_token(module, consul, token)
|
||||||
|
supplied_rules = yml_to_rules(module, rules)
|
||||||
|
print existing_rules
|
||||||
|
print supplied_rules
|
||||||
|
changed = not existing_rules == supplied_rules
|
||||||
|
if changed:
|
||||||
|
y = supplied_rules.to_hcl()
|
||||||
|
token = consul.acl.update(
|
||||||
|
token,
|
||||||
|
name=name,
|
||||||
|
type=token_type,
|
||||||
|
rules=supplied_rules.to_hcl())
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
rules = yml_to_rules(module, rules)
|
||||||
|
if rules.are_rules():
|
||||||
|
rules = rules.to_json()
|
||||||
|
else:
|
||||||
|
rules = None
|
||||||
|
|
||||||
|
token = consul.acl.create(
|
||||||
|
name=name, type=token_type, rules=rules)
|
||||||
|
changed = True
|
||||||
|
except Exception, e:
|
||||||
|
module.fail_json(
|
||||||
|
msg="No token returned, check your managment key and that \
|
||||||
|
the host is in the acl datacenter %s" % e)
|
||||||
|
except Exception, e:
|
||||||
|
module.fail_json(msg="Could not create/update acl %s" % e)
|
||||||
|
|
||||||
|
module.exit_json(changed=changed,
|
||||||
|
token=token,
|
||||||
|
rules=rules,
|
||||||
|
name=name,
|
||||||
|
type=token_type)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_acl(module):
|
||||||
|
state = module.params.get('state')
|
||||||
|
token = module.params.get('token')
|
||||||
|
mgmt = module.params.get('mgmt_token')
|
||||||
|
|
||||||
|
consul = get_consul_api(module, token=mgmt)
|
||||||
|
changed = token and consul.acl.info(token)
|
||||||
|
if changed:
|
||||||
|
token = consul.acl.destroy(token)
|
||||||
|
|
||||||
|
module.exit_json(changed=changed, token=token)
|
||||||
|
|
||||||
|
|
||||||
|
def load_rules_for_token(module, consul_api, token):
|
||||||
|
try:
|
||||||
|
rules = Rules()
|
||||||
|
info = consul_api.acl.info(token)
|
||||||
|
if info and info['Rules']:
|
||||||
|
rule_set = to_ascii(info['Rules'])
|
||||||
|
for rule in hcl.loads(rule_set).values():
|
||||||
|
for key, policy in rule.iteritems():
|
||||||
|
rules.add_rule(Rule(key, policy['policy']))
|
||||||
|
return rules
|
||||||
|
except Exception, e:
|
||||||
|
module.fail_json(
|
||||||
|
msg="Could not load rule list from retrieved rule data %s, %s" % (
|
||||||
|
token, e))
|
||||||
|
|
||||||
|
return json_to_rules(module, loaded)
|
||||||
|
|
||||||
|
def to_ascii(unicode_string):
|
||||||
|
if isinstance(unicode_string, unicode):
|
||||||
|
return unicode_string.encode('ascii', 'ignore')
|
||||||
|
return unicode_string
|
||||||
|
|
||||||
|
def yml_to_rules(module, yml_rules):
|
||||||
|
rules = Rules()
|
||||||
|
if yml_rules:
|
||||||
|
for rule in yml_rules:
|
||||||
|
if not('key' in rule or 'policy' in rule):
|
||||||
|
module.fail_json(msg="a rule requires a key and a policy.")
|
||||||
|
rules.add_rule(Rule(rule['key'], rule['policy']))
|
||||||
|
return rules
|
||||||
|
|
||||||
|
template = '''key "%s" {
|
||||||
|
policy = "%s"
|
||||||
|
}'''
|
||||||
|
|
||||||
|
class Rules:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.rules = {}
|
||||||
|
|
||||||
|
def add_rule(self, rule):
|
||||||
|
self.rules[rule.key] = rule
|
||||||
|
|
||||||
|
def are_rules(self):
|
||||||
|
return len(self.rules) > 0
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
rules = {}
|
||||||
|
for key, rule in self.rules.iteritems():
|
||||||
|
rules[key] = {'policy': rule.policy}
|
||||||
|
return json.dumps({'keys': rules})
|
||||||
|
|
||||||
|
def to_hcl(self):
|
||||||
|
|
||||||
|
rules = ""
|
||||||
|
for key, rule in self.rules.iteritems():
|
||||||
|
rules += template % (key, rule.policy)
|
||||||
|
|
||||||
|
return to_ascii(rules)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not (other or isinstance(other, self.__class__)
|
||||||
|
or len(other.rules) == len(self.rules)):
|
||||||
|
return False
|
||||||
|
|
||||||
|
for name, other_rule in other.rules.iteritems():
|
||||||
|
if not name in self.rules:
|
||||||
|
return False
|
||||||
|
rule = self.rules[name]
|
||||||
|
|
||||||
|
if not (rule and rule == other_rule):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.to_hcl()
|
||||||
|
|
||||||
|
class Rule:
|
||||||
|
|
||||||
|
def __init__(self, key, policy):
|
||||||
|
self.key = key
|
||||||
|
self.policy = policy
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return (isinstance(other, self.__class__)
|
||||||
|
and self.key == other.key
|
||||||
|
and self.policy == other.policy)
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.key) ^ hash(self.policy)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '%s %s' % (self.key, self.policy)
|
||||||
|
|
||||||
|
def get_consul_api(module, token=None):
|
||||||
|
if not token:
|
||||||
|
token = token = module.params.get('token')
|
||||||
|
return consul.Consul(host=module.params.get('host'),
|
||||||
|
port=module.params.get('port'),
|
||||||
|
token=token)
|
||||||
|
|
||||||
|
def test_dependencies(module):
|
||||||
|
if not python_consul_installed:
|
||||||
|
module.fail_json(msg="python-consul required for this module. "\
|
||||||
|
"see http://python-consul.readthedocs.org/en/latest/#installation")
|
||||||
|
|
||||||
|
if not pyhcl_installed:
|
||||||
|
module.fail_json( msg="pyhcl required for this module."\
|
||||||
|
" see https://pypi.python.org/pypi/pyhcl")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = dict(
|
||||||
|
mgmt_token=dict(required=True),
|
||||||
|
host=dict(default='localhost'),
|
||||||
|
name=dict(required=False),
|
||||||
|
port=dict(default=8500, type='int'),
|
||||||
|
rules=dict(default=None, required=False, type='list'),
|
||||||
|
state=dict(default='present', choices=['present', 'absent']),
|
||||||
|
token=dict(required=False),
|
||||||
|
token_type=dict(
|
||||||
|
required=False, choices=['client', 'management'], default='client')
|
||||||
|
)
|
||||||
|
module = AnsibleModule(argument_spec, supports_check_mode=False)
|
||||||
|
|
||||||
|
test_dependencies(module)
|
||||||
|
|
||||||
|
try:
|
||||||
|
execute(module)
|
||||||
|
except ConnectionError, e:
|
||||||
|
module.fail_json(msg='Could not connect to consul agent at %s:%s, error was %s' % (
|
||||||
|
module.params.get('host'), module.params.get('port'), str(e)))
|
||||||
|
except Exception, e:
|
||||||
|
module.fail_json(msg=str(e))
|
||||||
|
|
||||||
|
# import module snippets
|
||||||
|
from ansible.module_utils.basic import *
|
||||||
|
main()
|
@ -0,0 +1,263 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# (c) 2015, Steve Gargan <steve.gargan@gmail.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/>.
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
module: consul_kv
|
||||||
|
short_description: "manipulate entries in the key/value store of a consul
|
||||||
|
cluster. See http://www.consul.io/docs/agent/http.html#kv for more details."
|
||||||
|
description:
|
||||||
|
- allows the addition, modification and deletion of key/value entries in a
|
||||||
|
consul cluster via the agent. The entire contents of the record, including
|
||||||
|
the indices, flags and session are returned as 'value'. If the key
|
||||||
|
represents a prefix then Note that when a value is removed, the existing
|
||||||
|
value if any is returned as part of the results.
|
||||||
|
requirements:
|
||||||
|
- python-consul
|
||||||
|
- requests
|
||||||
|
version_added: "1.9"
|
||||||
|
author: Steve Gargan (steve.gargan@gmail.com)
|
||||||
|
options:
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- the action to take with the supplied key and value. If the state is
|
||||||
|
'present', the key contents will be set to the value supplied,
|
||||||
|
'changed' will be set to true only if the value was different to the
|
||||||
|
current contents. The state 'absent' will remove the key/value pair,
|
||||||
|
again 'changed' will be set to true only if the key actually existed
|
||||||
|
prior to the removal. An attempt can be made to obtain or free the
|
||||||
|
lock associated with a key/value pair with the states 'acquire' or
|
||||||
|
'release' respectively. a valid session must be supplied to make the
|
||||||
|
attempt changed will be true if the attempt is successful, false
|
||||||
|
otherwise.
|
||||||
|
required: false
|
||||||
|
choices: ['present', 'absent', 'acquire', 'release']
|
||||||
|
default: present
|
||||||
|
key:
|
||||||
|
description:
|
||||||
|
- the key at which the value should be stored.
|
||||||
|
required: true
|
||||||
|
value:
|
||||||
|
description:
|
||||||
|
- the value should be associated with the given key, required if state
|
||||||
|
is present
|
||||||
|
required: true
|
||||||
|
recurse:
|
||||||
|
description:
|
||||||
|
- if the key represents a prefix, each entry with the prefix can be
|
||||||
|
retrieved by setting this to true.
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
session:
|
||||||
|
description:
|
||||||
|
- the session that should be used to acquire or release a lock
|
||||||
|
associated with a key/value pair
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
token:
|
||||||
|
description:
|
||||||
|
- the token key indentifying an ACL rule set that controls access to
|
||||||
|
the key value pair
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
cas:
|
||||||
|
description:
|
||||||
|
- used when acquiring a lock with a session. If the cas is 0, then
|
||||||
|
Consul will only put the key if it does not already exist. If the
|
||||||
|
cas value is non-zero, then the key is only set if the index matches
|
||||||
|
the ModifyIndex of that key.
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
flags:
|
||||||
|
description:
|
||||||
|
- opaque integer value that can be passed when setting a value.
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
host:
|
||||||
|
description:
|
||||||
|
- host of the consul agent defaults to localhost
|
||||||
|
required: false
|
||||||
|
default: localhost
|
||||||
|
port:
|
||||||
|
description:
|
||||||
|
- the port on which the consul agent is running
|
||||||
|
required: false
|
||||||
|
default: 8500
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
|
||||||
|
- name: add or update the value associated with a key in the key/value store
|
||||||
|
consul_kv:
|
||||||
|
key: somekey
|
||||||
|
value: somevalue
|
||||||
|
|
||||||
|
- name: remove a key from the store
|
||||||
|
consul_kv:
|
||||||
|
key: somekey
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: add a node to an arbitrary group via consul inventory (see consul.ini)
|
||||||
|
consul_kv:
|
||||||
|
key: ansible/groups/dc1/somenode
|
||||||
|
value: 'top_secret'
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
|
try:
|
||||||
|
import consul
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
python_consul_installed = True
|
||||||
|
except ImportError, e:
|
||||||
|
python_consul_installed = False
|
||||||
|
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
|
||||||
|
def execute(module):
|
||||||
|
|
||||||
|
state = module.params.get('state')
|
||||||
|
|
||||||
|
if state == 'acquire' or state == 'release':
|
||||||
|
lock(module, state)
|
||||||
|
if state == 'present':
|
||||||
|
add_value(module)
|
||||||
|
else:
|
||||||
|
remove_value(module)
|
||||||
|
|
||||||
|
|
||||||
|
def lock(module, state):
|
||||||
|
|
||||||
|
session = module.params.get('session')
|
||||||
|
key = module.params.get('key')
|
||||||
|
value = module.params.get('value')
|
||||||
|
|
||||||
|
if not session:
|
||||||
|
module.fail(
|
||||||
|
msg='%s of lock for %s requested but no session supplied' %
|
||||||
|
(state, key))
|
||||||
|
|
||||||
|
if state == 'acquire':
|
||||||
|
successful = consul_api.kv.put(key, value,
|
||||||
|
cas=module.params.get('cas'),
|
||||||
|
acquire=session,
|
||||||
|
flags=module.params.get('flags'))
|
||||||
|
else:
|
||||||
|
successful = consul_api.kv.put(key, value,
|
||||||
|
cas=module.params.get('cas'),
|
||||||
|
release=session,
|
||||||
|
flags=module.params.get('flags'))
|
||||||
|
|
||||||
|
module.exit_json(changed=successful,
|
||||||
|
index=index,
|
||||||
|
key=key)
|
||||||
|
|
||||||
|
|
||||||
|
def add_value(module):
|
||||||
|
|
||||||
|
consul_api = get_consul_api(module)
|
||||||
|
|
||||||
|
key = module.params.get('key')
|
||||||
|
value = module.params.get('value')
|
||||||
|
|
||||||
|
index, existing = consul_api.kv.get(key)
|
||||||
|
|
||||||
|
changed = not existing or (existing and existing['Value'] != value)
|
||||||
|
if changed and not module.check_mode:
|
||||||
|
changed = consul_api.kv.put(key, value,
|
||||||
|
cas=module.params.get('cas'),
|
||||||
|
flags=module.params.get('flags'))
|
||||||
|
|
||||||
|
if module.params.get('retrieve'):
|
||||||
|
index, stored = consul_api.kv.get(key)
|
||||||
|
|
||||||
|
module.exit_json(changed=changed,
|
||||||
|
index=index,
|
||||||
|
key=key,
|
||||||
|
data=stored)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_value(module):
|
||||||
|
''' remove the value associated with the given key. if the recurse parameter
|
||||||
|
is set then any key prefixed with the given key will be removed. '''
|
||||||
|
consul_api = get_consul_api(module)
|
||||||
|
|
||||||
|
key = module.params.get('key')
|
||||||
|
value = module.params.get('value')
|
||||||
|
|
||||||
|
index, existing = consul_api.kv.get(
|
||||||
|
key, recurse=module.params.get('recurse'))
|
||||||
|
|
||||||
|
changed = existing != None
|
||||||
|
if changed and not module.check_mode:
|
||||||
|
consul_api.kv.delete(key, module.params.get('recurse'))
|
||||||
|
|
||||||
|
module.exit_json(changed=changed,
|
||||||
|
index=index,
|
||||||
|
key=key,
|
||||||
|
data=existing)
|
||||||
|
|
||||||
|
|
||||||
|
def get_consul_api(module, token=None):
|
||||||
|
return consul.Consul(host=module.params.get('host'),
|
||||||
|
port=module.params.get('port'),
|
||||||
|
token=module.params.get('token'))
|
||||||
|
|
||||||
|
def test_dependencies(module):
|
||||||
|
if not python_consul_installed:
|
||||||
|
module.fail_json(msg="python-consul required for this module. "\
|
||||||
|
"see http://python-consul.readthedocs.org/en/latest/#installation")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
argument_spec = dict(
|
||||||
|
cas=dict(required=False),
|
||||||
|
flags=dict(required=False),
|
||||||
|
key=dict(required=True),
|
||||||
|
host=dict(default='localhost'),
|
||||||
|
port=dict(default=8500, type='int'),
|
||||||
|
recurse=dict(required=False, type='bool'),
|
||||||
|
retrieve=dict(required=False, default=True),
|
||||||
|
state=dict(default='present', choices=['present', 'absent']),
|
||||||
|
token=dict(required=False, default='anonymous'),
|
||||||
|
value=dict(required=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
module = AnsibleModule(argument_spec, supports_check_mode=False)
|
||||||
|
|
||||||
|
test_dependencies(module)
|
||||||
|
|
||||||
|
try:
|
||||||
|
execute(module)
|
||||||
|
except ConnectionError, e:
|
||||||
|
module.fail_json(msg='Could not connect to consul agent at %s:%s, error was %s' % (
|
||||||
|
module.params.get('host'), module.params.get('port'), str(e)))
|
||||||
|
except Exception, e:
|
||||||
|
module.fail_json(msg=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
# import module snippets
|
||||||
|
from ansible.module_utils.basic import *
|
||||||
|
main()
|
@ -0,0 +1,268 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# (c) 2015, Steve Gargan <steve.gargan@gmail.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/>.
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
module: consul_session
|
||||||
|
short_description: "manipulate consul sessions"
|
||||||
|
description:
|
||||||
|
- allows the addition, modification and deletion of sessions in a consul
|
||||||
|
cluster. These sessions can then be used in conjunction with key value pairs
|
||||||
|
to implement distributed locks. In depth documentation for working with
|
||||||
|
sessions can be found here http://www.consul.io/docs/internals/sessions.html
|
||||||
|
requirements:
|
||||||
|
- python-consul
|
||||||
|
- requests
|
||||||
|
version_added: "1.9"
|
||||||
|
author: Steve Gargan (steve.gargan@gmail.com)
|
||||||
|
options:
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- whether the session should be present i.e. created if it doesn't
|
||||||
|
exist, or absent, removed if present. If created, the ID for the
|
||||||
|
session is returned in the output. If absent, the name or ID is
|
||||||
|
required to remove the session. Info for a single session, all the
|
||||||
|
sessions for a node or all available sessions can be retrieved by
|
||||||
|
specifying info, node or list for the state; for node or info, the
|
||||||
|
node name or session id is required as parameter.
|
||||||
|
required: false
|
||||||
|
choices: ['present', 'absent', 'info', 'node', 'list']
|
||||||
|
default: present
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- the name that should be associated with the session. This is opaque
|
||||||
|
to Consul and not required.
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
delay:
|
||||||
|
description:
|
||||||
|
- the optional lock delay that can be attached to the session when it
|
||||||
|
is created. Locks for invalidated sessions ar blocked from being
|
||||||
|
acquired until this delay has expired. Valid units for delays
|
||||||
|
include 'ns', 'us', 'ms', 's', 'm', 'h'
|
||||||
|
default: 15s
|
||||||
|
required: false
|
||||||
|
node:
|
||||||
|
description:
|
||||||
|
- the name of the node that with which the session will be associated.
|
||||||
|
by default this is the name of the agent.
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
datacenter:
|
||||||
|
description:
|
||||||
|
- name of the datacenter in which the session exists or should be
|
||||||
|
created.
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
checks:
|
||||||
|
description:
|
||||||
|
- a list of checks that will be used to verify the session health. If
|
||||||
|
all the checks fail, the session will be invalidated and any locks
|
||||||
|
associated with the session will be release and can be acquired once
|
||||||
|
the associated lock delay has expired.
|
||||||
|
required: false
|
||||||
|
default: None
|
||||||
|
host:
|
||||||
|
description:
|
||||||
|
- host of the consul agent defaults to localhost
|
||||||
|
required: false
|
||||||
|
default: localhost
|
||||||
|
port:
|
||||||
|
description:
|
||||||
|
- the port on which the consul agent is running
|
||||||
|
required: false
|
||||||
|
default: 8500
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: register basic session with consul
|
||||||
|
consul_session:
|
||||||
|
name: session1
|
||||||
|
|
||||||
|
- name: register a session with an existing check
|
||||||
|
consul_session:
|
||||||
|
name: session_with_check
|
||||||
|
checks:
|
||||||
|
- existing_check_name
|
||||||
|
|
||||||
|
- name: register a session with lock_delay
|
||||||
|
consul_session:
|
||||||
|
name: session_with_delay
|
||||||
|
delay: 20s
|
||||||
|
|
||||||
|
- name: retrieve info about session by id
|
||||||
|
consul_session: id=session_id state=info
|
||||||
|
|
||||||
|
- name: retrieve active sessions
|
||||||
|
consul_session: state=list
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
try:
|
||||||
|
import consul
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
python_consul_installed = True
|
||||||
|
except ImportError, e:
|
||||||
|
python_consul_installed = False
|
||||||
|
|
||||||
|
def execute(module):
|
||||||
|
|
||||||
|
state = module.params.get('state')
|
||||||
|
|
||||||
|
if state in ['info', 'list', 'node']:
|
||||||
|
lookup_sessions(module)
|
||||||
|
elif state == 'present':
|
||||||
|
update_session(module)
|
||||||
|
else:
|
||||||
|
remove_session(module)
|
||||||
|
|
||||||
|
def lookup_sessions(module):
|
||||||
|
|
||||||
|
datacenter = module.params.get('datacenter')
|
||||||
|
|
||||||
|
state = module.params.get('state')
|
||||||
|
consul = get_consul_api(module)
|
||||||
|
try:
|
||||||
|
if state == 'list':
|
||||||
|
sessions_list = consul.session.list(dc=datacenter)
|
||||||
|
#ditch the index, this can be grabbed from the results
|
||||||
|
if sessions_list and sessions_list[1]:
|
||||||
|
sessions_list = sessions_list[1]
|
||||||
|
module.exit_json(changed=True,
|
||||||
|
sessions=sessions_list)
|
||||||
|
elif state == 'node':
|
||||||
|
node = module.params.get('node')
|
||||||
|
if not node:
|
||||||
|
module.fail_json(
|
||||||
|
msg="node name is required to retrieve sessions for node")
|
||||||
|
sessions = consul.session.node(node, dc=datacenter)
|
||||||
|
module.exit_json(changed=True,
|
||||||
|
node=node,
|
||||||
|
sessions=sessions)
|
||||||
|
elif state == 'info':
|
||||||
|
session_id = module.params.get('id')
|
||||||
|
if not session_id:
|
||||||
|
module.fail_json(
|
||||||
|
msg="session_id is required to retrieve indvidual session info")
|
||||||
|
|
||||||
|
session_by_id = consul.session.info(session_id, dc=datacenter)
|
||||||
|
module.exit_json(changed=True,
|
||||||
|
session_id=session_id,
|
||||||
|
sessions=session_by_id)
|
||||||
|
|
||||||
|
except Exception, e:
|
||||||
|
module.fail_json(msg="Could not retrieve session info %s" % e)
|
||||||
|
|
||||||
|
|
||||||
|
def update_session(module):
|
||||||
|
|
||||||
|
name = module.params.get('name')
|
||||||
|
session_id = module.params.get('id')
|
||||||
|
delay = module.params.get('delay')
|
||||||
|
checks = module.params.get('checks')
|
||||||
|
datacenter = module.params.get('datacenter')
|
||||||
|
node = module.params.get('node')
|
||||||
|
|
||||||
|
consul = get_consul_api(module)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
session = consul.session.create(
|
||||||
|
name=name,
|
||||||
|
node=node,
|
||||||
|
lock_delay=validate_duration('delay', delay),
|
||||||
|
dc=datacenter,
|
||||||
|
checks=checks
|
||||||
|
)
|
||||||
|
module.exit_json(changed=True,
|
||||||
|
session_id=session,
|
||||||
|
name=name,
|
||||||
|
delay=delay,
|
||||||
|
checks=checks,
|
||||||
|
node=node)
|
||||||
|
except Exception, e:
|
||||||
|
module.fail_json(msg="Could not create/update session %s" % e)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_session(module):
|
||||||
|
session_id = module.params.get('id')
|
||||||
|
|
||||||
|
if not session_id:
|
||||||
|
module.fail_json(msg="""A session id must be supplied in order to
|
||||||
|
remove a session.""")
|
||||||
|
|
||||||
|
consul = get_consul_api(module)
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
session = consul.session.destroy(session_id)
|
||||||
|
|
||||||
|
module.exit_json(changed=True,
|
||||||
|
session_id=session_id)
|
||||||
|
except Exception, e:
|
||||||
|
module.fail_json(msg="Could not remove session with id '%s' %s" % (
|
||||||
|
session_id, e))
|
||||||
|
|
||||||
|
def validate_duration(name, duration):
|
||||||
|
if duration:
|
||||||
|
duration_units = ['ns', 'us', 'ms', 's', 'm', 'h']
|
||||||
|
if not any((duration.endswith(suffix) for suffix in duration_units)):
|
||||||
|
raise Exception('Invalid %s %s you must specify units (%s)' %
|
||||||
|
(name, duration, ', '.join(duration_units)))
|
||||||
|
return duration
|
||||||
|
|
||||||
|
def get_consul_api(module):
|
||||||
|
return consul.Consul(host=module.params.get('host'),
|
||||||
|
port=module.params.get('port'))
|
||||||
|
|
||||||
|
def test_dependencies(module):
|
||||||
|
if not python_consul_installed:
|
||||||
|
module.fail_json(msg="python-consul required for this module. "\
|
||||||
|
"see http://python-consul.readthedocs.org/en/latest/#installation")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = dict(
|
||||||
|
checks=dict(default=None, required=False, type='list'),
|
||||||
|
delay=dict(required=False,type='str', default='15s'),
|
||||||
|
host=dict(default='localhost'),
|
||||||
|
port=dict(default=8500, type='int'),
|
||||||
|
id=dict(required=False),
|
||||||
|
name=dict(required=False),
|
||||||
|
node=dict(required=False),
|
||||||
|
state=dict(default='present',
|
||||||
|
choices=['present', 'absent', 'info', 'node', 'list'])
|
||||||
|
)
|
||||||
|
|
||||||
|
module = AnsibleModule(argument_spec, supports_check_mode=False)
|
||||||
|
|
||||||
|
test_dependencies(module)
|
||||||
|
|
||||||
|
try:
|
||||||
|
execute(module)
|
||||||
|
except ConnectionError, e:
|
||||||
|
module.fail_json(msg='Could not connect to consul agent at %s:%s, error was %s' % (
|
||||||
|
module.params.get('host'), module.params.get('port'), str(e)))
|
||||||
|
except Exception, e:
|
||||||
|
module.fail_json(msg=str(e))
|
||||||
|
|
||||||
|
# import module snippets
|
||||||
|
from ansible.module_utils.basic import *
|
||||||
|
main()
|
Loading…
Reference in New Issue