mirror of https://github.com/ansible/ansible.git
Merge branch 'devel' of github.com:ansible/ansible into devel
Conflicts: library/monitoring/pagerdutyreviewable/pr18780/r1
commit
9a9924b975
@ -0,0 +1,242 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: digital_ocean_domain
|
||||
short_description: Create/delete a DNS record in DigitalOcean
|
||||
description:
|
||||
- Create/delete a DNS record in DigitalOcean.
|
||||
version_added: "1.6"
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Indicate desired state of the target.
|
||||
default: present
|
||||
choices: ['present', 'active', 'absent', 'deleted']
|
||||
client_id:
|
||||
description:
|
||||
- Digital Ocean manager id.
|
||||
api_key:
|
||||
description:
|
||||
- Digital Ocean api key.
|
||||
id:
|
||||
description:
|
||||
- Numeric, the droplet id you want to operate on.
|
||||
name:
|
||||
description:
|
||||
- String, this is the name of the droplet - must be formatted by hostname rules, or the name of a SSH key, or the name of a domain.
|
||||
ip:
|
||||
description:
|
||||
- The IP address to point a domain at.
|
||||
|
||||
notes:
|
||||
- Two environment variables can be used, DO_CLIENT_ID and DO_API_KEY.
|
||||
'''
|
||||
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a domain record
|
||||
|
||||
- digital_ocean_domain: >
|
||||
state=present
|
||||
name=my.digitalocean.domain
|
||||
ip=127.0.0.1
|
||||
|
||||
# Create a droplet and a corresponding domain record
|
||||
|
||||
- digital_cean_droplet: >
|
||||
state=present
|
||||
name=test_droplet
|
||||
size_id=1
|
||||
region_id=2
|
||||
image_id=3
|
||||
register: test_droplet
|
||||
|
||||
- digital_ocean_domain: >
|
||||
state=present
|
||||
name={{ test_droplet.name }}.my.domain
|
||||
ip={{ test_droplet.ip_address }}
|
||||
'''
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
try:
|
||||
from dopy.manager import DoError, DoManager
|
||||
except ImportError as e:
|
||||
print "failed=True msg='dopy required for this module'"
|
||||
sys.exit(1)
|
||||
|
||||
class TimeoutError(DoError):
|
||||
def __init__(self, msg, id):
|
||||
super(TimeoutError, self).__init__(msg)
|
||||
self.id = id
|
||||
|
||||
class JsonfyMixIn(object):
|
||||
def to_json(self):
|
||||
return self.__dict__
|
||||
|
||||
class DomainRecord(JsonfyMixIn):
|
||||
manager = None
|
||||
|
||||
def __init__(self, json):
|
||||
self.__dict__.update(json)
|
||||
update_attr = __init__
|
||||
|
||||
def update(self, data = None, record_type = None):
|
||||
json = self.manager.edit_domain_record(self.domain_id,
|
||||
self.id,
|
||||
record_type if record_type is not None else self.record_type,
|
||||
data if data is not None else self.data)
|
||||
self.__dict__.update(json)
|
||||
return self
|
||||
|
||||
def destroy(self):
|
||||
json = self.manager.destroy_domain_record(self.domain_id, self.id)
|
||||
return json
|
||||
|
||||
class Domain(JsonfyMixIn):
|
||||
manager = None
|
||||
|
||||
def __init__(self, domain_json):
|
||||
self.__dict__.update(domain_json)
|
||||
|
||||
def destroy(self):
|
||||
self.manager.destroy_domain(self.id)
|
||||
|
||||
def records(self):
|
||||
json = self.manager.all_domain_records(self.id)
|
||||
return map(DomainRecord, json)
|
||||
|
||||
@classmethod
|
||||
def add(cls, name, ip):
|
||||
json = cls.manager.new_domain(name, ip)
|
||||
return cls(json)
|
||||
|
||||
@classmethod
|
||||
def setup(cls, client_id, api_key):
|
||||
cls.manager = DoManager(client_id, api_key)
|
||||
DomainRecord.manager = cls.manager
|
||||
|
||||
@classmethod
|
||||
def list_all(cls):
|
||||
domains = cls.manager.all_domains()
|
||||
return map(cls, domains)
|
||||
|
||||
@classmethod
|
||||
def find(cls, name=None, id=None):
|
||||
if name is None and id is None:
|
||||
return False
|
||||
|
||||
domains = Domain.list_all()
|
||||
|
||||
if id is not None:
|
||||
for domain in domains:
|
||||
if domain.id == id:
|
||||
return domain
|
||||
|
||||
if name is not None:
|
||||
for domain in domains:
|
||||
if domain.name == name:
|
||||
return domain
|
||||
|
||||
return False
|
||||
|
||||
def core(module):
|
||||
def getkeyordie(k):
|
||||
v = module.params[k]
|
||||
if v is None:
|
||||
module.fail_json(msg='Unable to load %s' % k)
|
||||
return v
|
||||
|
||||
try:
|
||||
# params['client_id'] will be None even if client_id is not passed in
|
||||
client_id = module.params['client_id'] or os.environ['DO_CLIENT_ID']
|
||||
api_key = module.params['api_key'] or os.environ['DO_API_KEY']
|
||||
except KeyError, e:
|
||||
module.fail_json(msg='Unable to load %s' % e.message)
|
||||
|
||||
changed = True
|
||||
state = module.params['state']
|
||||
|
||||
Domain.setup(client_id, api_key)
|
||||
if state in ('present'):
|
||||
domain = Domain.find(id=module.params["id"])
|
||||
|
||||
if not domain:
|
||||
domain = Domain.find(name=getkeyordie("name"))
|
||||
|
||||
if not domain:
|
||||
domain = Domain.add(getkeyordie("name"),
|
||||
getkeyordie("ip"))
|
||||
module.exit_json(changed=True, domain=domain.to_json())
|
||||
else:
|
||||
records = domain.records()
|
||||
at_record = None
|
||||
for record in records:
|
||||
if record.name == "@":
|
||||
at_record = record
|
||||
|
||||
if not at_record.data == getkeyordie("ip"):
|
||||
record.update(data=getkeyordie("ip"), record_type='A')
|
||||
module.exit_json(changed=True, domain=Domain.find(id=record.domain_id).to_json())
|
||||
|
||||
module.exit_json(changed=False, domain=domain.to_json())
|
||||
|
||||
elif state in ('absent'):
|
||||
domain = None
|
||||
if "id" in module.params:
|
||||
domain = Domain.find(id=module.params["id"])
|
||||
|
||||
if not domain and "name" in module.params:
|
||||
domain = Domain.find(name=module.params["name"])
|
||||
|
||||
if not domain:
|
||||
module.exit_json(changed=False, msg="Domain not found.")
|
||||
|
||||
event_json = domain.destroy()
|
||||
module.exit_json(changed=True, event=event_json)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
state = dict(choices=['active', 'present', 'absent', 'deleted'], default='present'),
|
||||
client_id = dict(aliases=['CLIENT_ID'], no_log=True),
|
||||
api_key = dict(aliases=['API_KEY'], no_log=True),
|
||||
name = dict(type='str'),
|
||||
id = dict(aliases=['droplet_id'], type='int'),
|
||||
ip = dict(type='str'),
|
||||
),
|
||||
required_one_of = (
|
||||
['id', 'name'],
|
||||
),
|
||||
)
|
||||
|
||||
try:
|
||||
core(module)
|
||||
except TimeoutError as e:
|
||||
module.fail_json(msg=str(e), id=e.id)
|
||||
except (DoError, Exception) as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
@ -0,0 +1,178 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: digital_ocean_sshkey
|
||||
short_description: Create/delete an SSH key in DigitalOcean
|
||||
description:
|
||||
- Create/delete an SSH key.
|
||||
version_added: "1.6"
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Indicate desired state of the target.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
client_id:
|
||||
description:
|
||||
- Digital Ocean manager id.
|
||||
api_key:
|
||||
description:
|
||||
- Digital Ocean api key.
|
||||
id:
|
||||
description:
|
||||
- Numeric, the SSH key id you want to operate on.
|
||||
name:
|
||||
description:
|
||||
- String, this is the name of an SSH key to create or destroy.
|
||||
ssh_pub_key:
|
||||
description:
|
||||
- The public SSH key you want to add to your account.
|
||||
|
||||
notes:
|
||||
- Two environment variables can be used, DO_CLIENT_ID and DO_API_KEY.
|
||||
'''
|
||||
|
||||
|
||||
EXAMPLES = '''
|
||||
# Ensure a SSH key is present
|
||||
# If a key matches this name, will return the ssh key id and changed = False
|
||||
# If no existing key matches this name, a new key is created, the ssh key id is returned and changed = False
|
||||
|
||||
- digital_ocean_sshkey: >
|
||||
state=present
|
||||
name=my_ssh_key
|
||||
ssh_pub_key='ssh-rsa AAAA...'
|
||||
client_id=XXX
|
||||
api_key=XXX
|
||||
|
||||
'''
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
try:
|
||||
from dopy.manager import DoError, DoManager
|
||||
except ImportError as e:
|
||||
print "failed=True msg='dopy required for this module'"
|
||||
sys.exit(1)
|
||||
|
||||
class TimeoutError(DoError):
|
||||
def __init__(self, msg, id):
|
||||
super(TimeoutError, self).__init__(msg)
|
||||
self.id = id
|
||||
|
||||
class JsonfyMixIn(object):
|
||||
def to_json(self):
|
||||
return self.__dict__
|
||||
|
||||
class SSH(JsonfyMixIn):
|
||||
manager = None
|
||||
|
||||
def __init__(self, ssh_key_json):
|
||||
self.__dict__.update(ssh_key_json)
|
||||
update_attr = __init__
|
||||
|
||||
def destroy(self):
|
||||
self.manager.destroy_ssh_key(self.id)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def setup(cls, client_id, api_key):
|
||||
cls.manager = DoManager(client_id, api_key)
|
||||
|
||||
@classmethod
|
||||
def find(cls, name):
|
||||
if not name:
|
||||
return False
|
||||
keys = cls.list_all()
|
||||
for key in keys:
|
||||
if key.name == name:
|
||||
return key
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def list_all(cls):
|
||||
json = cls.manager.all_ssh_keys()
|
||||
return map(cls, json)
|
||||
|
||||
@classmethod
|
||||
def add(cls, name, key_pub):
|
||||
json = cls.manager.new_ssh_key(name, key_pub)
|
||||
return cls(json)
|
||||
|
||||
def core(module):
|
||||
def getkeyordie(k):
|
||||
v = module.params[k]
|
||||
if v is None:
|
||||
module.fail_json(msg='Unable to load %s' % k)
|
||||
return v
|
||||
|
||||
try:
|
||||
# params['client_id'] will be None even if client_id is not passed in
|
||||
client_id = module.params['client_id'] or os.environ['DO_CLIENT_ID']
|
||||
api_key = module.params['api_key'] or os.environ['DO_API_KEY']
|
||||
except KeyError, e:
|
||||
module.fail_json(msg='Unable to load %s' % e.message)
|
||||
|
||||
changed = True
|
||||
state = module.params['state']
|
||||
|
||||
SSH.setup(client_id, api_key)
|
||||
name = getkeyordie('name')
|
||||
if state in ('present'):
|
||||
key = SSH.find(name)
|
||||
if key:
|
||||
module.exit_json(changed=False, ssh_key=key.to_json())
|
||||
key = SSH.add(name, getkeyordie('ssh_pub_key'))
|
||||
module.exit_json(changed=True, ssh_key=key.to_json())
|
||||
|
||||
elif state in ('absent'):
|
||||
key = SSH.find(name)
|
||||
if not key:
|
||||
module.exit_json(changed=False, msg='SSH key with the name of %s is not found.' % name)
|
||||
key.destroy()
|
||||
module.exit_json(changed=True)
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
state = dict(choices=['present', 'absent'], default='present'),
|
||||
client_id = dict(aliases=['CLIENT_ID'], no_log=True),
|
||||
api_key = dict(aliases=['API_KEY'], no_log=True),
|
||||
name = dict(type='str'),
|
||||
id = dict(aliases=['droplet_id'], type='int'),
|
||||
ssh_pub_key = dict(type='str'),
|
||||
),
|
||||
required_one_of = (
|
||||
['id', 'name'],
|
||||
),
|
||||
)
|
||||
|
||||
try:
|
||||
core(module)
|
||||
except TimeoutError as e:
|
||||
module.fail_json(msg=str(e), id=e.id)
|
||||
except (DoError, Exception) as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
@ -0,0 +1,196 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# (c) 2013, Nimbis Services
|
||||
#
|
||||
# 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: ec2_ami_search
|
||||
short_description: Retrieve AWS AMI for a given operating system.
|
||||
version_added: "1.6"
|
||||
description:
|
||||
- Look up the most recent AMI on AWS for a given operating system.
|
||||
- Returns C(ami), C(aki), C(ari), C(serial), C(tag)
|
||||
- If there is no AKI or ARI associated with an image, these will be C(null).
|
||||
- Only supports images from cloud-images.ubuntu.com
|
||||
- 'Example output: C({"ami": "ami-69f5a900", "changed": false, "aki": "aki-88aa75e1", "tag": "release", "ari": null, "serial": "20131024"})'
|
||||
version_added: "1.6"
|
||||
options:
|
||||
distro:
|
||||
description: Linux distribution (e.g., C(ubuntu))
|
||||
required: true
|
||||
choices: ["ubuntu"]
|
||||
release:
|
||||
description: short name of the release (e.g., C(precise))
|
||||
required: true
|
||||
stream:
|
||||
description: Type of release.
|
||||
required: false
|
||||
default: "server"
|
||||
choices: ["server", "desktop"]
|
||||
store:
|
||||
description: Back-end store for instance
|
||||
required: false
|
||||
default: "ebs"
|
||||
choices: ["ebs", "instance-store"]
|
||||
arch:
|
||||
description: CPU architecture
|
||||
required: false
|
||||
default: "amd64"
|
||||
choices: ["i386", "amd64"]
|
||||
region:
|
||||
description: EC2 region
|
||||
required: false
|
||||
default: us-east-1
|
||||
choices: ["ap-northeast-1", "ap-southeast-1", "ap-southeast-2",
|
||||
"eu-west-1", "sa-east-1", "us-east-1", "us-west-1", "us-west-2"]
|
||||
virt:
|
||||
description: virutalization type
|
||||
required: false
|
||||
default: paravirtual
|
||||
choices: ["paravirtual", "hvm"]
|
||||
|
||||
author: Lorin Hochstein
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Launch an Ubuntu 12.04 (Precise Pangolin) EC2 instance
|
||||
hosts: 127.0.0.1
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Get the Ubuntu precise AMI
|
||||
ec2_ami_search: distro=ubuntu release=precise region=us-west-1 store=instance-store
|
||||
register: ubuntu_image
|
||||
- name: Start the EC2 instance
|
||||
ec2: image={{ ubuntu_image.ami }} instance_type=m1.small key_name=mykey
|
||||
'''
|
||||
|
||||
import csv
|
||||
import json
|
||||
import urllib2
|
||||
import urlparse
|
||||
|
||||
SUPPORTED_DISTROS = ['ubuntu']
|
||||
|
||||
AWS_REGIONS = ['ap-northeast-1',
|
||||
'ap-southeast-1',
|
||||
'ap-southeast-2',
|
||||
'eu-west-1',
|
||||
'sa-east-1',
|
||||
'us-east-1',
|
||||
'us-west-1',
|
||||
'us-west-2']
|
||||
|
||||
|
||||
def get_url(module, url):
|
||||
""" Get url and return response """
|
||||
try:
|
||||
r = urllib2.urlopen(url)
|
||||
except (urllib2.HTTPError, urllib2.URLError), e:
|
||||
code = getattr(e, 'code', -1)
|
||||
module.fail_json(msg="Request failed: %s" % str(e), status_code=code)
|
||||
return r
|
||||
|
||||
|
||||
def ubuntu(module):
|
||||
""" Get the ami for ubuntu """
|
||||
|
||||
release = module.params['release']
|
||||
stream = module.params['stream']
|
||||
store = module.params['store']
|
||||
arch = module.params['arch']
|
||||
region = module.params['region']
|
||||
virt = module.params['virt']
|
||||
|
||||
url = get_ubuntu_url(release, stream)
|
||||
|
||||
req = get_url(module, url)
|
||||
reader = csv.reader(req, delimiter='\t')
|
||||
try:
|
||||
ami, aki, ari, tag, serial = lookup_ubuntu_ami(reader, release, stream,
|
||||
store, arch, region, virt)
|
||||
module.exit_json(changed=False, ami=ami, aki=aki, ari=ari, tag=tag,
|
||||
serial=serial)
|
||||
except KeyError:
|
||||
module.fail_json(msg="No matching AMI found")
|
||||
|
||||
|
||||
def lookup_ubuntu_ami(table, release, stream, store, arch, region, virt):
|
||||
""" Look up the Ubuntu AMI that matches query given a table of AMIs
|
||||
|
||||
table: an iterable that returns a row of
|
||||
(release, stream, tag, serial, region, ami, aki, ari, virt)
|
||||
release: ubuntu release name
|
||||
stream: 'server' or 'desktop'
|
||||
store: 'ebs' or 'instance-store'
|
||||
arch: 'i386' or 'amd64'
|
||||
region: EC2 region
|
||||
virt: 'paravirtual' or 'hvm'
|
||||
|
||||
Returns (ami, aki, ari, tag, serial)"""
|
||||
expected = (release, stream, store, arch, region, virt)
|
||||
|
||||
for row in table:
|
||||
(actual_release, actual_stream, tag, serial,
|
||||
actual_store, actual_arch, actual_region, ami, aki, ari,
|
||||
actual_virt) = row
|
||||
actual = (actual_release, actual_stream, actual_store, actual_arch,
|
||||
actual_region, actual_virt)
|
||||
if actual == expected:
|
||||
# aki and ari are sometimes blank
|
||||
if aki == '':
|
||||
aki = None
|
||||
if ari == '':
|
||||
ari = None
|
||||
return (ami, aki, ari, tag, serial)
|
||||
|
||||
raise KeyError()
|
||||
|
||||
|
||||
def get_ubuntu_url(release, stream):
|
||||
url = "https://cloud-images.ubuntu.com/query/%s/%s/released.current.txt"
|
||||
return url % (release, stream)
|
||||
|
||||
|
||||
def main():
|
||||
arg_spec = dict(
|
||||
distro=dict(required=True, choices=SUPPORTED_DISTROS),
|
||||
release=dict(required=True),
|
||||
stream=dict(required=False, default='server',
|
||||
choices=['desktop', 'server']),
|
||||
store=dict(required=False, default='ebs',
|
||||
choices=['ebs', 'instance-store']),
|
||||
arch=dict(required=False, default='amd64',
|
||||
choices=['i386', 'amd64']),
|
||||
region=dict(required=False, default='us-east-1', choices=AWS_REGIONS),
|
||||
virt=dict(required=False, default='paravirtual',
|
||||
choices=['paravirtual', 'hvm'])
|
||||
)
|
||||
module = AnsibleModule(argument_spec=arg_spec)
|
||||
distro = module.params['distro']
|
||||
|
||||
if distro == 'ubuntu':
|
||||
ubuntu(module)
|
||||
else:
|
||||
module.fail_json(msg="Unsupported distro: %s" % distro)
|
||||
|
||||
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,219 @@
|
||||
#!/usr/bin/python
|
||||
# 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: ec2_asg
|
||||
short_description: Create or delete AWS Autoscaling Groups
|
||||
description:
|
||||
- Can create or delete AWS Autoscaling Groups
|
||||
- Works with the ec2_lc module to manage Launch Configurations
|
||||
version_added: "1.6"
|
||||
author: Gareth Rushgrove
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- register or deregister the instance
|
||||
required: true
|
||||
choices: ['present', 'absent']
|
||||
name:
|
||||
description:
|
||||
- Unique name for group to be created or deleted
|
||||
required: true
|
||||
load_balancers:
|
||||
description:
|
||||
- List of ELB names to use for the group
|
||||
required: false
|
||||
availability_zones:
|
||||
description:
|
||||
- List of availability zone names in which to create the group.
|
||||
required: false
|
||||
launch_config_name:
|
||||
description:
|
||||
- Name of the Launch configuration to use for the group. See the ec2_lc module for managing these.
|
||||
required: false
|
||||
min_size:
|
||||
description:
|
||||
- Minimum number of instances in group
|
||||
required: false
|
||||
max_size:
|
||||
description:
|
||||
- Maximum number of instances in group
|
||||
required: false
|
||||
desired_capacity:
|
||||
description:
|
||||
- Desired number of instances in group
|
||||
required: false
|
||||
region:
|
||||
description:
|
||||
- The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used.
|
||||
required: false
|
||||
aliases: ['aws_region', 'ec2_region']
|
||||
vpc_zone_identifier:
|
||||
description:
|
||||
- List of VPC subnets to use
|
||||
required: false
|
||||
default: None
|
||||
extends_documentation_fragment: aws
|
||||
"""
|
||||
|
||||
EXAMPLES = '''
|
||||
- ec2_asg:
|
||||
name: special
|
||||
load_balancers: 'lb1,lb2'
|
||||
availability_zones: 'eu-west-1a,eu-west-1b'
|
||||
launch_config_name: 'lc-1'
|
||||
min_size: 1
|
||||
max_size: 10
|
||||
desired_capacity: 5
|
||||
vpc_zone_identifier: 'subnet-abcd1234,subnet-1a2b3c4d'
|
||||
'''
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.ec2 import *
|
||||
|
||||
try:
|
||||
import boto.ec2.autoscale
|
||||
from boto.ec2.autoscale import AutoScaleConnection, AutoScalingGroup
|
||||
from boto.exception import BotoServerError
|
||||
except ImportError:
|
||||
print "failed=True msg='boto required for this module'"
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def enforce_required_arguments(module):
|
||||
''' As many arguments are not required for autoscale group deletion
|
||||
they cannot be mandatory arguments for the module, so we enforce
|
||||
them here '''
|
||||
missing_args = []
|
||||
for arg in ('min_size', 'max_size', 'launch_config_name', 'availability_zones'):
|
||||
if module.params[arg] is None:
|
||||
missing_args.append(arg)
|
||||
if missing_args:
|
||||
module.fail_json(msg="Missing required arguments for autoscaling group create/update: %s" % ",".join(missing_args))
|
||||
|
||||
|
||||
def create_autoscaling_group(connection, module):
|
||||
enforce_required_arguments(module)
|
||||
|
||||
group_name = module.params.get('name')
|
||||
load_balancers = module.params['load_balancers']
|
||||
availability_zones = module.params['availability_zones']
|
||||
launch_config_name = module.params.get('launch_config_name')
|
||||
min_size = module.params['min_size']
|
||||
max_size = module.params['max_size']
|
||||
desired_capacity = module.params.get('desired_capacity')
|
||||
vpc_zone_identifier = module.params.get('vpc_zone_identifier')
|
||||
|
||||
launch_configs = connection.get_all_launch_configurations(names=[launch_config_name])
|
||||
|
||||
as_groups = connection.get_all_groups(names=[group_name])
|
||||
|
||||
if not as_groups:
|
||||
ag = AutoScalingGroup(
|
||||
group_name=group_name,
|
||||
load_balancers=load_balancers,
|
||||
availability_zones=availability_zones,
|
||||
launch_config=launch_configs[0],
|
||||
min_size=min_size,
|
||||
max_size=max_size,
|
||||
desired_capacity=desired_capacity,
|
||||
vpc_zone_identifier=vpc_zone_identifier,
|
||||
connection=connection)
|
||||
|
||||
try:
|
||||
connection.create_auto_scaling_group(ag)
|
||||
module.exit_json(changed=True)
|
||||
except BotoServerError, e:
|
||||
module.fail_json(msg=str(e))
|
||||
else:
|
||||
as_group = as_groups[0]
|
||||
changed = False
|
||||
for attr in ('launch_config_name', 'max_size', 'min_size', 'desired_capacity',
|
||||
'vpc_zone_identifier', 'availability_zones'):
|
||||
if getattr(as_group, attr) != module.params.get(attr):
|
||||
changed = True
|
||||
setattr(as_group, attr, module.params.get(attr))
|
||||
# handle loadbalancers separately because None != []
|
||||
load_balancers = module.params.get('load_balancers') or []
|
||||
if as_group.load_balancers != load_balancers:
|
||||
changed = True
|
||||
as_group.load_balancers = module.params.get('load_balancers')
|
||||
|
||||
try:
|
||||
if changed:
|
||||
as_group.update()
|
||||
module.exit_json(changed=changed)
|
||||
except BotoServerError, e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
def delete_autoscaling_group(connection, module):
|
||||
group_name = module.params.get('name')
|
||||
groups = connection.get_all_groups(names=[group_name])
|
||||
if groups:
|
||||
group = groups[0]
|
||||
group.shutdown_instances()
|
||||
|
||||
instances = True
|
||||
while instances:
|
||||
connection.get_all_groups()
|
||||
for group in groups:
|
||||
if group.name == group_name:
|
||||
if not group.instances:
|
||||
instances = False
|
||||
time.sleep(10)
|
||||
|
||||
group.delete()
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = ec2_argument_spec()
|
||||
argument_spec.update(
|
||||
dict(
|
||||
name=dict(required=True, type='str'),
|
||||
load_balancers=dict(type='list'),
|
||||
availability_zones=dict(type='list'),
|
||||
launch_config_name=dict(type='str'),
|
||||
min_size=dict(type='int'),
|
||||
max_size=dict(type='int'),
|
||||
desired_capacity=dict(type='int'),
|
||||
vpc_zone_identifier=dict(type='str'),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
)
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argument_spec)
|
||||
|
||||
state = module.params.get('state')
|
||||
|
||||
region, ec2_url, aws_connect_params = get_aws_connection_info(module)
|
||||
try:
|
||||
connection = connect_to_aws(boto.ec2.autoscale, region, **aws_connect_params)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
if state == 'present':
|
||||
create_autoscaling_group(connection, module)
|
||||
elif state == 'absent':
|
||||
delete_autoscaling_group(connection, module)
|
||||
|
||||
main()
|
@ -0,0 +1,199 @@
|
||||
#!/usr/bin/python
|
||||
# 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: ec2_lc
|
||||
short_description: Create or delete AWS Autoscaling Launch Configurations
|
||||
description:
|
||||
- Can create or delete AwS Autoscaling Configurations
|
||||
- Works with the ec2_asg module to manage Autoscaling Groups
|
||||
version_added: "1.6"
|
||||
author: Gareth Rushgrove
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- register or deregister the instance
|
||||
required: true
|
||||
choices: ['present', 'absent']
|
||||
name:
|
||||
description:
|
||||
- Unique name for configuration
|
||||
required: true
|
||||
image_id:
|
||||
description:
|
||||
- The AMI unique identifier to be used for the group
|
||||
required: false
|
||||
key_name:
|
||||
description:
|
||||
- The SSH key name to be used for access to managed instances
|
||||
required: false
|
||||
security_groups:
|
||||
description:
|
||||
- A list of security groups into which instances should be found
|
||||
required: false
|
||||
region:
|
||||
description:
|
||||
- The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used.
|
||||
required: false
|
||||
aliases: ['aws_region', 'ec2_region']
|
||||
volumes:
|
||||
description:
|
||||
- a list of volume dicts, each containing device name and optionally ephemeral id or snapshot id. Size and type (and number of iops for io device type) must be specified for a new volume or a root volume, and may be passed for a snapshot volume. For any volume, a volume size less than 1 will be interpreted as a request not to create the volume.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
user_data:
|
||||
description:
|
||||
- opaque blob of data which is made available to the ec2 instance
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
extends_documentation_fragment: aws
|
||||
"""
|
||||
|
||||
EXAMPLES = '''
|
||||
- ec2_lc:
|
||||
name: special
|
||||
image_id: ami-XXX
|
||||
key_name: default
|
||||
security_groups: 'group,group2'
|
||||
|
||||
'''
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.ec2 import *
|
||||
|
||||
try:
|
||||
from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping
|
||||
import boto.ec2.autoscale
|
||||
from boto.ec2.autoscale import LaunchConfiguration
|
||||
from boto.exception import BotoServerError
|
||||
except ImportError:
|
||||
print "failed=True msg='boto required for this module'"
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def create_block_device(module, volume):
|
||||
# Not aware of a way to determine this programatically
|
||||
# http://aws.amazon.com/about-aws/whats-new/2013/10/09/ebs-provisioned-iops-maximum-iops-gb-ratio-increased-to-30-1/
|
||||
MAX_IOPS_TO_SIZE_RATIO = 30
|
||||
if 'snapshot' not in volume and 'ephemeral' not in volume:
|
||||
if 'volume_size' not in volume:
|
||||
module.fail_json(msg='Size must be specified when creating a new volume or modifying the root volume')
|
||||
if 'snapshot' in volume:
|
||||
if 'device_type' in volume and volume.get('device_type') == 'io1' and 'iops' not in volume:
|
||||
module.fail_json(msg='io1 volumes must have an iops value set')
|
||||
if 'ephemeral' in volume:
|
||||
if 'snapshot' in volume:
|
||||
module.fail_json(msg='Cannot set both ephemeral and snapshot')
|
||||
return BlockDeviceType(snapshot_id=volume.get('snapshot'),
|
||||
ephemeral_name=volume.get('ephemeral'),
|
||||
size=volume.get('volume_size'),
|
||||
volume_type=volume.get('device_type'),
|
||||
delete_on_termination=volume.get('delete_on_termination', False),
|
||||
iops=volume.get('iops'))
|
||||
|
||||
|
||||
def create_launch_config(connection, module):
|
||||
name = module.params.get('name')
|
||||
image_id = module.params.get('image_id')
|
||||
key_name = module.params.get('key_name')
|
||||
security_groups = module.params['security_groups']
|
||||
user_data = module.params.get('user_data')
|
||||
volumes = module.params['volumes']
|
||||
instance_type = module.params.get('instance_type')
|
||||
bdm = BlockDeviceMapping()
|
||||
|
||||
if volumes:
|
||||
for volume in volumes:
|
||||
if 'device_name' not in volume:
|
||||
module.fail_json(msg='Device name must be set for volume')
|
||||
# Minimum volume size is 1GB. We'll use volume size explicitly set to 0
|
||||
# to be a signal not to create this volume
|
||||
if 'volume_size' not in volume or int(volume['volume_size']) > 0:
|
||||
bdm[volume['device_name']] = create_block_device(module, volume)
|
||||
|
||||
lc = LaunchConfiguration(
|
||||
name=name,
|
||||
image_id=image_id,
|
||||
key_name=key_name,
|
||||
security_groups=security_groups,
|
||||
user_data=user_data,
|
||||
block_device_mappings=[bdm],
|
||||
instance_type=instance_type)
|
||||
|
||||
launch_configs = connection.get_all_launch_configurations(names=[name])
|
||||
changed = False
|
||||
if not launch_configs:
|
||||
try:
|
||||
connection.create_launch_configuration(lc)
|
||||
launch_configs = connection.get_all_launch_configurations(names=[name])
|
||||
changed = True
|
||||
except BotoServerError, e:
|
||||
module.fail_json(msg=str(e))
|
||||
result = launch_configs[0]
|
||||
|
||||
module.exit_json(changed=changed, name=result.name, created_time=str(result.created_time),
|
||||
image_id=result.image_id, arn=result.launch_configuration_arn,
|
||||
security_groups=result.security_groups, instance_type=instance_type)
|
||||
|
||||
|
||||
def delete_launch_config(connection, module):
|
||||
name = module.params.get('name')
|
||||
launch_configs = connection.get_all_launch_configurations(names=[name])
|
||||
if launch_configs:
|
||||
launch_configs[0].delete()
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = ec2_argument_spec()
|
||||
argument_spec.update(
|
||||
dict(
|
||||
name=dict(required=True, type='str'),
|
||||
image_id=dict(type='str'),
|
||||
key_name=dict(type='str'),
|
||||
security_groups=dict(type='list'),
|
||||
user_data=dict(type='str'),
|
||||
volumes=dict(type='list'),
|
||||
instance_type=dict(type='str'),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
)
|
||||
)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec)
|
||||
|
||||
region, ec2_url, aws_connect_params = get_aws_connection_info(module)
|
||||
|
||||
try:
|
||||
connection = connect_to_aws(boto.ec2.autoscale, region, **aws_connect_params)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
state = module.params.get('state')
|
||||
|
||||
if state == 'present':
|
||||
create_launch_config(connection, module)
|
||||
elif state == 'absent':
|
||||
delete_launch_config(connection, module)
|
||||
|
||||
main()
|
@ -0,0 +1,264 @@
|
||||
#!/usr/bin/python
|
||||
# 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: ec2_metric_alarm
|
||||
short_description: "Create/update or delete AWS Cloudwatch 'metric alarms'"
|
||||
description:
|
||||
- Can create or delete AWS metric alarms
|
||||
- Metrics you wish to alarm on must already exist
|
||||
version_added: "1.6"
|
||||
author: Zacharie Eakin
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- register or deregister the alarm
|
||||
required: true
|
||||
choices: ['present', 'absent']
|
||||
name:
|
||||
desciption:
|
||||
- Unique name for the alarm
|
||||
required: true
|
||||
metric:
|
||||
description:
|
||||
- Name of the monitored metric (e.g. CPUUtilization)
|
||||
- Metric must already exist
|
||||
required: false
|
||||
namespace:
|
||||
description:
|
||||
- Name of the appropriate namespace, which determines the category it will appear under in cloudwatch
|
||||
required: false
|
||||
options: ['AWS/AutoScaling','AWS/Billing','AWS/DynamoDB','AWS/ElastiCache','AWS/EBS','AWS/EC2','AWS/ELB','AWS/ElasticMapReduce','AWS/OpsWorks','AWS/Redshift','AWS/RDS','AWS/Route53','AWS/SNS','AWS/SQS','AWS/StorageGateway']
|
||||
statistic:
|
||||
description:
|
||||
- Operation applied to the metric
|
||||
- Works in conjunction with period and evaluation_periods to determine the comparison value
|
||||
required: false
|
||||
options: ['SampleCount','Average','Sum','Minimum','Maximum']
|
||||
comparison:
|
||||
description:
|
||||
- Determines how the threshold value is compared
|
||||
required: false
|
||||
options: ['<=','<','>','>=']
|
||||
threshold:
|
||||
description:
|
||||
- Sets the min/max bound for triggering the alarm
|
||||
required: false
|
||||
period:
|
||||
description:
|
||||
- The time (in seconds) between metric evaluations
|
||||
required: false
|
||||
evaluation_periods:
|
||||
description:
|
||||
- The number of times in which the metric is evaluated before final calculation
|
||||
required: false
|
||||
unit:
|
||||
description:
|
||||
- The threshold's unit of measurement
|
||||
required: false
|
||||
options: ['Seconds','Microseconds','Milliseconds','Bytes','Kilobytes','Megabytes','Gigabytes','Terabytes','Bits','Kilobits','Megabits','Gigabits','Terabits','Percent','Count','Bytes/Second','Kilobytes/Second','Megabytes/Second','Gigabytes/Second','Terabytes/Second','Bits/Second','Kilobits/Second','Megabits/Second','Gigabits/Second','Terabits/Second','Count/Second','None']
|
||||
description:
|
||||
description:
|
||||
- A longer desciption of the alarm
|
||||
required: false
|
||||
dimensions:
|
||||
description:
|
||||
- Describes to what the alarm is applied
|
||||
required: false
|
||||
alarm_actions:
|
||||
description:
|
||||
- A list of the names action(s) taken when the alarm is in the 'alarm' status
|
||||
required: false
|
||||
insufficient_data_actions:
|
||||
description:
|
||||
- A list of the names of action(s) to take when the alarm is in the 'insufficient_data' status
|
||||
required: false
|
||||
ok_actions:
|
||||
description:
|
||||
- A list of the names of action(s) to take when the alarm is in the 'ok' status
|
||||
required: false
|
||||
extends_documentation_fragment: aws
|
||||
"""
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: create alarm
|
||||
ec2_metric_alarm:
|
||||
state: present
|
||||
region: ap-southeast-2
|
||||
name: "cpu-low"
|
||||
metric: "CPUUtilization"
|
||||
namespace: "AWS/EC2"
|
||||
statistic: Average
|
||||
comparison: "<="
|
||||
threshold: 5.0
|
||||
period: 300
|
||||
evaluation_periods: 3
|
||||
unit: "Percent"
|
||||
description: "This will alarm when a bamboo slave's cpu usage average is lower than 5% for 15 minutes "
|
||||
dimensions: {'InstanceId':'i-XXX'}
|
||||
alarm_actions: ["action1","action2"]
|
||||
|
||||
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.ec2 import *
|
||||
|
||||
try:
|
||||
import boto.ec2.cloudwatch
|
||||
from boto.ec2.cloudwatch import CloudWatchConnection, MetricAlarm
|
||||
from boto.exception import BotoServerError
|
||||
except ImportError:
|
||||
print "failed=True msg='boto required for this module'"
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def create_metric_alarm(connection, module):
|
||||
|
||||
name = module.params.get('name')
|
||||
metric = module.params.get('metric')
|
||||
namespace = module.params.get('namespace')
|
||||
statistic = module.params.get('statistic')
|
||||
comparison = module.params.get('comparison')
|
||||
threshold = module.params.get('threshold')
|
||||
period = module.params.get('period')
|
||||
evaluation_periods = module.params.get('evaluation_periods')
|
||||
unit = module.params.get('unit')
|
||||
description = module.params.get('description')
|
||||
dimensions = module.params.get('dimensions')
|
||||
alarm_actions = module.params.get('alarm_actions')
|
||||
insufficient_data_actions = module.params.get('insufficient_data_actions')
|
||||
ok_actions = module.params.get('ok_actions')
|
||||
|
||||
alarms = connection.describe_alarms(alarm_names=[name])
|
||||
|
||||
if not alarms:
|
||||
|
||||
alm = MetricAlarm(
|
||||
name=name,
|
||||
metric=metric,
|
||||
namespace=namespace,
|
||||
statistic=statistic,
|
||||
comparison=comparison,
|
||||
threshold=threshold,
|
||||
period=period,
|
||||
evaluation_periods=evaluation_periods,
|
||||
unit=unit,
|
||||
description=description,
|
||||
dimensions=dimensions,
|
||||
alarm_actions=alarm_actions,
|
||||
insufficient_data_actions=insufficient_data_actions,
|
||||
ok_actions=ok_actions
|
||||
)
|
||||
try:
|
||||
connection.create_alarm(alm)
|
||||
module.exit_json(changed=True)
|
||||
except BotoServerError, e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
else:
|
||||
alarm = alarms[0]
|
||||
changed = False
|
||||
|
||||
for attr in ('comparison','metric','namespace','statistic','threshold','period','evaluation_periods','unit','description'):
|
||||
if getattr(alarm, attr) != module.params.get(attr):
|
||||
changed = True
|
||||
setattr(alarm, attr, module.params.get(attr))
|
||||
#this is to deal with a current bug where you cannot assign '<=>' to the comparator when modifying an existing alarm
|
||||
comparison = alarm.comparison
|
||||
comparisons = {'<=' : 'LessThanOrEqualToThreshold', '<' : 'LessThanThreshold', '>=' : 'GreaterThanOrEqualToThreshold', '>' : 'GreaterThanThreshold'}
|
||||
alarm.comparison = comparisons[comparison]
|
||||
|
||||
dim1 = module.params.get('dimensions')
|
||||
dim2 = alarm.dimensions
|
||||
|
||||
for keys in dim1:
|
||||
if not isinstance(dim1[keys], list):
|
||||
dim1[keys] = [dim1[keys]]
|
||||
if dim1[keys] != dim2[keys]:
|
||||
changed=True
|
||||
setattr(alarm, 'dimensions', dim1)
|
||||
|
||||
for attr in ('alarm_actions','insufficient_data_actions','ok_actions'):
|
||||
action = module.params.get(attr) or []
|
||||
if getattr(alarm, attr) != action:
|
||||
changed = True
|
||||
setattr(alarm, attr, module.params.get(attr))
|
||||
|
||||
try:
|
||||
if changed:
|
||||
connection.create_alarm(alarm)
|
||||
module.exit_json(changed=changed)
|
||||
except BotoServerError, e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
def delete_metric_alarm(connection, module):
|
||||
name = module.params.get('name')
|
||||
|
||||
alarms = connection.describe_alarms(alarm_names=[name])
|
||||
|
||||
if alarms:
|
||||
try:
|
||||
connection.delete_alarms([name])
|
||||
module.exit_json(changed=True)
|
||||
except BotoServerError, e:
|
||||
module.fail_json(msg=str(e))
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = ec2_argument_spec()
|
||||
argument_spec.update(
|
||||
dict(
|
||||
name=dict(required=True, type='str'),
|
||||
metric=dict(type='str'),
|
||||
namespace=dict(type='str', choices=['AWS/AutoScaling', 'AWS/Billing', 'AWS/DynamoDB', 'AWS/ElastiCache', 'AWS/EBS', 'AWS/EC2',
|
||||
'AWS/ELB', 'AWS/ElasticMapReduce', 'AWS/OpsWorks', 'AWS/Redshift', 'AWS/RDS', 'AWS/Route53', 'AWS/SNS', 'AWS/SQS', 'AWS/StorageGateway']), statistic=dict(type='str', choices=['SampleCount', 'Average', 'Sum', 'Minimum', 'Maximum']),
|
||||
comparison=dict(type='str', choices=['<=', '<', '>', '>=']),
|
||||
threshold=dict(type='float'),
|
||||
period=dict(type='int'),
|
||||
unit=dict(type='str', choices=['Seconds', 'Microseconds', 'Milliseconds', 'Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'Terabytes', 'Bits', 'Kilobits', 'Megabits', 'Gigabits', 'Terabits', 'Percent', 'Count', 'Bytes/Second', 'Kilobytes/Second', 'Megabytes/Second', 'Gigabytes/Second', 'Terabytes/Second', 'Bits/Second', 'Kilobits/Second', 'Megabits/Second', 'Gigabits/Second', 'Terabits/Second', 'Count/Second', 'None']),
|
||||
evaluation_periods=dict(type='int'),
|
||||
description=dict(type='str'),
|
||||
dimensions=dict(type='dict'),
|
||||
alarm_actions=dict(type='list'),
|
||||
insufficient_data_actions=dict(type='list'),
|
||||
ok_actions=dict(type='list'),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
region=dict(aliases=['aws_region', 'ec2_region'], choices=AWS_REGIONS),
|
||||
)
|
||||
)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec)
|
||||
|
||||
state = module.params.get('state')
|
||||
|
||||
region, ec2_url, aws_connect_params = get_aws_connection_info(module)
|
||||
try:
|
||||
connection = connect_to_aws(boto.ec2.cloudwatch, region, **aws_connect_params)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
if state == 'present':
|
||||
create_metric_alarm(connection, module)
|
||||
elif state == 'absent':
|
||||
delete_metric_alarm(connection, module)
|
||||
|
||||
main()
|
@ -0,0 +1,180 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
DOCUMENTATION = """
|
||||
module: ec2_scaling_policy
|
||||
short_description: Create or delete AWS scaling policies for Autoscaling groups
|
||||
description:
|
||||
- Can create or delete scaling policies for autoscaling groups
|
||||
- Referenced autoscaling groups must already exist
|
||||
version_added: "1.6"
|
||||
author: Zacharie Eakin
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- register or deregister the policy
|
||||
required: true
|
||||
choices: ['present', 'absent']
|
||||
name:
|
||||
description:
|
||||
- Unique name for the scaling policy
|
||||
required: true
|
||||
asg_name:
|
||||
description:
|
||||
- Name of the associated autoscaling group
|
||||
required: true
|
||||
adjustment_type:
|
||||
desciption:
|
||||
- The type of change in capacity of the autoscaling group
|
||||
required: false
|
||||
choices: ['ChangeInCapacity','ExactCapacity','PercentChangeInCapacity']
|
||||
scaling_adjustment:
|
||||
description:
|
||||
- The amount by which the autoscaling group is adjusted by the policy
|
||||
required: false
|
||||
min_adjustment_step:
|
||||
description:
|
||||
- Minimum amount of adjustment when policy is triggered
|
||||
required: false
|
||||
cooldown:
|
||||
description:
|
||||
- The minimum period of time between which autoscaling actions can take place
|
||||
required: false
|
||||
extends_documentation_fragment: aws
|
||||
"""
|
||||
|
||||
EXAMPLES = '''
|
||||
- ec2_scaling_policy:
|
||||
state: present
|
||||
region: US-XXX
|
||||
name: "scaledown-policy"
|
||||
adjustment_type: "ChangeInCapacity"
|
||||
asg_name: "slave-pool"
|
||||
scaling_adjustment: -1
|
||||
min_adjustment_step: 1
|
||||
cooldown: 300
|
||||
'''
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.ec2 import *
|
||||
|
||||
try:
|
||||
import boto.ec2.autoscale
|
||||
from boto.ec2.autoscale import ScalingPolicy
|
||||
from boto.exception import BotoServerError
|
||||
|
||||
except ImportError:
|
||||
print "failed=True msg='boto required for this module'"
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def create_scaling_policy(connection, module):
|
||||
sp_name = module.params.get('name')
|
||||
adjustment_type = module.params.get('adjustment_type')
|
||||
asg_name = module.params.get('asg_name')
|
||||
scaling_adjustment = module.params.get('scaling_adjustment')
|
||||
min_adjustment_step = module.params.get('min_adjustment_step')
|
||||
cooldown = module.params.get('cooldown')
|
||||
|
||||
scalingPolicies = connection.get_all_policies(as_group=asg_name,policy_names=[sp_name])
|
||||
|
||||
if not scalingPolicies:
|
||||
sp = ScalingPolicy(
|
||||
name=sp_name,
|
||||
adjustment_type=adjustment_type,
|
||||
as_name=asg_name,
|
||||
scaling_adjustment=scaling_adjustment,
|
||||
min_adjustment_step=min_adjustment_step,
|
||||
cooldown=cooldown)
|
||||
|
||||
try:
|
||||
connection.create_scaling_policy(sp)
|
||||
module.exit_json(changed=True)
|
||||
except BotoServerError, e:
|
||||
module.fail_json(msg=str(e))
|
||||
else:
|
||||
policy = scalingPolicies[0]
|
||||
changed = False
|
||||
|
||||
#min_adjustment_step attribute is only relevant if the adjustment_type
|
||||
#is set to percentage change in capacity, so it is a special case
|
||||
if getattr(policy, 'adjustment_type') == 'PercentChangeInCapacity':
|
||||
if getattr(policy, 'min_adjustment_step') != module.params.get('min_adjustment_step'):
|
||||
changed = True
|
||||
|
||||
#set the min adjustment step incase the user decided to change their adjustment type to percentage
|
||||
setattr(policy, 'min_adjustment_step', module.params.get('min_adjustment_step'))
|
||||
|
||||
#check the remaining attributes
|
||||
for attr in ('adjustment_type','scaling_adjustment','cooldown'):
|
||||
if getattr(policy, attr) != module.params.get(attr):
|
||||
changed = True
|
||||
setattr(policy, attr, module.params.get(attr))
|
||||
|
||||
try:
|
||||
if changed:
|
||||
connection.create_scaling_policy(policy)
|
||||
policy = connection.get_all_policies(policy_names=[sp_name])[0]
|
||||
module.exit_json(changed=changed, name=policy.name, arn=policy.policy_arn, as_name=policy.as_name, scaling_adjustment=policy.scaling_adjustment, cooldown=policy.cooldown, adjustment_type=policy.adjustment_type, min_adjustment_step=policy.min_adjustment_step)
|
||||
module.exit_json(changed=changed)
|
||||
except BotoServerError, e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
def delete_scaling_policy(connection, module):
|
||||
sp_name = module.params.get('name')
|
||||
asg_name = module.params.get('asg_name')
|
||||
|
||||
scalingPolicies = connection.get_all_policies(as_group=asg_name,policy_names=[sp_name])
|
||||
|
||||
if scalingPolicies:
|
||||
try:
|
||||
connection.delete_policy(sp_name, asg_name)
|
||||
module.exit_json(changed=True)
|
||||
except BotoServerError, e:
|
||||
module.exit_json(changed=False, msg=str(e))
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = ec2_argument_spec()
|
||||
argument_spec.update(
|
||||
dict(
|
||||
name = dict(required=True, type='str'),
|
||||
adjustment_type = dict(type='str', choices=['ChangeInCapacity','ExactCapacity','PercentChangeInCapacity']),
|
||||
asg_name = dict(required=True, type='str'),
|
||||
scaling_adjustment = dict(type='int'),
|
||||
min_adjustment_step = dict(type='int'),
|
||||
cooldown = dict(type='int'),
|
||||
region = dict(aliases=['aws_region', 'ec2_region'], choices=AWS_REGIONS),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
)
|
||||
)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec)
|
||||
|
||||
region, ec2_url, aws_connect_params = get_aws_connection_info(module)
|
||||
|
||||
state = module.params.get('state')
|
||||
|
||||
try:
|
||||
connection = connect_to_aws(boto.ec2.autoscale, region, **aws_connect_params)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg = str(e))
|
||||
|
||||
if state == 'present':
|
||||
create_scaling_policy(connection, module)
|
||||
elif state == 'absent':
|
||||
delete_scaling_policy(connection, module)
|
||||
|
||||
|
||||
main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,236 @@
|
||||
#!/usr/bin/python
|
||||
# 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/>.
|
||||
|
||||
# This is a DOCUMENTATION stub specific to this module, it extends
|
||||
# a documentation fragment located in ansible.utils.module_docs_fragments
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: rax_cbs
|
||||
short_description: Manipulate Rackspace Cloud Block Storage Volumes
|
||||
description:
|
||||
- Manipulate Rackspace Cloud Block Storage Volumes
|
||||
version_added: 1.6
|
||||
options:
|
||||
description:
|
||||
description:
|
||||
- Description to give the volume being created
|
||||
default: null
|
||||
meta:
|
||||
description:
|
||||
- A hash of metadata to associate with the volume
|
||||
default: null
|
||||
name:
|
||||
description:
|
||||
- Name to give the volume being created
|
||||
default: null
|
||||
required: true
|
||||
size:
|
||||
description:
|
||||
- Size of the volume to create in Gigabytes
|
||||
default: 100
|
||||
required: true
|
||||
snapshot_id:
|
||||
description:
|
||||
- The id of the snapshot to create the volume from
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- Indicate desired state of the resource
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
default: present
|
||||
required: true
|
||||
volume_type:
|
||||
description:
|
||||
- Type of the volume being created
|
||||
choices:
|
||||
- SATA
|
||||
- SSD
|
||||
default: SATA
|
||||
required: true
|
||||
wait:
|
||||
description:
|
||||
- wait for the volume to be in state 'available' before returning
|
||||
default: "no"
|
||||
choices:
|
||||
- "yes"
|
||||
- "no"
|
||||
wait_timeout:
|
||||
description:
|
||||
- how long before wait gives up, in seconds
|
||||
default: 300
|
||||
author: Christopher H. Laco, Matt Martz
|
||||
extends_documentation_fragment: rackspace.openstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Build a Block Storage Volume
|
||||
gather_facts: False
|
||||
hosts: local
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Storage volume create request
|
||||
local_action:
|
||||
module: rax_cbs
|
||||
credentials: ~/.raxpub
|
||||
name: my-volume
|
||||
description: My Volume
|
||||
volume_type: SSD
|
||||
size: 150
|
||||
region: DFW
|
||||
wait: yes
|
||||
state: present
|
||||
meta:
|
||||
app: my-cool-app
|
||||
register: my_volume
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
from uuid import UUID
|
||||
from types import NoneType
|
||||
|
||||
try:
|
||||
import pyrax
|
||||
HAS_PYRAX = True
|
||||
except ImportError:
|
||||
HAS_PYRAX = False
|
||||
|
||||
NON_CALLABLES = (basestring, bool, dict, int, list, NoneType)
|
||||
VOLUME_STATUS = ('available', 'attaching', 'creating', 'deleting', 'in-use',
|
||||
'error', 'error_deleting')
|
||||
|
||||
|
||||
def cloud_block_storage(module, state, name, description, meta, size,
|
||||
snapshot_id, volume_type, wait, wait_timeout):
|
||||
for arg in (state, name, size, volume_type):
|
||||
if not arg:
|
||||
module.fail_json(msg='%s is required for rax_cbs' % arg)
|
||||
|
||||
if size < 100:
|
||||
module.fail_json(msg='"size" must be greater than or equal to 100')
|
||||
|
||||
changed = False
|
||||
volume = None
|
||||
instance = {}
|
||||
|
||||
cbs = pyrax.cloud_blockstorage
|
||||
|
||||
if cbs is None:
|
||||
module.fail_json(msg='Failed to instantiate client. This '
|
||||
'typically indicates an invalid region or an '
|
||||
'incorrectly capitalized region name.')
|
||||
|
||||
try:
|
||||
UUID(name)
|
||||
volume = cbs.get(name)
|
||||
except ValueError:
|
||||
try:
|
||||
volume = cbs.find(name=name)
|
||||
except Exception, e:
|
||||
module.fail_json(msg='%s' % e)
|
||||
|
||||
if state == 'present':
|
||||
if not volume:
|
||||
try:
|
||||
volume = cbs.create(name, size=size, volume_type=volume_type,
|
||||
description=description,
|
||||
metadata=meta,
|
||||
snapshot_id=snapshot_id)
|
||||
changed = True
|
||||
except Exception, e:
|
||||
module.fail_json(msg='%s' % e.message)
|
||||
else:
|
||||
if wait:
|
||||
attempts = wait_timeout / 5
|
||||
pyrax.utils.wait_for_build(volume, interval=5,
|
||||
attempts=attempts)
|
||||
|
||||
volume.get()
|
||||
for key, value in vars(volume).iteritems():
|
||||
if (isinstance(value, NON_CALLABLES) and
|
||||
not key.startswith('_')):
|
||||
instance[key] = value
|
||||
|
||||
result = dict(changed=changed, volume=instance)
|
||||
|
||||
if volume.status == 'error':
|
||||
result['msg'] = '%s failed to build' % volume.id
|
||||
elif wait and volume.status not in VOLUME_STATUS:
|
||||
result['msg'] = 'Timeout waiting on %s' % volume.id
|
||||
|
||||
if 'msg' in result:
|
||||
module.fail_json(**result)
|
||||
else:
|
||||
module.exit_json(**result)
|
||||
|
||||
elif state == 'absent':
|
||||
if volume:
|
||||
try:
|
||||
volume.delete()
|
||||
changed = True
|
||||
except Exception, e:
|
||||
module.fail_json(msg='%s' % e.message)
|
||||
|
||||
module.exit_json(changed=changed, volume=instance)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = rax_argument_spec()
|
||||
argument_spec.update(
|
||||
dict(
|
||||
description=dict(),
|
||||
meta=dict(type='dict', default={}),
|
||||
name=dict(required=True),
|
||||
size=dict(type='int', default=100),
|
||||
snapshot_id=dict(),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
volume_type=dict(choices=['SSD', 'SATA'], default='SATA'),
|
||||
wait=dict(type='bool', default=False),
|
||||
wait_timeout=dict(type='int', default=300)
|
||||
)
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=rax_required_together()
|
||||
)
|
||||
|
||||
if not HAS_PYRAX:
|
||||
module.fail_json(msg='pyrax is required for this module')
|
||||
|
||||
description = module.params.get('description')
|
||||
meta = module.params.get('meta')
|
||||
name = module.params.get('name')
|
||||
size = module.params.get('size')
|
||||
snapshot_id = module.params.get('snapshot_id')
|
||||
state = module.params.get('state')
|
||||
volume_type = module.params.get('volume_type')
|
||||
wait = module.params.get('wait')
|
||||
wait_timeout = module.params.get('wait_timeout')
|
||||
|
||||
setup_rax_module(module, pyrax)
|
||||
|
||||
cloud_block_storage(module, state, name, description, meta, size,
|
||||
snapshot_id, volume_type, wait, wait_timeout)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.rax import *
|
||||
|
||||
### invoke the module
|
||||
main()
|
@ -0,0 +1,268 @@
|
||||
#!/usr/bin/python
|
||||
# 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/>.
|
||||
|
||||
# This is a DOCUMENTATION stub specific to this module, it extends
|
||||
# a documentation fragment located in ansible.utils.module_docs_fragments
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: rax_cbs_attachments
|
||||
short_description: Manipulate Rackspace Cloud Block Storage Volume Attachments
|
||||
description:
|
||||
- Manipulate Rackspace Cloud Block Storage Volume Attachments
|
||||
version_added: 1.6
|
||||
options:
|
||||
device:
|
||||
description:
|
||||
- The device path to attach the volume to, e.g. /dev/xvde
|
||||
default: null
|
||||
required: true
|
||||
volume:
|
||||
description:
|
||||
- Name or id of the volume to attach/detach
|
||||
default: null
|
||||
required: true
|
||||
server:
|
||||
description:
|
||||
- Name or id of the server to attach/detach
|
||||
default: null
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- Indicate desired state of the resource
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
default: present
|
||||
required: true
|
||||
wait:
|
||||
description:
|
||||
- wait for the volume to be in 'in-use'/'available' state before returning
|
||||
default: "no"
|
||||
choices:
|
||||
- "yes"
|
||||
- "no"
|
||||
wait_timeout:
|
||||
description:
|
||||
- how long before wait gives up, in seconds
|
||||
default: 300
|
||||
author: Christopher H. Laco, Matt Martz
|
||||
extends_documentation_fragment: rackspace.openstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Attach a Block Storage Volume
|
||||
gather_facts: False
|
||||
hosts: local
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Storage volume attach request
|
||||
local_action:
|
||||
module: rax_cbs_attachments
|
||||
credentials: ~/.raxpub
|
||||
volume: my-volume
|
||||
server: my-server
|
||||
device: /dev/xvdd
|
||||
region: DFW
|
||||
wait: yes
|
||||
state: present
|
||||
register: my_volume
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
from uuid import UUID
|
||||
from types import NoneType
|
||||
|
||||
try:
|
||||
import pyrax
|
||||
HAS_PYRAX = True
|
||||
except ImportError:
|
||||
HAS_PYRAX = False
|
||||
|
||||
NON_CALLABLES = (basestring, bool, dict, int, list, NoneType)
|
||||
|
||||
|
||||
def cloud_block_storage_attachments(module, state, volume, server, device,
|
||||
wait, wait_timeout):
|
||||
for arg in (state, volume, server, device):
|
||||
if not arg:
|
||||
module.fail_json(msg='%s is required for rax_cbs_attachments' %
|
||||
arg)
|
||||
|
||||
cbs = pyrax.cloud_blockstorage
|
||||
cs = pyrax.cloudservers
|
||||
|
||||
if cbs is None or cs is None:
|
||||
module.fail_json(msg='Failed to instantiate client. This '
|
||||
'typically indicates an invalid region or an '
|
||||
'incorrectly capitalized region name.')
|
||||
|
||||
changed = False
|
||||
instance = {}
|
||||
|
||||
try:
|
||||
UUID(volume)
|
||||
volume = cbs.get(volume)
|
||||
except ValueError:
|
||||
try:
|
||||
volume = cbs.find(name=volume)
|
||||
except Exception, e:
|
||||
module.fail_json(msg='%s' % e)
|
||||
|
||||
if not volume:
|
||||
module.fail_json(msg='No matching storage volumes were found')
|
||||
|
||||
if state == 'present':
|
||||
try:
|
||||
UUID(server)
|
||||
server = cs.servers.get(server)
|
||||
except ValueError:
|
||||
servers = cs.servers.list(search_opts=dict(name='^%s$' % server))
|
||||
if not servers:
|
||||
module.fail_json(msg='No Server was matched by name, '
|
||||
'try using the Server ID instead')
|
||||
if len(servers) > 1:
|
||||
module.fail_json(msg='Multiple servers matched by name, '
|
||||
'try using the Server ID instead')
|
||||
|
||||
# We made it this far, grab the first and hopefully only server
|
||||
# in the list
|
||||
server = servers[0]
|
||||
|
||||
if (volume.attachments and
|
||||
volume.attachments[0]['server_id'] == server.id):
|
||||
changed = False
|
||||
elif volume.attachments:
|
||||
module.fail_json(msg='Volume is attached to another server')
|
||||
else:
|
||||
try:
|
||||
volume.attach_to_instance(server, mountpoint=device)
|
||||
changed = True
|
||||
except Exception, e:
|
||||
module.fail_json(msg='%s' % e.message)
|
||||
|
||||
volume.get()
|
||||
|
||||
for key, value in vars(volume).iteritems():
|
||||
if (isinstance(value, NON_CALLABLES) and
|
||||
not key.startswith('_')):
|
||||
instance[key] = value
|
||||
|
||||
result = dict(changed=changed, volume=instance)
|
||||
|
||||
if volume.status == 'error':
|
||||
result['msg'] = '%s failed to build' % volume.id
|
||||
elif wait:
|
||||
attempts = wait_timeout / 5
|
||||
pyrax.utils.wait_until(volume, 'status', 'in-use',
|
||||
interval=5, attempts=attempts)
|
||||
|
||||
if 'msg' in result:
|
||||
module.fail_json(**result)
|
||||
else:
|
||||
module.exit_json(**result)
|
||||
|
||||
elif state == 'absent':
|
||||
try:
|
||||
UUID(server)
|
||||
server = cs.servers.get(server)
|
||||
except ValueError:
|
||||
servers = cs.servers.list(search_opts=dict(name='^%s$' % server))
|
||||
if not servers:
|
||||
module.fail_json(msg='No Server was matched by name, '
|
||||
'try using the Server ID instead')
|
||||
if len(servers) > 1:
|
||||
module.fail_json(msg='Multiple servers matched by name, '
|
||||
'try using the Server ID instead')
|
||||
|
||||
# We made it this far, grab the first and hopefully only server
|
||||
# in the list
|
||||
server = servers[0]
|
||||
|
||||
if (volume.attachments and
|
||||
volume.attachments[0]['server_id'] == server.id):
|
||||
try:
|
||||
volume.detach()
|
||||
if wait:
|
||||
pyrax.utils.wait_until(volume, 'status', 'available',
|
||||
interval=3, attempts=0,
|
||||
verbose=False)
|
||||
changed = True
|
||||
except Exception, e:
|
||||
module.fail_json(msg='%s' % e.message)
|
||||
|
||||
volume.get()
|
||||
changed = True
|
||||
elif volume.attachments:
|
||||
module.fail_json(msg='Volume is attached to another server')
|
||||
|
||||
for key, value in vars(volume).iteritems():
|
||||
if (isinstance(value, NON_CALLABLES) and
|
||||
not key.startswith('_')):
|
||||
instance[key] = value
|
||||
|
||||
result = dict(changed=changed, volume=instance)
|
||||
|
||||
if volume.status == 'error':
|
||||
result['msg'] = '%s failed to build' % volume.id
|
||||
|
||||
if 'msg' in result:
|
||||
module.fail_json(**result)
|
||||
else:
|
||||
module.exit_json(**result)
|
||||
|
||||
module.exit_json(changed=changed, volume=instance)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = rax_argument_spec()
|
||||
argument_spec.update(
|
||||
dict(
|
||||
device=dict(required=True),
|
||||
volume=dict(required=True),
|
||||
server=dict(required=True),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
wait=dict(type='bool', default=False),
|
||||
wait_timeout=dict(type='int', default=300)
|
||||
)
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=rax_required_together()
|
||||
)
|
||||
|
||||
if not HAS_PYRAX:
|
||||
module.fail_json(msg='pyrax is required for this module')
|
||||
|
||||
device = module.params.get('device')
|
||||
volume = module.params.get('volume')
|
||||
server = module.params.get('server')
|
||||
state = module.params.get('state')
|
||||
wait = module.params.get('wait')
|
||||
wait_timeout = module.params.get('wait_timeout')
|
||||
|
||||
setup_rax_module(module, pyrax)
|
||||
|
||||
cloud_block_storage_attachments(module, state, volume, server, device,
|
||||
wait, wait_timeout)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.rax import *
|
||||
|
||||
### invoke the module
|
||||
main()
|
@ -0,0 +1,117 @@
|
||||
#!/usr/bin/python
|
||||
# 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/>.
|
||||
|
||||
# This is a DOCUMENTATION stub specific to this module, it extends
|
||||
# a documentation fragment located in ansible.utils.module_docs_fragments
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: rax_identity
|
||||
short_description: Load Rackspace Cloud Identity
|
||||
description:
|
||||
- Verifies Rackspace Cloud credentials and returns identity information
|
||||
version_added: "1.5"
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Indicate desired state of the resource
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
author: Christopher H. Laco, Matt Martz
|
||||
extends_documentation_fragment: rackspace.openstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Load Rackspace Cloud Identity
|
||||
gather_facts: False
|
||||
hosts: local
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Load Identity
|
||||
local_action:
|
||||
module: rax_identity
|
||||
credentials: ~/.raxpub
|
||||
region: DFW
|
||||
register: rackspace_identity
|
||||
'''
|
||||
|
||||
from types import NoneType
|
||||
|
||||
try:
|
||||
import pyrax
|
||||
HAS_PYRAX = True
|
||||
except ImportError:
|
||||
HAS_PYRAX = False
|
||||
|
||||
|
||||
NON_CALLABLES = (basestring, bool, dict, int, list, NoneType)
|
||||
|
||||
|
||||
def cloud_identity(module, state, identity):
|
||||
for arg in (state, identity):
|
||||
if not arg:
|
||||
module.fail_json(msg='%s is required for rax_identity' % arg)
|
||||
|
||||
instance = dict(
|
||||
authenticated=identity.authenticated,
|
||||
credentials=identity._creds_file
|
||||
)
|
||||
changed = False
|
||||
|
||||
for key, value in vars(identity).iteritems():
|
||||
if (isinstance(value, NON_CALLABLES) and
|
||||
not key.startswith('_')):
|
||||
instance[key] = value
|
||||
|
||||
if state == 'present':
|
||||
if not identity.authenticated:
|
||||
module.fail_json(msg='Credentials could not be verified!')
|
||||
|
||||
module.exit_json(changed=changed, identity=instance)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = rax_argument_spec()
|
||||
argument_spec.update(
|
||||
dict(
|
||||
state=dict(default='present', choices=['present', 'absent'])
|
||||
)
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=rax_required_together()
|
||||
)
|
||||
|
||||
if not HAS_PYRAX:
|
||||
module.fail_json(msg='pyrax is required for this module')
|
||||
|
||||
state = module.params.get('state')
|
||||
|
||||
setup_rax_module(module, pyrax)
|
||||
|
||||
if pyrax.identity is None:
|
||||
module.fail_json(msg='Failed to instantiate client. This '
|
||||
'typically indicates an invalid region or an '
|
||||
'incorrectly capitalized region name.')
|
||||
|
||||
cloud_identity(module, state, pyrax.identity)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.rax import *
|
||||
|
||||
### invoke the module
|
||||
main()
|
@ -0,0 +1,160 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Evan Kaufman <evan@digitalflophouse.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/>.
|
||||
|
||||
import re
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: replace
|
||||
author: Evan Kaufman
|
||||
short_description: Replace all instances of a particular string in a
|
||||
file using a back-referenced regular expression.
|
||||
description:
|
||||
- This module will replace all instances of a pattern within a file.
|
||||
- It is up to the user to maintain idempotence by ensuring that the
|
||||
same pattern would never match any replacements made.
|
||||
version_added: "1.6"
|
||||
options:
|
||||
dest:
|
||||
required: true
|
||||
aliases: [ name, destfile ]
|
||||
description:
|
||||
- The file to modify.
|
||||
regexp:
|
||||
required: true
|
||||
description:
|
||||
- The regular expression to look for in the contents of the file.
|
||||
Uses Python regular expressions; see
|
||||
U(http://docs.python.org/2/library/re.html).
|
||||
Uses multiline mode, which means C(^) and C($) match the beginning
|
||||
and end respectively of I(each line) of the file.
|
||||
replace:
|
||||
required: false
|
||||
description:
|
||||
- The string to replace regexp matches. May contain backreferences
|
||||
that will get expanded with the regexp capture groups if the regexp
|
||||
matches. If not set, matches are removed entirely.
|
||||
backup:
|
||||
required: false
|
||||
default: "no"
|
||||
choices: [ "yes", "no" ]
|
||||
description:
|
||||
- Create a backup file including the timestamp information so you can
|
||||
get the original file back if you somehow clobbered it incorrectly.
|
||||
validate:
|
||||
required: false
|
||||
description:
|
||||
- validation to run before copying into place
|
||||
required: false
|
||||
default: None
|
||||
others:
|
||||
description:
|
||||
- All arguments accepted by the M(file) module also work here.
|
||||
required: false
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- replace: dest=/etc/hosts regexp='(\s+)old\.host\.name(\s+.*)?$' replace='\1new.host.name\2' backup=yes
|
||||
|
||||
- replace: dest=/home/jdoe/.ssh/known_hosts regexp='^old\.host\.name[^\n]*\n' owner=jdoe group=jdoe mode=644
|
||||
|
||||
- replace: dest=/etc/apache/ports regexp='^(NameVirtualHost|Listen)\s+80\s*$' replace='\1 127.0.0.1:8080' validate='/usr/sbin/apache2ctl -f %s -t'
|
||||
"""
|
||||
|
||||
def write_changes(module,contents,dest):
|
||||
|
||||
tmpfd, tmpfile = tempfile.mkstemp()
|
||||
f = os.fdopen(tmpfd,'wb')
|
||||
f.write(contents)
|
||||
f.close()
|
||||
|
||||
validate = module.params.get('validate', None)
|
||||
valid = not validate
|
||||
if validate:
|
||||
(rc, out, err) = module.run_command(validate % tmpfile)
|
||||
valid = rc == 0
|
||||
if rc != 0:
|
||||
module.fail_json(msg='failed to validate: '
|
||||
'rc:%s error:%s' % (rc,err))
|
||||
if valid:
|
||||
module.atomic_move(tmpfile, dest)
|
||||
|
||||
def check_file_attrs(module, changed, message):
|
||||
|
||||
file_args = module.load_file_common_arguments(module.params)
|
||||
if module.set_file_attributes_if_different(file_args, False):
|
||||
|
||||
if changed:
|
||||
message += " and "
|
||||
changed = True
|
||||
message += "ownership, perms or SE linux context changed"
|
||||
|
||||
return message, changed
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
dest=dict(required=True, aliases=['name', 'destfile']),
|
||||
regexp=dict(required=True),
|
||||
replace=dict(default='', type='str'),
|
||||
backup=dict(default=False, type='bool'),
|
||||
validate=dict(default=None, type='str'),
|
||||
),
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
params = module.params
|
||||
dest = os.path.expanduser(params['dest'])
|
||||
|
||||
if os.path.isdir(dest):
|
||||
module.fail_json(rc=256, msg='Destination %s is a directory !' % dest)
|
||||
|
||||
if not os.path.exists(dest):
|
||||
module.fail_json(rc=257, msg='Destination %s does not exist !' % dest)
|
||||
else:
|
||||
f = open(dest, 'rb')
|
||||
contents = f.read()
|
||||
f.close()
|
||||
|
||||
mre = re.compile(params['regexp'], re.MULTILINE)
|
||||
result = re.subn(mre, params['replace'], contents, 0)
|
||||
|
||||
if result[1] > 0:
|
||||
msg = '%s replacements made' % result[1]
|
||||
changed = True
|
||||
else:
|
||||
msg = ''
|
||||
changed = False
|
||||
|
||||
if changed and not module.check_mode:
|
||||
if params['backup'] and os.path.exists(dest):
|
||||
module.backup_local(dest)
|
||||
write_changes(module, result[0], dest)
|
||||
|
||||
msg, changed = check_file_attrs(module, changed, msg)
|
||||
module.exit_json(changed=changed, msg=msg)
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
|
||||
main()
|
@ -0,0 +1,169 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (C) Seth Edwards, 2014
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
|
||||
import base64
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: librato_annotation
|
||||
short_description: create an annotation in librato
|
||||
description:
|
||||
- Create an annotation event on the given annotation stream :name. If the annotation stream does not exist, it will be created automatically
|
||||
version_added: "1.6"
|
||||
author: Seth Edwards
|
||||
requirements:
|
||||
- urllib2
|
||||
- base64
|
||||
options:
|
||||
user:
|
||||
description:
|
||||
- Librato account username
|
||||
required: true
|
||||
api_key:
|
||||
description:
|
||||
- Librato account api key
|
||||
required: true
|
||||
name:
|
||||
description:
|
||||
- The annotation stream name
|
||||
- If the annotation stream does not exist, it will be created automatically
|
||||
required: false
|
||||
title:
|
||||
description:
|
||||
- The title of an annotation is a string and may contain spaces
|
||||
- The title should be a short, high-level summary of the annotation e.g. v45 Deployment
|
||||
required: true
|
||||
source:
|
||||
description:
|
||||
- A string which describes the originating source of an annotation when that annotation is tracked across multiple members of a population
|
||||
required: false
|
||||
description:
|
||||
description:
|
||||
- The description contains extra meta-data about a particular annotation
|
||||
- The description should contain specifics on the individual annotation e.g. Deployed 9b562b2 shipped new feature foo!
|
||||
required: false
|
||||
start_time:
|
||||
description:
|
||||
- The unix timestamp indicating the the time at which the event referenced by this annotation started
|
||||
required: false
|
||||
end_time:
|
||||
description:
|
||||
- The unix timestamp indicating the the time at which the event referenced by this annotation ended
|
||||
- For events that have a duration, this is a useful way to annotate the duration of the event
|
||||
required: false
|
||||
links:
|
||||
description:
|
||||
- See examples
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a simple annotation event with a source
|
||||
- librato_annotation:
|
||||
user: user@example.com
|
||||
api_key: XXXXXXXXXXXXXXXXX
|
||||
title: 'App Config Change'
|
||||
source: 'foo.bar'
|
||||
description: 'This is a detailed description of the config change'
|
||||
|
||||
# Create an annotation that includes a link
|
||||
- librato_annotation:
|
||||
user: user@example.com
|
||||
api_key: XXXXXXXXXXXXXXXXXX
|
||||
name: 'code.deploy'
|
||||
title: 'app code deploy'
|
||||
description: 'this is a detailed description of a deployment'
|
||||
links:
|
||||
- { rel: 'example', href: 'http://www.example.com/deploy' }
|
||||
|
||||
# Create an annotation with a start_time and end_time
|
||||
- librato_annotation:
|
||||
user: user@example.com
|
||||
api_key: XXXXXXXXXXXXXXXXXX
|
||||
name: 'maintenance'
|
||||
title: 'Maintenance window'
|
||||
description: 'This is a detailed description of maintenance'
|
||||
start_time: 1395940006
|
||||
end_time: 1395954406
|
||||
'''
|
||||
|
||||
|
||||
try:
|
||||
import urllib2
|
||||
HAS_URLLIB2 = True
|
||||
except ImportError:
|
||||
HAS_URLLIB2 = False
|
||||
|
||||
def post_annotation(module):
|
||||
user = module.params['user']
|
||||
api_key = module.params['api_key']
|
||||
name = module.params['name']
|
||||
title = module.params['title']
|
||||
|
||||
url = 'https://metrics-api.librato.com/v1/annotations/%s' % name
|
||||
params = {}
|
||||
params['title'] = title
|
||||
|
||||
if module.params['source'] != None:
|
||||
params['source'] = module.params['source']
|
||||
if module.params['description'] != None:
|
||||
params['description'] = module.params['description']
|
||||
if module.params['start_time'] != None:
|
||||
params['start_time'] = module.params['start_time']
|
||||
if module.params['end_time'] != None:
|
||||
params['end_time'] = module.params['end_time']
|
||||
if module.params['links'] != None:
|
||||
params['links'] = module.params['links']
|
||||
|
||||
json_body = module.jsonify(params)
|
||||
|
||||
headers = {}
|
||||
headers['Content-Type'] = 'application/json'
|
||||
headers['Authorization'] = b"Basic " + base64.b64encode(user + b":" + api_key).strip()
|
||||
req = urllib2.Request(url, json_body, headers)
|
||||
try:
|
||||
response = urllib2.urlopen(req)
|
||||
except urllib2.HTTPError as e:
|
||||
module.fail_json(msg="Request Failed", reason=e.reason)
|
||||
response = response.read()
|
||||
module.exit_json(changed=True, annotation=response)
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
user = dict(required=True),
|
||||
api_key = dict(required=True),
|
||||
name = dict(required=False),
|
||||
title = dict(required=True),
|
||||
source = dict(required=False),
|
||||
description = dict(required=False),
|
||||
start_time = dict(required=False, default=None, type='int'),
|
||||
end_time = dict(require=False, default=None, type='int'),
|
||||
links = dict(type='list')
|
||||
)
|
||||
)
|
||||
|
||||
post_annotation(module)
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
@ -0,0 +1,130 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Ivan Vanderbyl <ivan@app.io>
|
||||
#
|
||||
# This module 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.
|
||||
#
|
||||
# This software 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 this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: logentries
|
||||
author: Ivan Vanderbyl
|
||||
short_description: Module for tracking logs via logentries.com
|
||||
description:
|
||||
- Sends logs to LogEntries in realtime
|
||||
version_added: "1.6"
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- path to a log file
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- following state of the log
|
||||
choices: [ 'present', 'absent' ]
|
||||
required: false
|
||||
default: present
|
||||
notes:
|
||||
- Requires the LogEntries agent which can be installed following the instructions at logentries.com
|
||||
'''
|
||||
EXAMPLES = '''
|
||||
- logentries: path=/var/log/nginx/access.log state=present
|
||||
- logentries: path=/var/log/nginx/error.log state=absent
|
||||
'''
|
||||
|
||||
def query_log_status(module, le_path, path, state="present"):
|
||||
""" Returns whether a log is followed or not. """
|
||||
|
||||
if state == "present":
|
||||
rc, out, err = module.run_command("%s followed %s" % (le_path, path))
|
||||
if rc == 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def follow_log(module, le_path, logs):
|
||||
""" Follows one or more logs if not already followed. """
|
||||
|
||||
followed_count = 0
|
||||
|
||||
for log in logs:
|
||||
if query_log_status(module, le_path, log):
|
||||
continue
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
rc, out, err = module.run_command([le_path, 'follow', log])
|
||||
|
||||
if not query_log_status(module, le_path, log):
|
||||
module.fail_json(msg="failed to follow '%s': %s" % (log, err.strip()))
|
||||
|
||||
followed_count += 1
|
||||
|
||||
if followed_count > 0:
|
||||
module.exit_json(changed=True, msg="followed %d log(s)" % (followed_count,))
|
||||
|
||||
module.exit_json(changed=False, msg="logs(s) already followed")
|
||||
|
||||
def unfollow_log(module, le_path, logs):
|
||||
""" Unfollows one or more logs if followed. """
|
||||
|
||||
removed_count = 0
|
||||
|
||||
# Using a for loop incase of error, we can report the package that failed
|
||||
for log in logs:
|
||||
# Query the log first, to see if we even need to remove.
|
||||
if not query_log_status(module, le_path, log):
|
||||
continue
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
rc, out, err = module.run_command([le_path, 'rm', log])
|
||||
|
||||
if query_log_status(module, le_path, log):
|
||||
module.fail_json(msg="failed to remove '%s': %s" % (log, err.strip()))
|
||||
|
||||
removed_count += 1
|
||||
|
||||
if removed_count > 0:
|
||||
module.exit_json(changed=True, msg="removed %d package(s)" % removed_count)
|
||||
|
||||
module.exit_json(changed=False, msg="logs(s) already unfollowed")
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
path = dict(aliases=["name"], required=True),
|
||||
state = dict(default="present", choices=["present", "followed", "absent", "unfollowed"])
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
le_path = module.get_bin_path('le', True, ['/usr/local/bin'])
|
||||
|
||||
p = module.params
|
||||
|
||||
# Handle multiple log files
|
||||
logs = p["path"].split(",")
|
||||
logs = filter(None, logs)
|
||||
|
||||
if p["state"] in ["present", "followed"]:
|
||||
follow_log(module, le_path, logs)
|
||||
|
||||
elif p["state"] in ["absent", "unfollowed"]:
|
||||
unfollow_log(module, le_path, logs)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
@ -0,0 +1,133 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014, Max Riveiro, <kavu13@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: rollbar_deployment
|
||||
version_added: 1.6
|
||||
author: Max Riveiro
|
||||
short_description: Notify Rollbar about app deployments
|
||||
description:
|
||||
- Notify Rollbar about app deployments
|
||||
(see https://rollbar.com/docs/deploys_other/)
|
||||
options:
|
||||
token:
|
||||
description:
|
||||
- Your project access token.
|
||||
required: true
|
||||
environment:
|
||||
description:
|
||||
- Name of the environment being deployed, e.g. 'production'.
|
||||
required: true
|
||||
revision:
|
||||
description:
|
||||
- Revision number/sha being deployed.
|
||||
required: true
|
||||
user:
|
||||
description:
|
||||
- User who deployed.
|
||||
required: false
|
||||
rollbar_user:
|
||||
description:
|
||||
- Rollbar username of the user who deployed.
|
||||
required: false
|
||||
comment:
|
||||
description:
|
||||
- Deploy comment (e.g. what is being deployed).
|
||||
required: false
|
||||
url:
|
||||
description:
|
||||
- Optional URL to submit the notification to.
|
||||
required: false
|
||||
default: 'https://api.rollbar.com/api/1/deploy/'
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates for the target url will not be validated.
|
||||
This should only be used on personally controlled sites using
|
||||
self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices: ['yes', 'no']
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- rollbar_deployment: token=AAAAAA
|
||||
environment='staging'
|
||||
user='ansible'
|
||||
revision=4.2,
|
||||
rollbar_user='admin',
|
||||
comment='Test Deploy'
|
||||
'''
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
token=dict(required=True),
|
||||
environment=dict(required=True),
|
||||
revision=dict(required=True),
|
||||
user=dict(required=False),
|
||||
rollbar_user=dict(required=False),
|
||||
comment=dict(required=False),
|
||||
url=dict(
|
||||
required=False,
|
||||
default='https://api.rollbar.com/api/1/deploy/'
|
||||
),
|
||||
validate_certs=dict(default='yes', type='bool'),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
params = dict(
|
||||
access_token=module.params['token'],
|
||||
environment=module.params['environment'],
|
||||
revision=module.params['revision']
|
||||
)
|
||||
|
||||
if module.params['user']:
|
||||
params['local_username'] = module.params['user']
|
||||
|
||||
if module.params['rollbar_user']:
|
||||
params['rollbar_username'] = module.params['rollbar_user']
|
||||
|
||||
if module.params['comment']:
|
||||
params['comment'] = module.params['comment']
|
||||
|
||||
url = module.params.get('url')
|
||||
|
||||
try:
|
||||
data = urllib.urlencode(params)
|
||||
response, info = fetch_url(module, url, data=data)
|
||||
except Exception, e:
|
||||
module.fail_json(msg='Unable to notify Rollbar: %s' % e)
|
||||
else:
|
||||
if info['status'] == 200:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.fail_json(msg='HTTP result code: %d connecting to %s' % (info['status'], url))
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
|
||||
main()
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,302 @@
|
||||
#!/usr/bin/python
|
||||
# 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: dnsimple
|
||||
version_added: "1.6"
|
||||
short_description: Interface with dnsimple.com (a DNS hosting service).
|
||||
description:
|
||||
- "Manages domains and records via the DNSimple API, see the docs: U(http://developer.dnsimple.com/)"
|
||||
options:
|
||||
account_email:
|
||||
description:
|
||||
- "Account email. If ommitted, the env variables DNSIMPLE_EMAIL and DNSIMPLE_API_TOKEN will be looked for. If those aren't found, a C(.dnsimple) file will be looked for, see: U(https://github.com/mikemaccana/dnsimple-python#getting-started)"
|
||||
required: false
|
||||
default: null
|
||||
|
||||
account_api_token:
|
||||
description:
|
||||
- Account API token. See I(account_email) for info.
|
||||
required: false
|
||||
default: null
|
||||
|
||||
domain:
|
||||
description:
|
||||
- Domain to work with. Can be the domain name (e.g. "mydomain.com") or the numeric ID of the domain in DNSimple. If ommitted, a list of domains will be returned.
|
||||
- If domain is present but the domain doesn't exist, it will be created.
|
||||
required: false
|
||||
default: null
|
||||
|
||||
record:
|
||||
description:
|
||||
- Record to add, if blank a record for the domain will be created, supports the wildcard (*)
|
||||
required: false
|
||||
default: null
|
||||
|
||||
record_ids:
|
||||
description:
|
||||
- List of records to ensure they either exist or don't exist
|
||||
required: false
|
||||
default: null
|
||||
|
||||
type:
|
||||
description:
|
||||
- The type of DNS record to create
|
||||
required: false
|
||||
choices: [ 'A', 'ALIAS', 'CNAME', 'MX', 'SPF', 'URL', 'TXT', 'NS', 'SRV', 'NAPTR', 'PTR', 'AAAA', 'SSHFP', 'HINFO', 'POOL' ]
|
||||
default: null
|
||||
|
||||
ttl:
|
||||
description:
|
||||
- The TTL to give the new record
|
||||
required: false
|
||||
default: 3600 (one hour)
|
||||
|
||||
value:
|
||||
description:
|
||||
- Record value
|
||||
- "Must be specified when trying to ensure a record exists"
|
||||
required: false
|
||||
default: null
|
||||
|
||||
priority:
|
||||
description:
|
||||
- Record priority
|
||||
required: false
|
||||
default: null
|
||||
|
||||
state:
|
||||
description:
|
||||
- whether the record should exist or not
|
||||
required: false
|
||||
choices: [ 'present', 'absent' ]
|
||||
default: null
|
||||
|
||||
solo:
|
||||
description:
|
||||
- Whether the record should be the only one for that record type and record name. Only use with state=present on a record
|
||||
required: false
|
||||
default: null
|
||||
|
||||
requirements: [ dnsimple ]
|
||||
author: Alex Coomans
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# authenicate using email and API token
|
||||
- local_action: dnsimple account_email=test@example.com account_api_token=dummyapitoken
|
||||
|
||||
# fetch all domains
|
||||
- local_action dnsimple
|
||||
register: domains
|
||||
|
||||
# fetch my.com domain records
|
||||
- local_action: dnsimple domain=my.com state=present
|
||||
register: records
|
||||
|
||||
# delete a domain
|
||||
- local_action: dnsimple domain=my.com state=absent
|
||||
|
||||
# create a test.my.com A record to point to 127.0.0.01
|
||||
- local_action: dnsimple domain=my.com record=test type=A value=127.0.0.1
|
||||
register: record
|
||||
|
||||
# and then delete it
|
||||
- local_action: dnsimple domain=my.com record_ids={{ record['id'] }}
|
||||
|
||||
# create a my.com CNAME record to example.com
|
||||
- local_action: dnsimple domain=my.com record= type=CNAME value=example.com state=present
|
||||
|
||||
# change it's ttl
|
||||
- local_action: dnsimple domain=my.com record= type=CNAME value=example.com ttl=600 state=present
|
||||
|
||||
# and delete the record
|
||||
- local_action: dnsimpledomain=my.com record= type=CNAME value=example.com state=absent
|
||||
|
||||
'''
|
||||
|
||||
import os
|
||||
try:
|
||||
from dnsimple import DNSimple
|
||||
from dnsimple.dnsimple import DNSimpleException
|
||||
except ImportError:
|
||||
print "failed=True msg='dnsimple required for this module'"
|
||||
sys.exit(1)
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
account_email = dict(required=False),
|
||||
account_api_token = dict(required=False, no_log=True),
|
||||
domain = dict(required=False),
|
||||
record = dict(required=False),
|
||||
record_ids = dict(required=False, type='list'),
|
||||
type = dict(required=False, choices=['A', 'ALIAS', 'CNAME', 'MX', 'SPF', 'URL', 'TXT', 'NS', 'SRV', 'NAPTR', 'PTR', 'AAAA', 'SSHFP', 'HINFO', 'POOL']),
|
||||
ttl = dict(required=False, default=3600, type='int'),
|
||||
value = dict(required=False),
|
||||
priority = dict(required=False, type='int'),
|
||||
state = dict(required=False, choices=['present', 'absent']),
|
||||
solo = dict(required=False, type='bool'),
|
||||
),
|
||||
required_together = (
|
||||
['record', 'value']
|
||||
),
|
||||
supports_check_mode = True,
|
||||
)
|
||||
|
||||
account_email = module.params.get('account_email')
|
||||
account_api_token = module.params.get('account_api_token')
|
||||
domain = module.params.get('domain')
|
||||
record = module.params.get('record')
|
||||
record_ids = module.params.get('record_ids')
|
||||
record_type = module.params.get('type')
|
||||
ttl = module.params.get('ttl')
|
||||
value = module.params.get('value')
|
||||
priority = module.params.get('priority')
|
||||
state = module.params.get('state')
|
||||
is_solo = module.params.get('solo')
|
||||
|
||||
if account_email and account_api_token:
|
||||
client = DNSimple(email=account_email, api_token=account_api_token)
|
||||
elif os.environ.get('DNSIMPLE_EMAIL') and os.environ.get('DNSIMPLE_API_TOKEN'):
|
||||
client = DNSimple(email=os.environ.get('DNSIMPLE_EMAIL'), api_token=os.environ.get('DNSIMPLE_API_TOKEN'))
|
||||
else:
|
||||
client = DNSimple()
|
||||
|
||||
try:
|
||||
# Let's figure out what operation we want to do
|
||||
|
||||
# No domain, return a list
|
||||
if not domain:
|
||||
domains = client.domains()
|
||||
module.exit_json(changed=False, result=[d['domain'] for d in domains])
|
||||
|
||||
# Domain & No record
|
||||
if domain and record is None and not record_ids:
|
||||
domains = [d['domain'] for d in client.domains()]
|
||||
if domain.isdigit():
|
||||
dr = next((d for d in domains if d['id'] == int(domain)), None)
|
||||
else:
|
||||
dr = next((d for d in domains if d['name'] == domain), None)
|
||||
if state == 'present':
|
||||
if dr:
|
||||
module.exit_json(changed=False, result=dr)
|
||||
else:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=True, result=client.add_domain(domain)['domain'])
|
||||
elif state == 'absent':
|
||||
if dr:
|
||||
if not module.check_mode:
|
||||
client.delete(domain)
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
else:
|
||||
module.fail_json(msg="'%s' is an unknown value for the state argument" % state)
|
||||
|
||||
# need the not none check since record could be an empty string
|
||||
if domain and record is not None:
|
||||
records = [r['record'] for r in client.records(str(domain))]
|
||||
|
||||
if not record_type:
|
||||
module.fail_json(msg="Missing the record type")
|
||||
|
||||
if not value:
|
||||
module.fail_json(msg="Missing the record value")
|
||||
|
||||
rr = next((r for r in records if r['name'] == record and r['record_type'] == record_type and r['content'] == value), None)
|
||||
|
||||
if state == 'present':
|
||||
changed = False
|
||||
if is_solo:
|
||||
# delete any records that have the same name and record type
|
||||
same_type = [r['id'] for r in records if r['name'] == record and r['record_type'] == record_type]
|
||||
if rr:
|
||||
same_type = [rid for rid in same_type if rid != rr['id']]
|
||||
if same_type:
|
||||
if not module.check_mode:
|
||||
for rid in same_type:
|
||||
client.delete_record(str(domain), rid)
|
||||
changed = True
|
||||
if rr:
|
||||
# check if we need to update
|
||||
if rr['ttl'] != ttl or rr['prio'] != priority:
|
||||
data = {}
|
||||
if ttl: data['ttl'] = ttl
|
||||
if priority: data['prio'] = priority
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=True, result=client.update_record(str(domain), str(rr['id']), data)['record'])
|
||||
else:
|
||||
module.exit_json(changed=changed, result=rr)
|
||||
else:
|
||||
# create it
|
||||
data = {
|
||||
'name': record,
|
||||
'record_type': record_type,
|
||||
'content': value,
|
||||
}
|
||||
if ttl: data['ttl'] = ttl
|
||||
if priority: data['prio'] = priority
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=True, result=client.add_record(str(domain), data)['record'])
|
||||
elif state == 'absent':
|
||||
if rr:
|
||||
if not module.check_mode:
|
||||
client.delete_record(str(domain), rr['id'])
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
else:
|
||||
module.fail_json(msg="'%s' is an unknown value for the state argument" % state)
|
||||
|
||||
# Make sure these record_ids either all exist or none
|
||||
if domain and record_ids:
|
||||
current_records = [str(r['record']['id']) for r in client.records(str(domain))]
|
||||
wanted_records = [str(r) for r in record_ids]
|
||||
if state == 'present':
|
||||
difference = list(set(wanted_records) - set(current_records))
|
||||
if difference:
|
||||
module.fail_json(msg="Missing the following records: %s" % difference)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
elif state == 'absent':
|
||||
difference = list(set(wanted_records) & set(current_records))
|
||||
if difference:
|
||||
if not module.check_mode:
|
||||
for rid in difference:
|
||||
client.delete_record(str(domain), rid)
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
else:
|
||||
module.fail_json(msg="'%s' is an unknown value for the state argument" % state)
|
||||
|
||||
except DNSimpleException, e:
|
||||
module.fail_json(msg="Unable to contact DNSimple: %s" % e.message)
|
||||
|
||||
module.fail_json(msg="Unknown what you wanted me to do")
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/python -tt
|
||||
# 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/>.
|
||||
|
||||
import subprocess
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: lldp
|
||||
version_added: 1.6
|
||||
short_description: get details reported by lldp
|
||||
description:
|
||||
- Reads data out of lldpctl
|
||||
options: {}
|
||||
author: Andy Hill
|
||||
notes:
|
||||
- Requires lldpd running and lldp enabled on switches
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Retrieve switch/port information
|
||||
- name: Gather information from lldp
|
||||
lldp:
|
||||
|
||||
- name: Print each switch/port
|
||||
debug: msg="{{ lldp[item]['chassis']['name'] }} / {{ lldp[item]['port']['ifalias'] }}
|
||||
with_items: lldp.keys()
|
||||
|
||||
# TASK: [Print each switch/port] ***********************************************************
|
||||
# ok: [10.13.0.22] => (item=eth2) => {"item": "eth2", "msg": "switch1.example.com / Gi0/24"}
|
||||
# ok: [10.13.0.22] => (item=eth1) => {"item": "eth1", "msg": "switch2.example.com / Gi0/3"}
|
||||
# ok: [10.13.0.22] => (item=eth0) => {"item": "eth0", "msg": "switch3.example.com / Gi0/3"}
|
||||
|
||||
'''
|
||||
|
||||
def gather_lldp():
|
||||
cmd = ['lldpctl', '-f', 'keyvalue']
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
(output, err) = proc.communicate()
|
||||
if output:
|
||||
output_dict = {}
|
||||
lldp_entries = output.split("\n")
|
||||
|
||||
for entry in lldp_entries:
|
||||
if entry:
|
||||
path, value = entry.strip().split("=", 1)
|
||||
path = path.split(".")
|
||||
path_components, final = path[:-1], path[-1]
|
||||
|
||||
current_dict = output_dict
|
||||
for path_component in path_components:
|
||||
current_dict[path_component] = current_dict.get(path_component, {})
|
||||
current_dict = current_dict[path_component]
|
||||
current_dict[final] = value
|
||||
return output_dict
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule({})
|
||||
|
||||
lldp_output = gather_lldp()
|
||||
try:
|
||||
data = {'lldp': lldp_output['lldp']}
|
||||
module.exit_json(ansible_facts=data)
|
||||
except TypeError:
|
||||
module.fail_json(msg="lldpctl command failed. is lldpd running?")
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
||||
|
@ -0,0 +1,140 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2014, Matt Martz <matt@sivel.net>
|
||||
#
|
||||
# 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: nexmo
|
||||
short_description: Send a SMS via nexmo
|
||||
description:
|
||||
- Send a SMS message via nexmo
|
||||
version_added: 1.6
|
||||
author: Matt Martz
|
||||
options:
|
||||
api_key:
|
||||
description:
|
||||
- Nexmo API Key
|
||||
required: true
|
||||
api_secret:
|
||||
description:
|
||||
- Nexmo API Secret
|
||||
required: true
|
||||
src:
|
||||
description:
|
||||
- Nexmo Number to send from
|
||||
required: true
|
||||
dest:
|
||||
description:
|
||||
- Phone number(s) to send SMS message to
|
||||
required: true
|
||||
msg:
|
||||
description:
|
||||
- Message to text to send. Messages longer than 160 characters will be
|
||||
split into multiple messages
|
||||
required: true
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
required: false
|
||||
default: 'yes'
|
||||
choices:
|
||||
- 'yes'
|
||||
- 'no'
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Send notification message via Nexmo
|
||||
local_action:
|
||||
module: nexmo
|
||||
api_key: 640c8a53
|
||||
api_secret: 0ce239a6
|
||||
src: 12345678901
|
||||
dest:
|
||||
- 10987654321
|
||||
- 16789012345
|
||||
msg: "{{ inventory_hostname }} completed"
|
||||
"""
|
||||
|
||||
|
||||
NEXMO_API = 'https://rest.nexmo.com/sms/json'
|
||||
|
||||
|
||||
def send_msg(module):
|
||||
failed = list()
|
||||
responses = dict()
|
||||
msg = {
|
||||
'api_key': module.params.get('api_key'),
|
||||
'api_secret': module.params.get('api_secret'),
|
||||
'from': module.params.get('src'),
|
||||
'text': module.params.get('msg')
|
||||
}
|
||||
for number in module.params.get('dest'):
|
||||
msg['to'] = number
|
||||
url = "%s?%s" % (NEXMO_API, urllib.urlencode(msg))
|
||||
|
||||
headers = dict(Accept='application/json')
|
||||
response, info = fetch_url(module, url, headers=headers)
|
||||
if info['status'] != 200:
|
||||
failed.append(number)
|
||||
responses[number] = dict(failed=True)
|
||||
|
||||
try:
|
||||
responses[number] = json.load(response)
|
||||
except:
|
||||
failed.append(number)
|
||||
responses[number] = dict(failed=True)
|
||||
else:
|
||||
for message in responses[number]['messages']:
|
||||
if int(message['status']) != 0:
|
||||
failed.append(number)
|
||||
responses[number] = dict(failed=True, **responses[number])
|
||||
|
||||
if failed:
|
||||
msg = 'One or messages failed to send'
|
||||
else:
|
||||
msg = ''
|
||||
|
||||
module.exit_json(failed=bool(failed), msg=msg, changed=False,
|
||||
responses=responses)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = url_argument_spec()
|
||||
argument_spec.update(
|
||||
dict(
|
||||
api_key=dict(required=True, no_log=True),
|
||||
api_secret=dict(required=True, no_log=True),
|
||||
src=dict(required=True, type='int'),
|
||||
dest=dict(required=True, type='list'),
|
||||
msg=dict(required=True),
|
||||
),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec
|
||||
)
|
||||
|
||||
send_msg(module)
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
|
||||
main()
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue