Relocating extras into lib/ansible/modules/ after merge

This commit is contained in:
James Cammarata 2016-12-08 00:36:57 -05:00 committed by Matt Clay
commit 011ea55a8f
596 changed files with 0 additions and 266 deletions

View file

@ -0,0 +1,589 @@
#!/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/>.
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'community',
'version': '1.0'}
DOCUMENTATION = """
module: consul
short_description: "Add, modify & delete services within a consul cluster."
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.
- "See http://consul.io for more details."
requirements:
- "python >= 2.6"
- python-consul
- requests
version_added: "2.0"
author: "Steve Gargan (@sgargan)"
options:
state:
description:
- register or deregister the consul service, defaults to present
required: true
choices: ['present', 'absent']
service_name:
description:
- 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
scheme:
description:
- the protocol scheme on which the consul agent is running
required: false
default: http
version_added: "2.1"
validate_certs:
description:
- whether to verify the tls certificate of the consul agent
required: false
default: True
version_added: "2.1"
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
service_address:
description:
- the address to advertise that the service will be listening on.
This value will be passed as the I(Address) parameter to Consul's
U(/v1/agent/service/register) API method, so refer to the Consul API
documentation for further details.
required: false
default: None
version_added: "2.1"
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
http:
description:
- checks can be registered with an http endpoint. This means that consul
will check that the http endpoint returns a successful http status.
Interval must also be provided with this option.
required: false
default: None
version_added: "2.0"
timeout:
description:
- A custom HTTP check timeout. The consul default is 10 seconds.
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.
required: false
default: None
version_added: "2.0"
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:
service_name: nginx
service_port: 80
- name: register nginx service with curl check
consul:
service_name: nginx
service_port: 80
script: "curl http://localhost"
interval: 60s
- name: register nginx with an http check
consul:
service_name: nginx
service_port: 80
interval: 60s
http: "http://localhost:80/status"
- name: register external service nginx available at 10.1.5.23
consul:
service_name: nginx
service_port: 80
service_address: 10.1.5.23
- name: register nginx with some service tags
consul:
service_name: nginx
service_port: 80
tags:
- prod
- webservers
- name: remove nginx service
consul:
service_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
- name: register an http check against a service that's already registered
consul:
check_name: nginx-check2
check_id: nginx-check2
service_id: nginx
interval: 60s
http: "http://localhost:80/morestatus"
'''
try:
import consul
from requests.exceptions import ConnectionError
python_consul_installed = True
except ImportError:
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 and not service_id:
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,
http=check.http,
timeout=check.timeout,
service_id=check.service_id)
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_or_name(consul_api, service.id)
# there is no way to retrieve the details of checks so if a check is present
# in the service it must be re-registered
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_or_name(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=[check.to_dict() for check in 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_or_name(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'),
scheme=module.params.get('scheme'),
verify=module.params.get('validate_certs'),
token=module.params.get('token'))
def get_service_by_id_or_name(consul_api, service_id_or_name):
''' 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_or_name or service['Service'] == service_id_or_name:
return ConsulService(loaded=service)
def parse_check(module):
if len(filter(None, [module.params.get('script'), module.params.get('ttl'), module.params.get('http')])) > 1:
module.fail_json(
msg='check are either script, http or ttl driven, supplying more than one does not make sense')
if module.params.get('check_id') or module.params.get('script') or module.params.get('ttl') or module.params.get('http'):
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'),
module.params.get('http'),
module.params.get('timeout'),
module.params.get('service_id'),
)
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_address'),
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, address=None, port=-1,
tags=None, loaded=None):
self.id = self.name = name
if service_id:
self.id = service_id
self.address = address
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,
address=self.address,
port=self.port,
tags=self.tags,
check=check.check)
else:
consul_api.agent.service.register(
self.name,
service_id=self.id,
address=self.address,
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, http=None, timeout=None, service_id=None):
self.check_id = self.name = name
if check_id:
self.check_id = check_id
self.service_id = service_id
self.notes = notes
self.node = node
self.host = host
self.interval = self.validate_duration('interval', interval)
self.ttl = self.validate_duration('ttl', ttl)
self.script = script
self.http = http
self.timeout = self.validate_duration('timeout', timeout)
self.check = None
if script:
self.check = consul.Check.script(script, self.interval)
if ttl:
self.check = consul.Check.ttl(self.ttl)
if http:
if interval is None:
raise Exception('http check must specify interval')
self.check = consul.Check.http(http, self.interval, self.timeout)
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)):
duration = "{}s".format(duration)
return duration
def register(self, consul_api):
consul_api.agent.check.register(self.name, check_id=self.check_id, service_id=self.service_id,
notes=self.notes,
check=self.check)
def __eq__(self, other):
return (isinstance(other, self.__class__)
and self.check_id == other.check_id
and self.service_id == other.service_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')
self._add(data, 'http')
self._add(data, 'timeout')
self._add(data, 'service_id')
return data
def _add(self, data, key, attr=None):
try:
if attr is 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'),
scheme=dict(required=False, default='http'),
validate_certs=dict(required=False, default=True, type='bool'),
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_address=dict(required=False, type='str', default=None),
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'),
http=dict(required=False, type='str'),
timeout=dict(required=False, type='str'),
tags=dict(required=False, type='list'),
token=dict(required=False, no_log=True)
),
supports_check_mode=False,
)
test_dependencies(module)
try:
register_with_consul(module)
except ConnectionError as 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 as e:
module.fail_json(msg=str(e))
# import module snippets
from ansible.module_utils.basic import *
if __name__ == '__main__':
main()

View file

@ -0,0 +1,367 @@
#!/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/>.
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'community',
'version': '1.0'}
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 >= 2.6"
- python-consul
- pyhcl
- requests
version_added: "2.0"
author: "Steve Gargan (@sgargan)"
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
required: false
choices: ['present', 'absent']
default: present
token_type:
description:
- the type of token that should be created, either management or
client
choices: ['client', 'management']
default: client
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 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
scheme:
description:
- the protocol scheme on which the consul agent is running
required: false
default: http
version_added: "2.1"
validate_certs:
description:
- whether to verify the tls certificate of the consul agent
required: false
default: True
version_added: "2.1"
"""
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: create an acl with specific token with both key and serivce rules
consul_acl:
mgmt_token: 'some_management_acl'
name: 'Foo access'
token: 'some_client_token'
rules:
- key: 'foo'
policy: read
- service: ''
policy: write
- service: 'secret-'
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
try:
import consul
from requests.exceptions import ConnectionError
python_consul_installed = True
except ImportError:
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)
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_hcl()
else:
rules = None
token = consul.acl.create(
name=name, type=token_type, rules=rules)
changed = True
except Exception as 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 as 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 = hcl.loads(to_ascii(info['Rules']))
for rule_type in rule_set:
for pattern, policy in rule_set[rule_type].iteritems():
rules.add_rule(rule_type, Rule(pattern, policy['policy']))
return rules
except Exception as 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 ('key' in rule and 'policy' in rule):
rules.add_rule('key', Rule(rule['key'], rule['policy']))
elif ('service' in rule and 'policy' in rule):
rules.add_rule('service', Rule(rule['service'], rule['policy']))
elif ('event' in rule and 'policy' in rule):
rules.add_rule('event', Rule(rule['event'], rule['policy']))
elif ('query' in rule and 'policy' in rule):
rules.add_rule('query', Rule(rule['query'], rule['policy']))
else:
module.fail_json(msg="a rule requires a key/service/event or query and a policy.")
return rules
template = '''%s "%s" {
policy = "%s"
}
'''
RULE_TYPES = ['key', 'service', 'event', 'query']
class Rules:
def __init__(self):
self.rules = {}
for rule_type in RULE_TYPES:
self.rules[rule_type] = {}
def add_rule(self, rule_type, rule):
self.rules[rule_type][rule.pattern] = rule
def are_rules(self):
return len(self) > 0
def to_hcl(self):
rules = ""
for rule_type in RULE_TYPES:
for pattern, rule in self.rules[rule_type].iteritems():
rules += template % (rule_type, pattern, rule.policy)
return to_ascii(rules)
def __len__(self):
count = 0
for rule_type in RULE_TYPES:
count += len(self.rules[rule_type])
return count
def __eq__(self, other):
if not (other or isinstance(other, self.__class__)
or len(other) == len(self)):
return False
for rule_type in RULE_TYPES:
for name, other_rule in other.rules[rule_type].iteritems():
if not name in self.rules[rule_type]:
return False
rule = self.rules[rule_type][name]
if not (rule and rule == other_rule):
return False
return True
def __str__(self):
return self.to_hcl()
class Rule:
def __init__(self, pattern, policy):
self.pattern = pattern
self.policy = policy
def __eq__(self, other):
return (isinstance(other, self.__class__)
and self.pattern == other.pattern
and self.policy == other.policy)
def __hash__(self):
return hash(self.pattern) ^ hash(self.policy)
def __str__(self):
return '%s %s' % (self.pattern, self.policy)
def get_consul_api(module, token=None):
if not token:
token = module.params.get('token')
return consul.Consul(host=module.params.get('host'),
port=module.params.get('port'),
scheme=module.params.get('scheme'),
verify=module.params.get('validate_certs'),
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, no_log=True),
host=dict(default='localhost'),
scheme=dict(required=False, default='http'),
validate_certs=dict(required=False, type='bool', default=True),
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, no_log=True),
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 as 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 as e:
module.fail_json(msg=str(e))
# import module snippets
from ansible.module_utils.basic import *
if __name__ == '__main__':
main()

View file

@ -0,0 +1,293 @@
#!/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/>.
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'community',
'version': '1.0'}
DOCUMENTATION = """
module: consul_kv
short_description: Manipulate entries in the key/value store of a consul cluster.
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.
- "See http://www.consul.io/docs/agent/http.html#kv for more details."
requirements:
- "python >= 2.6"
- python-consul
- requests
version_added: "2.0"
author: "Steve Gargan (@sgargan)"
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
scheme:
description:
- the protocol scheme on which the consul agent is running
required: false
default: http
version_added: "2.1"
validate_certs:
description:
- whether to verify the tls certificate of the consul agent
required: false
default: True
version_added: "2.1"
"""
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'
- name: Register a key/value pair with an associated session
consul_kv:
key: stg/node/server_birthday
value: 20160509
session: "{{ sessionid }}"
state: acquire
'''
import sys
try:
import consul
from requests.exceptions import ConnectionError
python_consul_installed = True
except ImportError:
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):
consul_api = get_consul_api(module)
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))
index, existing = consul_api.kv.get(key)
changed = not existing or (existing and existing['Value'] != value)
if changed and not module.check_mode:
if state == 'acquire':
changed = consul_api.kv.put(key, value,
cas=module.params.get('cas'),
acquire=session,
flags=module.params.get('flags'))
else:
changed = consul_api.kv.put(key, value,
cas=module.params.get('cas'),
release=session,
flags=module.params.get('flags'))
module.exit_json(changed=changed,
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'),
scheme=module.params.get('scheme'),
verify=module.params.get('validate_certs'),
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'),
scheme=dict(required=False, default='http'),
validate_certs=dict(required=False, type='bool', default=True),
port=dict(default=8500, type='int'),
recurse=dict(required=False, type='bool'),
retrieve=dict(required=False, type='bool', default=True),
state=dict(default='present', choices=['present', 'absent', 'acquire', 'release']),
token=dict(required=False, no_log=True),
value=dict(required=False),
session=dict(required=False)
)
module = AnsibleModule(argument_spec, supports_check_mode=False)
test_dependencies(module)
try:
execute(module)
except ConnectionError as 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 as e:
module.fail_json(msg=str(e))
# import module snippets
from ansible.module_utils.basic import *
if __name__ == '__main__':
main()

View file

@ -0,0 +1,286 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# (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/>.
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'community',
'version': '1.0'}
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 >= 2.6"
- python-consul
- requests
version_added: "2.0"
author: "Steve Gargan @sgargan"
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. Durations are in seconds
default: 15
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
scheme:
description:
- the protocol scheme on which the consul agent is running
required: false
default: http
version_added: "2.1"
validate_certs:
description:
- whether to verify the tls certificate of the consul agent
required: false
default: True
version_added: "2.1"
behavior:
description:
- the optional behavior that can be attached to the session when it
is created. This can be set to either release or delete. This
controls the behavior when a session is invalidated.
default: release
required: false
version_added: "2.2"
"""
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
'''
try:
import consul
from requests.exceptions import ConnectionError
python_consul_installed = True
except ImportError:
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_client = get_consul_api(module)
try:
if state == 'list':
sessions_list = consul_client.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_client.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_client.session.info(session_id, dc=datacenter)
module.exit_json(changed=True,
session_id=session_id,
sessions=session_by_id)
except Exception as e:
module.fail_json(msg="Could not retrieve session info %s" % e)
def update_session(module):
name = module.params.get('name')
delay = module.params.get('delay')
checks = module.params.get('checks')
datacenter = module.params.get('datacenter')
node = module.params.get('node')
behavior = module.params.get('behavior')
consul_client = get_consul_api(module)
try:
session = consul_client.session.create(
name=name,
behavior=behavior,
node=node,
lock_delay=delay,
dc=datacenter,
checks=checks
)
module.exit_json(changed=True,
session_id=session,
name=name,
behavior=behavior,
delay=delay,
checks=checks,
node=node)
except Exception as 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_client = get_consul_api(module)
try:
consul_client.session.destroy(session_id)
module.exit_json(changed=True,
session_id=session_id)
except Exception as e:
module.fail_json(msg="Could not remove session with id '%s' %s" % (
session_id, e))
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='int', default='15'),
behavior=dict(required=False,type='str', default='release',
choices=['release', 'delete']),
host=dict(default='localhost'),
port=dict(default=8500, type='int'),
scheme=dict(required=False, default='http'),
validate_certs=dict(required=False, default=True),
id=dict(required=False),
name=dict(required=False),
node=dict(required=False),
state=dict(default='present',
choices=['present', 'absent', 'info', 'node', 'list']),
datacenter=dict(required=False)
)
module = AnsibleModule(argument_spec, supports_check_mode=False)
test_dependencies(module)
try:
execute(module)
except ConnectionError as 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 as e:
module.fail_json(msg=str(e))
# import module snippets
from ansible.module_utils.basic import *
if __name__ == '__main__':
main()

View file

@ -0,0 +1,411 @@
#!/usr/bin/python
# Copyright 2015 Google Inc. All Rights Reserved.
#
# 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: kubernetes
version_added: "2.1"
short_description: Manage Kubernetes resources.
description:
- This module can manage Kubernetes resources on an existing cluster using
the Kubernetes server API. Users can specify in-line API data, or
specify an existing Kubernetes YAML file. Currently, this module,
Only supports HTTP Basic Auth
Only supports 'strategic merge' for update, http://goo.gl/fCPYxT
SSL certs are not working, use 'validate_certs=off' to disable
options:
api_endpoint:
description:
- The IPv4 API endpoint of the Kubernetes cluster.
required: true
default: null
aliases: ["endpoint"]
inline_data:
description:
- The Kubernetes YAML data to send to the API I(endpoint). This option is
mutually exclusive with C('file_reference').
required: true
default: null
file_reference:
description:
- Specify full path to a Kubernets YAML file to send to API I(endpoint).
This option is mutually exclusive with C('inline_data').
required: false
default: null
certificate_authority_data:
description:
- Certificate Authority data for Kubernetes server. Should be in either
standard PEM format or base64 encoded PEM data. Note that certificate
verification is broken until ansible supports a version of
'match_hostname' that can match the IP address against the CA data.
required: false
default: null
state:
description:
- The desired action to take on the Kubernetes data.
required: true
default: "present"
choices: ["present", "absent", "update", "replace"]
url_password:
description:
- The HTTP Basic Auth password for the API I(endpoint). This should be set
unless using the C('insecure') option.
default: null
aliases: ["password"]
url_username:
description:
- The HTTP Basic Auth username for the API I(endpoint). This should be set
unless using the C('insecure') option.
default: "admin"
aliases: ["username"]
insecure:
description:
- "Reverts the connection to using HTTP instead of HTTPS. This option should
only be used when execuing the M('kubernetes') module local to the Kubernetes
cluster using the insecure local port (locahost:8080 by default)."
validate_certs:
description:
- Enable/disable certificate validation. Note that this is set to
C(false) until Ansible can support IP address based certificate
hostname matching (exists in >= python3.5.0).
required: false
default: false
author: "Eric Johnson (@erjohnso) <erjohnso@google.com>"
'''
EXAMPLES = '''
# Create a new namespace with in-line YAML.
- name: Create a kubernetes namespace
kubernetes:
api_endpoint: 123.45.67.89
url_username: admin
url_password: redacted
inline_data:
kind: Namespace
apiVersion: v1
metadata:
name: ansible-test
labels:
label_env: production
label_ver: latest
annotations:
a1: value1
a2: value2
state: present
# Create a new namespace from a YAML file.
- name: Create a kubernetes namespace
kubernetes:
api_endpoint: 123.45.67.89
url_username: admin
url_password: redacted
file_reference: /path/to/create_namespace.yaml
state: present
# Do the same thing, but using the insecure localhost port
- name: Create a kubernetes namespace
kubernetes:
api_endpoint: 123.45.67.89
insecure: true
file_reference: /path/to/create_namespace.yaml
state: present
'''
RETURN = '''
# Example response from creating a Kubernetes Namespace.
api_response:
description: Raw response from Kubernetes API, content varies with API.
returned: success
type: dictionary
contains:
apiVersion: "v1"
kind: "Namespace"
metadata:
creationTimestamp: "2016-01-04T21:16:32Z"
name: "test-namespace"
resourceVersion: "509635"
selfLink: "/api/v1/namespaces/test-namespace"
uid: "6dbd394e-b328-11e5-9a02-42010af0013a"
spec:
finalizers:
- kubernetes
status:
phase: "Active"
'''
import base64
try:
import yaml
has_lib_yaml = True
except ImportError:
has_lib_yaml = False
############################################################################
############################################################################
# For API coverage, this Anislbe module provides capability to operate on
# all Kubernetes objects that support a "create" call (except for 'Events').
# In order to obtain a valid list of Kubernetes objects, the v1 spec file
# was referenced and the below python script was used to parse the JSON
# spec file, extract only the objects with a description starting with
# 'create a'. The script then iterates over all of these base objects
# to get the endpoint URL and was used to generate the KIND_URL map.
#
# import json
# from urllib2 import urlopen
#
# r = urlopen("https://raw.githubusercontent.com/kubernetes"
# "/kubernetes/master/api/swagger-spec/v1.json")
# v1 = json.load(r)
#
# apis = {}
# for a in v1['apis']:
# p = a['path']
# for o in a['operations']:
# if o["summary"].startswith("create a") and o["type"] != "v1.Event":
# apis[o["type"]] = p
#
# def print_kind_url_map():
# results = []
# for a in apis.keys():
# results.append('"%s": "%s"' % (a[3:].lower(), apis[a]))
# results.sort()
# print "KIND_URL = {"
# print ",\n".join(results)
# print "}"
#
# if __name__ == '__main__':
# print_kind_url_map()
############################################################################
############################################################################
KIND_URL = {
"binding": "/api/v1/namespaces/{namespace}/bindings",
"endpoints": "/api/v1/namespaces/{namespace}/endpoints",
"limitrange": "/api/v1/namespaces/{namespace}/limitranges",
"namespace": "/api/v1/namespaces",
"node": "/api/v1/nodes",
"persistentvolume": "/api/v1/persistentvolumes",
"persistentvolumeclaim": "/api/v1/namespaces/{namespace}/persistentvolumeclaims", # NOQA
"pod": "/api/v1/namespaces/{namespace}/pods",
"podtemplate": "/api/v1/namespaces/{namespace}/podtemplates",
"replicationcontroller": "/api/v1/namespaces/{namespace}/replicationcontrollers", # NOQA
"resourcequota": "/api/v1/namespaces/{namespace}/resourcequotas",
"secret": "/api/v1/namespaces/{namespace}/secrets",
"service": "/api/v1/namespaces/{namespace}/services",
"serviceaccount": "/api/v1/namespaces/{namespace}/serviceaccounts"
}
USER_AGENT = "ansible-k8s-module/0.0.1"
# TODO(erjohnso): SSL Certificate validation is currently unsupported.
# It can be made to work when the following are true:
# - Ansible consistently uses a "match_hostname" that supports IP Address
# matching. This is now true in >= python3.5.0. Currently, this feature
# is not yet available in backports.ssl_match_hostname (still 3.4).
# - Ansible allows passing in the self-signed CA cert that is created with
# a kubernetes master. The lib/ansible/module_utils/urls.py method,
# SSLValidationHandler.get_ca_certs() needs a way for the Kubernetes
# CA cert to be passed in and included in the generated bundle file.
# When this is fixed, the following changes can be made to this module,
# - Remove the 'return' statement in line 254 below
# - Set 'required=true' for certificate_authority_data and ensure that
# ansible's SSLValidationHandler.get_ca_certs() can pick up this CA cert
# - Set 'required=true' for the validate_certs param.
def decode_cert_data(module):
return
d = module.params.get("certificate_authority_data")
if d and not d.startswith("-----BEGIN"):
module.params["certificate_authority_data"] = base64.b64decode(d)
def api_request(module, url, method="GET", headers=None, data=None):
body = None
if data:
data = json.dumps(data)
response, info = fetch_url(module, url, method=method, headers=headers, data=data)
if int(info['status']) == -1:
module.fail_json(msg="Failed to execute the API request: %s" % info['msg'], url=url, method=method, headers=headers)
if response is not None:
body = json.loads(response.read())
return info, body
def k8s_create_resource(module, url, data):
info, body = api_request(module, url, method="POST", data=data, headers={"Content-Type": "application/json"})
if info['status'] == 409:
name = data["metadata"].get("name", None)
info, body = api_request(module, url + "/" + name)
return False, body
elif info['status'] >= 400:
module.fail_json(msg="failed to create the resource: %s" % info['msg'], url=url)
return True, body
def k8s_delete_resource(module, url, data):
name = data.get('metadata', {}).get('name')
if name is None:
module.fail_json(msg="Missing a named resource in object metadata when trying to remove a resource")
url = url + '/' + name
info, body = api_request(module, url, method="DELETE")
if info['status'] == 404:
return False, "Resource name '%s' already absent" % name
elif info['status'] >= 400:
module.fail_json(msg="failed to delete the resource '%s': %s" % (name, info['msg']), url=url)
return True, "Successfully deleted resource name '%s'" % name
def k8s_replace_resource(module, url, data):
name = data.get('metadata', {}).get('name')
if name is None:
module.fail_json(msg="Missing a named resource in object metadata when trying to replace a resource")
headers = {"Content-Type": "application/json"}
url = url + '/' + name
info, body = api_request(module, url, method="PUT", data=data, headers=headers)
if info['status'] == 409:
name = data["metadata"].get("name", None)
info, body = api_request(module, url + "/" + name)
return False, body
elif info['status'] >= 400:
module.fail_json(msg="failed to replace the resource '%s': %s" % (name, info['msg']), url=url)
return True, body
def k8s_update_resource(module, url, data):
name = data.get('metadata', {}).get('name')
if name is None:
module.fail_json(msg="Missing a named resource in object metadata when trying to update a resource")
headers = {"Content-Type": "application/strategic-merge-patch+json"}
url = url + '/' + name
info, body = api_request(module, url, method="PATCH", data=data, headers=headers)
if info['status'] == 409:
name = data["metadata"].get("name", None)
info, body = api_request(module, url + "/" + name)
return False, body
elif info['status'] >= 400:
module.fail_json(msg="failed to update the resource '%s': %s" % (name, info['msg']), url=url)
return True, body
def main():
module = AnsibleModule(
argument_spec=dict(
http_agent=dict(default=USER_AGENT),
url_username=dict(default="admin", aliases=["username"]),
url_password=dict(default="", no_log=True, aliases=["password"]),
force_basic_auth=dict(default="yes"),
validate_certs=dict(default=False, type='bool'),
certificate_authority_data=dict(required=False),
insecure=dict(default=False, type='bool'),
api_endpoint=dict(required=True),
file_reference=dict(required=False),
inline_data=dict(required=False),
state=dict(default="present", choices=["present", "absent", "update", "replace"])
),
mutually_exclusive = (('file_reference', 'inline_data'),
('url_username', 'insecure'),
('url_password', 'insecure')),
required_one_of = (('file_reference', 'inline_data'),),
)
if not has_lib_yaml:
module.fail_json(msg="missing python library: yaml")
decode_cert_data(module)
api_endpoint = module.params.get('api_endpoint')
state = module.params.get('state')
insecure = module.params.get('insecure')
inline_data = module.params.get('inline_data')
file_reference = module.params.get('file_reference')
if inline_data:
if not isinstance(inline_data, dict) and not isinstance(inline_data, list):
data = yaml.load(inline_data)
else:
data = inline_data
else:
try:
f = open(file_reference, "r")
data = [x for x in yaml.load_all(f)]
f.close()
if not data:
module.fail_json(msg="No valid data could be found.")
except:
module.fail_json(msg="The file '%s' was not found or contained invalid YAML/JSON data" % file_reference)
# set the transport type and build the target endpoint url
transport = 'https'
if insecure:
transport = 'http'
target_endpoint = "%s://%s" % (transport, api_endpoint)
body = []
changed = False
# make sure the data is a list
if not isinstance(data, list):
data = [ data ]
for item in data:
namespace = "default"
if item and 'metadata' in item:
namespace = item.get('metadata', {}).get('namespace', "default")
kind = item.get('kind', '').lower()
try:
url = target_endpoint + KIND_URL[kind]
except KeyError:
module.fail_json(msg="invalid resource kind specified in the data: '%s'" % kind)
url = url.replace("{namespace}", namespace)
else:
url = target_endpoint
if state == 'present':
item_changed, item_body = k8s_create_resource(module, url, item)
elif state == 'absent':
item_changed, item_body = k8s_delete_resource(module, url, item)
elif state == 'replace':
item_changed, item_body = k8s_replace_resource(module, url, item)
elif state == 'update':
item_changed, item_body = k8s_update_resource(module, url, item)
changed |= item_changed
body.append(item_body)
module.exit_json(changed=changed, api_response=body)
# import module snippets
from ansible.module_utils.basic import * # NOQA
from ansible.module_utils.urls import * # NOQA
if __name__ == '__main__':
main()

View file

@ -0,0 +1,256 @@
#!/usr/bin/python
# Copyright 2015 WP Engine, Inc. All rights reserved.
#
# 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: znode
version_added: "2.0"
short_description: Create, delete, retrieve, and update znodes using ZooKeeper.
options:
hosts:
description:
- A list of ZooKeeper servers (format '[server]:[port]').
required: true
name:
description:
- The path of the znode.
required: true
value:
description:
- The value assigned to the znode.
default: None
required: false
op:
description:
- An operation to perform. Mutually exclusive with state.
default: None
required: false
state:
description:
- The state to enforce. Mutually exclusive with op.
default: None
required: false
timeout:
description:
- The amount of time to wait for a node to appear.
default: 300
required: false
recursive:
description:
- Recursively delete node and all its children.
default: False
required: false
version_added: "2.1"
requirements:
- kazoo >= 2.1
- python >= 2.6
author: "Trey Perry (@treyperry)"
"""
EXAMPLES = """
# Creating or updating a znode with a given value
- znode:
hosts: 'localhost:2181'
name: /mypath
value: myvalue
state: present
# Getting the value and stat structure for a znode
- znode:
hosts: 'localhost:2181'
name: /mypath
op: get
# Listing a particular znode's children
- znode:
hosts: 'localhost:2181'
name: /zookeeper
op: list
# Waiting 20 seconds for a znode to appear at path /mypath
- znode:
hosts: 'localhost:2181'
name: /mypath
op: wait
timeout: 20
# Deleting a znode at path /mypath
- znode:
hosts: 'localhost:2181'
name: /mypath
state: absent
"""
try:
from kazoo.client import KazooClient
from kazoo.exceptions import NoNodeError, ZookeeperError
from kazoo.handlers.threading import KazooTimeoutError
KAZOO_INSTALLED = True
except ImportError:
KAZOO_INSTALLED = False
def main():
module = AnsibleModule(
argument_spec=dict(
hosts=dict(required=True, type='str'),
name=dict(required=True, type='str'),
value=dict(required=False, default=None, type='str'),
op=dict(required=False, default=None, choices=['get', 'wait', 'list']),
state=dict(choices=['present', 'absent']),
timeout=dict(required=False, default=300, type='int'),
recursive=dict(required=False, default=False, type='bool')
),
supports_check_mode=False
)
if not KAZOO_INSTALLED:
module.fail_json(msg='kazoo >= 2.1 is required to use this module. Use pip to install it.')
check = check_params(module.params)
if not check['success']:
module.fail_json(msg=check['msg'])
zoo = KazooCommandProxy(module)
try:
zoo.start()
except KazooTimeoutError:
module.fail_json(msg='The connection to the ZooKeeper ensemble timed out.')
command_dict = {
'op': {
'get': zoo.get,
'list': zoo.list,
'wait': zoo.wait
},
'state': {
'present': zoo.present,
'absent': zoo.absent
}
}
command_type = 'op' if 'op' in module.params and module.params['op'] is not None else 'state'
method = module.params[command_type]
result, result_dict = command_dict[command_type][method]()
zoo.shutdown()
if result:
module.exit_json(**result_dict)
else:
module.fail_json(**result_dict)
def check_params(params):
if not params['state'] and not params['op']:
return {'success': False, 'msg': 'Please define an operation (op) or a state.'}
if params['state'] and params['op']:
return {'success': False, 'msg': 'Please choose an operation (op) or a state, but not both.'}
return {'success': True}
class KazooCommandProxy():
def __init__(self, module):
self.module = module
self.zk = KazooClient(module.params['hosts'])
def absent(self):
return self._absent(self.module.params['name'])
def exists(self, znode):
return self.zk.exists(znode)
def list(self):
children = self.zk.get_children(self.module.params['name'])
return True, {'count': len(children), 'items': children, 'msg': 'Retrieved znodes in path.',
'znode': self.module.params['name']}
def present(self):
return self._present(self.module.params['name'], self.module.params['value'])
def get(self):
return self._get(self.module.params['name'])
def shutdown(self):
self.zk.stop()
self.zk.close()
def start(self):
self.zk.start()
def wait(self):
return self._wait(self.module.params['name'], self.module.params['timeout'])
def _absent(self, znode):
if self.exists(znode):
self.zk.delete(znode, recursive=self.module.params['recursive'])
return True, {'changed': True, 'msg': 'The znode was deleted.'}
else:
return True, {'changed': False, 'msg': 'The znode does not exist.'}
def _get(self, path):
if self.exists(path):
value, zstat = self.zk.get(path)
stat_dict = {}
for i in dir(zstat):
if not i.startswith('_'):
attr = getattr(zstat, i)
if isinstance(attr, (int, str)):
stat_dict[i] = attr
result = True, {'msg': 'The node was retrieved.', 'znode': path, 'value': value,
'stat': stat_dict}
else:
result = False, {'msg': 'The requested node does not exist.'}
return result
def _present(self, path, value):
if self.exists(path):
(current_value, zstat) = self.zk.get(path)
if value != current_value:
self.zk.set(path, value)
return True, {'changed': True, 'msg': 'Updated the znode value.', 'znode': path,
'value': value}
else:
return True, {'changed': False, 'msg': 'No changes were necessary.', 'znode': path, 'value': value}
else:
self.zk.create(path, value, makepath=True)
return True, {'changed': True, 'msg': 'Created a new znode.', 'znode': path, 'value': value}
def _wait(self, path, timeout, interval=5):
lim = time.time() + timeout
while time.time() < lim:
if self.exists(path):
return True, {'msg': 'The node appeared before the configured timeout.',
'znode': path, 'timeout': timeout}
else:
time.sleep(interval)
return False, {'msg': 'The node did not appear before the operation timed out.', 'timeout': timeout,
'znode': path}
from ansible.module_utils.basic import *
if __name__ == '__main__':
main()