mirror of https://github.com/ansible/ansible.git
adding Google Compute Engine modules and inventory plugin
parent
85cd7f7500
commit
ab47f575dd
@ -0,0 +1,417 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright 2013 Google Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: gce
|
||||
short_description: create or terminate GCE instances
|
||||
description:
|
||||
- Creates or terminates Google Compute Engine (GCE) instances. See
|
||||
U(https://cloud.google.com/products/compute-engine) for an overview.
|
||||
Full install/configuration instructions for the gce* modules can
|
||||
be found in the comments of ansible/test/gce_tests.py.
|
||||
options:
|
||||
image:
|
||||
description:
|
||||
- image string to use for the instance
|
||||
required: false
|
||||
default: "debian-7"
|
||||
aliases: []
|
||||
instance_names:
|
||||
description:
|
||||
- a comma-separated list of instance names to create or destroy
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
machine_type:
|
||||
description:
|
||||
- machine type to use for the instance, use 'n1-standard-1' by default
|
||||
required: false
|
||||
default: "n1-standard-1"
|
||||
aliases: []
|
||||
metadata:
|
||||
description:
|
||||
- a hash/dictionary of custom data for the instance; '{"key":"value",...}'
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
name:
|
||||
description:
|
||||
- instance name (or name prefix) to be used for each created instance
|
||||
required: false
|
||||
default: "gce"
|
||||
aliases: []
|
||||
network:
|
||||
description:
|
||||
- name of the network, 'default' will be used if not specified
|
||||
required: false
|
||||
default: "default"
|
||||
aliases: []
|
||||
persistent_boot_disk:
|
||||
description:
|
||||
- if set, create the instance with a persistent boot disk
|
||||
required: false
|
||||
default: "false"
|
||||
aliases: []
|
||||
state:
|
||||
description:
|
||||
- desired state of the resource
|
||||
required: false
|
||||
default: "present"
|
||||
choices: ["active", "present", "absent", "deleted"]
|
||||
aliases: []
|
||||
tags:
|
||||
description:
|
||||
- a comma-separated list of tags to associate with the instance
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
zone:
|
||||
description:
|
||||
- the GCE zone to use
|
||||
required: true
|
||||
default: "us-central1-a"
|
||||
choices: ["us-central1-a", "us-central1-b", "us-central2-a", "europe-west1-a", "europe-west1-b"]
|
||||
aliases: []
|
||||
|
||||
requirements: [ "libcloud" ]
|
||||
author: Eric Johnson <erjohnso@google.com>
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Basic provisioning example. Create a single Debian 7 instance in the
|
||||
# us-central1-a Zone of n1-standard-1 machine type.
|
||||
- local_action:
|
||||
module: gce
|
||||
name: test-instance
|
||||
zone: us-central1-a
|
||||
machine_type: n1-standard-1
|
||||
image: debian-7
|
||||
|
||||
# Example using defaults and with metadata to create a single 'foo' instance
|
||||
- local_action:
|
||||
module: gce
|
||||
name: foo
|
||||
metadata: '{"db":"postgres", "group":"qa", "id":500}'
|
||||
|
||||
|
||||
# Launch instances from a control node, runs some tasks on the new instances,
|
||||
# and then terminate them
|
||||
- name: Create a sandbox instance
|
||||
hosts: localhost
|
||||
vars:
|
||||
names: foo,bar
|
||||
machine_type: n1-standard-1
|
||||
image: debian-6
|
||||
zone: us-central1-a
|
||||
tasks:
|
||||
- name: Launch instances
|
||||
local_action: gce instance_names=${names} machine_type=${machine_type}
|
||||
image=${image} zone=${zone}
|
||||
register: gce
|
||||
- name: Wait for SSH to come up
|
||||
local_action: wait_for host=${item.public_ip} port=22 delay=10
|
||||
timeout=60 state=started
|
||||
with_items: ${gce.instance_data}
|
||||
|
||||
- name: Configure instance(s)
|
||||
hosts: launched
|
||||
sudo: True
|
||||
roles:
|
||||
- my_awesome_role
|
||||
- my_awesome_tasks
|
||||
|
||||
- name: Terminate instances
|
||||
hosts: localhost
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Terminate instances that were previously launched
|
||||
local_action:
|
||||
module: gce
|
||||
state: 'absent'
|
||||
instance_names: ${gce.instance_names}
|
||||
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
from libcloud.compute.types import Provider
|
||||
from libcloud.compute.providers import get_driver
|
||||
from libcloud.common.google import GoogleBaseError, QuotaExceededError, \
|
||||
ResourceExistsError, ResourceInUseError, ResourceNotFoundError
|
||||
_ = Provider.GCE
|
||||
except ImportError:
|
||||
print("failed=True " + \
|
||||
"msg='libcloud with GCE support (0.13.3+) required for this module'")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
from ast import literal_eval
|
||||
except ImportError:
|
||||
print("failed=True " + \
|
||||
"msg='GCE module requires python's 'ast' module, python v2.6+'")
|
||||
sys.exit(1)
|
||||
|
||||
# Load in the libcloud secrets file
|
||||
try:
|
||||
import secrets
|
||||
except ImportError:
|
||||
secrets = None
|
||||
ARGS = getattr(secrets, 'GCE_PARAMS', ())
|
||||
KWARGS = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
|
||||
|
||||
if not ARGS or not KWARGS.has_key('project'):
|
||||
print("failed=True " + \
|
||||
"msg='Missing GCE connection parametres in libcloud secrets file.'")
|
||||
sys.exit(1)
|
||||
|
||||
def unexpected_error_msg(error):
|
||||
"""Create an error string based on passed in error."""
|
||||
msg='Unexpected response: HTTP return_code['
|
||||
msg+='%s], API error code[%s] and message: %s' % (
|
||||
error.http_code, error.code, str(error.value))
|
||||
return msg
|
||||
|
||||
def get_instance_info(inst):
|
||||
"""Retrieves instance information from an instance object and returns it
|
||||
as a dictionary.
|
||||
|
||||
"""
|
||||
metadata = {}
|
||||
if inst.extra.has_key('metadata') and inst.extra['metadata'].has_key('items'):
|
||||
for md in inst.extra['metadata']['items']:
|
||||
metadata[md['key']] = md['value']
|
||||
|
||||
try:
|
||||
netname = inst.extra['networkInterfaces'][0]['network'].split('/')[-1]
|
||||
except:
|
||||
netname = None
|
||||
return({
|
||||
'image': not inst.image is None and inst.image.split('/')[-1] or None,
|
||||
'machine_type': inst.size,
|
||||
'metadata': metadata,
|
||||
'name': inst.name,
|
||||
'network': netname,
|
||||
'private_ip': inst.private_ip[0],
|
||||
'public_ip': inst.public_ip[0],
|
||||
'status': inst.extra.has_key('status') and inst.extra['status'] or None,
|
||||
'tags': inst.extra.has_key('tags') and inst.extra['tags'] or [],
|
||||
'zone': inst.extra.has_key('zone') and inst.extra['zone'].name or None,
|
||||
})
|
||||
|
||||
def create_instances(module, gce, instance_names):
|
||||
"""Creates new instances. Attributes other than instance_names are picked
|
||||
up from 'module'
|
||||
|
||||
module : AnsbileModule object
|
||||
gce: authenticated GCE libcloud driver
|
||||
instance_names: python list of instance names to create
|
||||
|
||||
Returns:
|
||||
A list of dictionaries with instance information
|
||||
about the instances that were launched.
|
||||
|
||||
"""
|
||||
image = module.params.get('image')
|
||||
machine_type = module.params.get('machine_type')
|
||||
metadata = module.params.get('metadata')
|
||||
network = module.params.get('network')
|
||||
persistent_boot_disk = module.params.get('persistent_boot_disk')
|
||||
state = module.params.get('state')
|
||||
tags = module.params.get('tags')
|
||||
zone = module.params.get('zone')
|
||||
|
||||
new_instances = []
|
||||
changed = False
|
||||
|
||||
lc_image = gce.ex_get_image(image)
|
||||
lc_network = gce.ex_get_network(network)
|
||||
lc_machine_type = gce.ex_get_size(machine_type)
|
||||
lc_zone = gce.ex_get_zone(zone)
|
||||
|
||||
# Try to convert the user's metadata value into the format expected
|
||||
# by GCE. First try to ensure user has proper quoting of a
|
||||
# dictionary-like syntax using 'literal_eval', then convert the python
|
||||
# dict into a python list of 'key' / 'value' dicts. Should end up
|
||||
# with:
|
||||
# [ {'key': key1, 'value': value1}, {'key': key2, 'value': value2}, ...]
|
||||
if metadata:
|
||||
try:
|
||||
md = literal_eval(metadata)
|
||||
if not isinstance(md, dict):
|
||||
raise ValueError('metadata must be a dict')
|
||||
except ValueError as e:
|
||||
print("failed=True msg='bad metadata: %s'" % str(e))
|
||||
sys.exit(1)
|
||||
except SyntaxError as e:
|
||||
print("failed=True msg='bad metadata syntax'")
|
||||
sys.exit(1)
|
||||
|
||||
items = []
|
||||
for k,v in md.items():
|
||||
items.append({"key": k,"value": v})
|
||||
metadata = {'items': items}
|
||||
|
||||
# These variables all have default values but check just in case
|
||||
if not lc_image or not lc_network or not lc_machine_type or not lc_zone:
|
||||
module.fail_json(msg='Missing required create instance variable',
|
||||
changed=False)
|
||||
|
||||
for name in instance_names:
|
||||
pd = None
|
||||
if persistent_boot_disk:
|
||||
try:
|
||||
pd = gce.create_volume(None, "%s" % name, image=lc_image)
|
||||
except ResourceExistsError:
|
||||
pd = gce.ex_get_volume("%s" % name, lc_zone)
|
||||
inst = None
|
||||
try:
|
||||
inst = gce.create_node(name, lc_machine_type, lc_image,
|
||||
location=lc_zone, ex_network=network, ex_tags=tags,
|
||||
ex_metadata=metadata, ex_boot_disk=pd)
|
||||
changed = True
|
||||
except ResourceExistsError:
|
||||
inst = gce.ex_get_node(name, lc_zone)
|
||||
except GoogleBaseError as e:
|
||||
module.fail_json(msg='Unexpected error attempting to create ' + \
|
||||
'instance %s, error: %s' % (name, e.value))
|
||||
|
||||
if inst:
|
||||
new_instances.append(inst)
|
||||
|
||||
instance_names = []
|
||||
instance_json_data = []
|
||||
for inst in new_instances:
|
||||
d = get_instance_info(inst)
|
||||
instance_names.append(d['name'])
|
||||
instance_json_data.append(d)
|
||||
|
||||
return (changed, instance_json_data, instance_names)
|
||||
|
||||
|
||||
def terminate_instances(module, gce, instance_names, zone_name):
|
||||
"""Terminates a list of instances.
|
||||
|
||||
module: Ansible module object
|
||||
gce: authenticated GCE connection object
|
||||
instance_names: a list of instance names to terminate
|
||||
zone_name: the zone where the instances reside prior to termination
|
||||
|
||||
Returns a dictionary of instance names that were terminated.
|
||||
|
||||
"""
|
||||
changed = False
|
||||
terminated_instance_names = []
|
||||
for name in instance_names:
|
||||
inst = None
|
||||
try:
|
||||
inst = gce.ex_get_node(name, zone_name)
|
||||
except ResourceNotFoundError:
|
||||
pass
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
if inst:
|
||||
gce.destroy_node(inst)
|
||||
terminated_instance_names.append(inst.name)
|
||||
changed = True
|
||||
|
||||
return (changed, terminated_instance_names)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
image = dict(default='debian-7'),
|
||||
instance_names = dict(),
|
||||
machine_type = dict(default='n1-standard-1'),
|
||||
metadata = dict(),
|
||||
name = dict(),
|
||||
network = dict(default='default'),
|
||||
persistent_boot_disk = dict(choices=BOOLEANS, default=False),
|
||||
state = dict(choices=['active', 'present', 'absent', 'deleted'],
|
||||
default='present'),
|
||||
tags = dict(type='list'),
|
||||
zone = dict(choices=['us-central1-a', 'us-central1-b',
|
||||
'us-central2-a', 'europe-west1-a', 'europe-west1-b'],
|
||||
default='us-central1-a'),
|
||||
)
|
||||
)
|
||||
|
||||
image = module.params.get('image')
|
||||
instance_names = module.params.get('instance_names')
|
||||
machine_type = module.params.get('machine_type')
|
||||
metadata = module.params.get('metadata')
|
||||
name = module.params.get('name')
|
||||
network = module.params.get('network')
|
||||
persistent_boot_disk = module.params.get('persistent_boot_disk')
|
||||
state = module.params.get('state')
|
||||
tags = module.params.get('tags')
|
||||
zone = module.params.get('zone')
|
||||
changed = False
|
||||
|
||||
try:
|
||||
gce = get_driver(Provider.GCE)(*ARGS, datacenter=zone, **KWARGS)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
|
||||
inames = []
|
||||
if isinstance(instance_names, list):
|
||||
inames = instance_names
|
||||
elif isinstance(instance_names, str):
|
||||
inames = instance_names.split(',')
|
||||
if name:
|
||||
inames.append(name)
|
||||
if not inames:
|
||||
module.fail_json(msg='Must specify a "name" or "instance_names"',
|
||||
changed=False)
|
||||
if not zone:
|
||||
module.fail_json(msg='Must specify a "zone"', changed=False)
|
||||
|
||||
json_output = {'zone': zone}
|
||||
if state in ['absent', 'deleted']:
|
||||
json_output['state'] = 'absent'
|
||||
(changed, terminated_instance_names) = terminate_instances(module,
|
||||
gce, inames, zone)
|
||||
|
||||
# based on what user specified, return the same variable, although
|
||||
# value could be different if an instance could not be destroyed
|
||||
if instance_names:
|
||||
json_output['instance_names'] = terminated_instance_names
|
||||
elif name:
|
||||
json_output['name'] = name
|
||||
|
||||
elif state in ['active', 'present']:
|
||||
json_output['state'] = 'present'
|
||||
(changed, instance_data,instance_name_list) = create_instances(
|
||||
module, gce, inames)
|
||||
json_output['instance_data'] = instance_data
|
||||
if instance_names:
|
||||
json_output['instance_names'] = instance_name_list
|
||||
elif name:
|
||||
json_output['name'] = name
|
||||
|
||||
|
||||
json_output['changed'] = changed
|
||||
print json.dumps(json_output)
|
||||
sys.exit(0)
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
|
||||
main()
|
@ -0,0 +1,325 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright 2013 Google Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: gce_lb
|
||||
short_description: create/destroy GCE load-balancer resources
|
||||
description:
|
||||
- This module can create and destroy Google Compue Engine C(loadbalancer)
|
||||
and C(httphealthcheck) resources. The primary LB resource is the
|
||||
C(load_balancer) resource and the health check parameters are all
|
||||
prefixed with I(httphealthcheck).
|
||||
The full documentation for Google Compute Engine load balancing is at
|
||||
U(https://developers.google.com/compute/docs/load-balancing/). However,
|
||||
the ansible module simplifies the configuration by following the
|
||||
libcloud model.
|
||||
Full install/configuration instructions for the gce* modules can
|
||||
be found in the comments of ansible/test/gce_tests.py.
|
||||
options:
|
||||
httphealthcheck_name:
|
||||
description:
|
||||
- the name identifier for the HTTP health check
|
||||
required: false
|
||||
default: null
|
||||
httphealthcheck_port:
|
||||
description:
|
||||
- the TCP port to use for HTTP health checking
|
||||
required: false
|
||||
default: 80
|
||||
httphealthcheck_path:
|
||||
description:
|
||||
- the url path to use for HTTP health checking
|
||||
required: false
|
||||
default: "/"
|
||||
httphealthcheck_interval:
|
||||
description:
|
||||
- the duration in seconds between each health check request
|
||||
required: false
|
||||
default: 5
|
||||
httphealthcheck_timeout:
|
||||
description:
|
||||
- the timeout in seconds before a request is considered a failed check
|
||||
required: false
|
||||
default: 5
|
||||
httphealthcheck_unhealthy_count:
|
||||
description:
|
||||
- number of consecutive failed checks before marking a node unhealthy
|
||||
required: false
|
||||
default: 2
|
||||
httphealthcheck_healthy_count:
|
||||
description:
|
||||
- number of consecutive successful checks before marking a node healthy
|
||||
required: false
|
||||
default: 2
|
||||
httphealthcheck_host:
|
||||
description:
|
||||
- host header to pass through on HTTP check requests
|
||||
required: false
|
||||
default: null
|
||||
name:
|
||||
description:
|
||||
- name of the load-balancer resource
|
||||
required: false
|
||||
default: null
|
||||
protocol:
|
||||
description:
|
||||
- the protocol used for the load-balancer packet forwarding, tcp or udp
|
||||
required: false
|
||||
default: "tcp"
|
||||
choices: ['tcp', 'udp']
|
||||
region:
|
||||
description:
|
||||
- the GCE region where the load-balancer is defined
|
||||
required: false
|
||||
choices: ["us-central1", "us-central2", "europe-west1"]
|
||||
external_ip:
|
||||
description:
|
||||
- the external static IPv4 (or auto-assigned) address for the LB
|
||||
required: false
|
||||
default: null
|
||||
port_range:
|
||||
description:
|
||||
- the port (range) to forward, e.g. 80 or 8000-8888 defaults to all ports
|
||||
required: false
|
||||
default: null
|
||||
members:
|
||||
description:
|
||||
- a list of zone/nodename pairs, e.g ['us-central1-a/www-a', ...]
|
||||
required: false
|
||||
aliases: ['nodes']
|
||||
state:
|
||||
description:
|
||||
- desired state of the LB
|
||||
default: "present"
|
||||
choices: ["active", "present", "absent", "deleted"]
|
||||
aliases: []
|
||||
|
||||
requirements: [ "libcloud" ]
|
||||
author: Eric Johnson <erjohnso@google.com>
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Simple example of creating a new LB, adding members, and a health check
|
||||
- local_action:
|
||||
module: gce_lb
|
||||
name: testlb
|
||||
region: us-central1
|
||||
members: ["us-central1-a/www-a", "us-central1-b/www-b"]
|
||||
httphealthcheck_name: hc
|
||||
httphealthcheck_port: 80
|
||||
httphealthcheck_path: "/up"
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
from libcloud.compute.types import Provider
|
||||
from libcloud.compute.providers import get_driver
|
||||
from libcloud.loadbalancer.types import Provider as Provider_lb
|
||||
from libcloud.loadbalancer.providers import get_driver as get_driver_lb
|
||||
from libcloud.common.google import GoogleBaseError, QuotaExceededError, \
|
||||
ResourceExistsError, ResourceNotFoundError
|
||||
_ = Provider.GCE
|
||||
except ImportError:
|
||||
print("failed=True " + \
|
||||
"msg='libcloud with GCE support required for this module.'")
|
||||
sys.exit(1)
|
||||
|
||||
# Load in the libcloud secrets file
|
||||
try:
|
||||
import secrets
|
||||
except ImportError:
|
||||
secrets = None
|
||||
ARGS = getattr(secrets, 'GCE_PARAMS', ())
|
||||
KWARGS = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
|
||||
|
||||
if not ARGS or not KWARGS.has_key('project'):
|
||||
print("failed=True msg='Missing GCE connection " + \
|
||||
"parameters in libcloud secrets file.'")
|
||||
sys.exit(1)
|
||||
|
||||
def unexpected_error_msg(error):
|
||||
"""Format error string based on passed in error."""
|
||||
msg='Unexpected response: HTTP return_code['
|
||||
msg+='%s], API error code[%s] and message: %s' % (
|
||||
error.http_code, error.code, str(error.value))
|
||||
return msg
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
httphealthcheck_name = dict(),
|
||||
httphealthcheck_port = dict(default=80),
|
||||
httphealthcheck_path = dict(default='/'),
|
||||
httphealthcheck_interval = dict(default=5),
|
||||
httphealthcheck_timeout = dict(default=5),
|
||||
httphealthcheck_unhealthy_count = dict(default=2),
|
||||
httphealthcheck_healthy_count = dict(default=2),
|
||||
httphealthcheck_host = dict(),
|
||||
name = dict(),
|
||||
protocol = dict(default='tcp'),
|
||||
region = dict(),
|
||||
external_ip = dict(),
|
||||
port_range = dict(),
|
||||
members = dict(type='list'),
|
||||
state = dict(default='present'),
|
||||
)
|
||||
)
|
||||
|
||||
httphealthcheck_name = module.params.get('httphealthcheck_name')
|
||||
httphealthcheck_port = module.params.get('httphealthcheck_port')
|
||||
httphealthcheck_path = module.params.get('httphealthcheck_path')
|
||||
httphealthcheck_interval = module.params.get('httphealthcheck_interval')
|
||||
httphealthcheck_timeout = module.params.get('httphealthcheck_timeout')
|
||||
httphealthcheck_unhealthy_count = \
|
||||
module.params.get('httphealthcheck_unhealthy_count')
|
||||
httphealthcheck_healthy_count = \
|
||||
module.params.get('httphealthcheck_healthy_count')
|
||||
httphealthcheck_host = module.params.get('httphealthcheck_host')
|
||||
name = module.params.get('name')
|
||||
protocol = module.params.get('protocol')
|
||||
region = module.params.get('region')
|
||||
external_ip = module.params.get('external_ip')
|
||||
port_range = module.params.get('port_range')
|
||||
members = module.params.get('members')
|
||||
state = module.params.get('state')
|
||||
|
||||
try:
|
||||
gce = get_driver(Provider.GCE)(*ARGS, **KWARGS)
|
||||
gcelb = get_driver_lb(Provider_lb.GCE)(gce_driver=gce)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
|
||||
changed = False
|
||||
json_output = {'name': name, 'state': state}
|
||||
|
||||
if not name and not httphealthcheck_name:
|
||||
module.fail_json(msg='Nothing to do, please specify a "name" ' + \
|
||||
'or "httphealthcheck_name" parameter', changed=False)
|
||||
|
||||
if state in ['active', 'present']:
|
||||
# first, create the httphealthcheck if requested
|
||||
hc = None
|
||||
if httphealthcheck_name:
|
||||
json_output['httphealthcheck_name'] = httphealthcheck_name
|
||||
try:
|
||||
hc = gcelb.ex_create_healthcheck(httphealthcheck_name,
|
||||
host=httphealthcheck_host, path=httphealthcheck_path,
|
||||
port=httphealthcheck_port,
|
||||
interval=httphealthcheck_interval,
|
||||
timeout=httphealthcheck_timeout,
|
||||
unhealthy_threshold=httphealthcheck_unhealthy_count,
|
||||
healthy_threshold=httphealthcheck_healthy_count)
|
||||
changed = True
|
||||
except ResourceExistsError:
|
||||
hc = gce.ex_get_healthcheck(httphealthcheck_name)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
|
||||
if hc is not None:
|
||||
json_output['httphealthcheck_host'] = hc.extra['host']
|
||||
json_output['httphealthcheck_path'] = hc.path
|
||||
json_output['httphealthcheck_port'] = hc.port
|
||||
json_output['httphealthcheck_interval'] = hc.interval
|
||||
json_output['httphealthcheck_timeout'] = hc.timeout
|
||||
json_output['httphealthcheck_unhealthy_count'] = \
|
||||
hc.unhealthy_threshold
|
||||
json_output['httphealthcheck_healthy_count'] = \
|
||||
hc.healthy_threshold
|
||||
|
||||
# create the forwarding rule (and target pool under the hood)
|
||||
lb = None
|
||||
if name:
|
||||
if not region:
|
||||
module.fail_json(msg='Missing required region name',
|
||||
changed=False)
|
||||
nodes = []
|
||||
output_nodes = []
|
||||
json_output['name'] = name
|
||||
# members is a python list of 'zone/inst' strings
|
||||
if members:
|
||||
for node in members:
|
||||
try:
|
||||
zone, node_name = node.split('/')
|
||||
nodes.append(gce.ex_get_node(node_name, zone))
|
||||
output_nodes.append(node)
|
||||
except:
|
||||
# skip nodes that are badly formatted or don't exist
|
||||
pass
|
||||
try:
|
||||
if hc is not None:
|
||||
lb = gcelb.create_balancer(name, port_range, protocol,
|
||||
None, nodes, ex_region=region, ex_healthchecks=[hc],
|
||||
ex_address=external_ip)
|
||||
else:
|
||||
lb = gcelb.create_balancer(name, port_range, protocol,
|
||||
None, nodes, ex_region=region, ex_address=external_ip)
|
||||
changed = True
|
||||
except ResourceExistsError:
|
||||
lb = gcelb.get_balancer(name)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
|
||||
if lb is not None:
|
||||
json_output['members'] = output_nodes
|
||||
json_output['protocol'] = protocol
|
||||
json_output['region'] = region
|
||||
json_output['external_ip'] = lb.ip
|
||||
json_output['port_range'] = lb.port
|
||||
hc_names = []
|
||||
if lb.extra.has_key('healthchecks'):
|
||||
for hc in lb.extra['healthchecks']:
|
||||
hc_names.append(hc.name)
|
||||
json_output['httphealthchecks'] = hc_names
|
||||
|
||||
if state in ['absent', 'deleted']:
|
||||
# first, delete the load balancer (forwarding rule and target pool)
|
||||
# if specified.
|
||||
if name:
|
||||
json_output['name'] = name
|
||||
try:
|
||||
lb = gcelb.get_balancer(name)
|
||||
gcelb.destroy_balancer(lb)
|
||||
changed = True
|
||||
except ResourceNotFoundError:
|
||||
pass
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
|
||||
# destroy the health check if specified
|
||||
if httphealthcheck_name:
|
||||
json_output['httphealthcheck_name'] = httphealthcheck_name
|
||||
try:
|
||||
hc = gce.ex_get_healthcheck(httphealthcheck_name)
|
||||
gce.ex_destroy_healthcheck(hc)
|
||||
changed = True
|
||||
except ResourceNotFoundError:
|
||||
pass
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
|
||||
|
||||
json_output['changed'] = changed
|
||||
print json.dumps(json_output)
|
||||
sys.exit(0)
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
|
||||
main()
|
@ -0,0 +1,267 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright 2013 Google Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: gce_net
|
||||
short_description: create/destroy GCE networks and firewall rules
|
||||
description:
|
||||
- This module can create and destroy Google Compue Engine networks and
|
||||
firewall rules U(https://developers.google.com/compute/docs/networking).
|
||||
The I(name) parameter is reserved for referencing a network while the
|
||||
I(fwname) parameter is used to reference firewall rules.
|
||||
IPv4 Address ranges must be specified using the CIDR
|
||||
U(http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) format.
|
||||
Full install/configuration instructions for the gce* modules can
|
||||
be found in the comments of ansible/test/gce_tests.py.
|
||||
options:
|
||||
allowed:
|
||||
description:
|
||||
- the protocol:ports to allow ('tcp:80' or 'tcp:80,443' or 'tcp:80-800')
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
ipv4_range:
|
||||
description:
|
||||
- the IPv4 address range in CIDR notation for the network
|
||||
required: false
|
||||
aliases: ['cidr']
|
||||
fwname:
|
||||
description:
|
||||
- name of the firewall rule
|
||||
required: false
|
||||
default: null
|
||||
aliases: ['fwrule']
|
||||
name:
|
||||
description:
|
||||
- name of the network
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
src_range:
|
||||
description:
|
||||
- the source IPv4 address range in CIDR notation
|
||||
required: false
|
||||
default: null
|
||||
aliases: ['src_cidr']
|
||||
src_tags:
|
||||
description:
|
||||
- the source instance tags for creating a firewall rule
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
state:
|
||||
description:
|
||||
- desired state of the persistent disk
|
||||
required: false
|
||||
default: "present"
|
||||
choices: ["active", "present", "absent", "deleted"]
|
||||
aliases: []
|
||||
|
||||
requirements: [ "libcloud" ]
|
||||
author: Eric Johnson <erjohnso@google.com>
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Simple example of creating a new network
|
||||
- local_action:
|
||||
module: gce_net
|
||||
name: privatenet
|
||||
ipv4_range: '10.240.16.0/24'
|
||||
|
||||
# Simple example of creating a new firewall rule
|
||||
- local_action:
|
||||
module: gce_net
|
||||
name: privatenet
|
||||
allowed: tcp:80,8080
|
||||
src_tags: ["web", "proxy"]
|
||||
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
from libcloud.compute.types import Provider
|
||||
from libcloud.compute.providers import get_driver
|
||||
from libcloud.common.google import GoogleBaseError, QuotaExceededError, \
|
||||
ResourceExistsError, ResourceNotFoundError
|
||||
_ = Provider.GCE
|
||||
except ImportError:
|
||||
print("failed=True " + \
|
||||
"msg='libcloud with GCE support required for this module.'")
|
||||
sys.exit(1)
|
||||
|
||||
# Load in the libcloud secrets file
|
||||
try:
|
||||
import secrets
|
||||
except ImportError:
|
||||
secrets = None
|
||||
ARGS = getattr(secrets, 'GCE_PARAMS', ())
|
||||
KWARGS = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
|
||||
|
||||
if not ARGS or not KWARGS.has_key('project'):
|
||||
print("failed=True msg='Missing GCE connection " + \
|
||||
"parameters in libcloud secrets file.'")
|
||||
sys.exit(1)
|
||||
|
||||
def unexpected_error_msg(error):
|
||||
"""Format error string based on passed in error."""
|
||||
msg='Unexpected response: HTTP return_code['
|
||||
msg+='%s], API error code[%s] and message: %s' % (
|
||||
error.http_code, error.code, str(error.value))
|
||||
return msg
|
||||
|
||||
def format_allowed(allowed):
|
||||
"""Format the 'allowed' value so that it is GCE compatible."""
|
||||
if allowed.count(":") == 0:
|
||||
protocol = allowed
|
||||
ports = []
|
||||
elif allowed.count(":") == 1:
|
||||
protocol, ports = allowed.split(":")
|
||||
else:
|
||||
return []
|
||||
if ports.count(","):
|
||||
ports = ports.split(",")
|
||||
else:
|
||||
ports = [ports]
|
||||
return_val = {"IPProtocol": protocol}
|
||||
if ports:
|
||||
return_val["ports"] = ports
|
||||
return [return_val]
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
allowed = dict(),
|
||||
ipv4_range = dict(),
|
||||
fwname = dict(),
|
||||
name = dict(),
|
||||
src_range = dict(),
|
||||
src_tags = dict(type='list'),
|
||||
state = dict(default='present'),
|
||||
)
|
||||
)
|
||||
|
||||
allowed = module.params.get('allowed')
|
||||
ipv4_range = module.params.get('ipv4_range')
|
||||
fwname = module.params.get('fwname')
|
||||
name = module.params.get('name')
|
||||
src_range = module.params.get('src_range')
|
||||
src_tags = module.params.get('src_tags')
|
||||
state = module.params.get('state')
|
||||
|
||||
try:
|
||||
gce = get_driver(Provider.GCE)(*ARGS, **KWARGS)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
|
||||
changed = False
|
||||
json_output = {'state': state}
|
||||
|
||||
if state in ['active', 'present']:
|
||||
network = None
|
||||
try:
|
||||
network = gce.ex_get_network(name)
|
||||
json_output['name'] = name
|
||||
json_output['ipv4_range'] = network.cidr
|
||||
except ResourceNotFoundError:
|
||||
pass
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
|
||||
# user wants to create a new network that doesn't yet exist
|
||||
if name and not network:
|
||||
if not ipv4_range:
|
||||
module.fail_json(msg="Missing required 'ipv4_range' parameter",
|
||||
changed=False)
|
||||
|
||||
try:
|
||||
network = gce.ex_create_network(name, ipv4_range)
|
||||
json_output['name'] = name
|
||||
json_output['ipv4_range'] = ipv4_range
|
||||
changed = True
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
|
||||
if fwname:
|
||||
# user creating a firewall rule
|
||||
if not allowed and not src_range and not src_tags:
|
||||
if changed and network:
|
||||
module.fail_json(
|
||||
msg="Network created, but missing required " + \
|
||||
"firewall rule parameter(s)", changed=True)
|
||||
module.fail_json(
|
||||
msg="Missing required firewall rule parameter(s)",
|
||||
changed=False)
|
||||
|
||||
allowed_list = format_allowed(allowed)
|
||||
|
||||
try:
|
||||
gce.ex_create_firewall(fwname, allowed_list, network=name,
|
||||
source_ranges=src_range, source_tags=src_tags)
|
||||
changed = True
|
||||
except ResourceExistsError:
|
||||
pass
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
|
||||
json_output['fwname'] = fwname
|
||||
json_output['allowed'] = allowed
|
||||
json_output['src_range'] = src_range
|
||||
json_output['src_tags'] = src_tags
|
||||
|
||||
if state in ['absent', 'deleted']:
|
||||
if fwname:
|
||||
json_output['fwname'] = fwname
|
||||
fw = None
|
||||
try:
|
||||
fw = gce.ex_get_firewall(fwname)
|
||||
except ResourceNotFoundError:
|
||||
pass
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
if fw:
|
||||
gce.ex_destroy_firewall(fw)
|
||||
changed = True
|
||||
if name:
|
||||
json_output['name'] = name
|
||||
network = None
|
||||
try:
|
||||
network = gce.ex_get_network(name)
|
||||
# json_output['d1'] = 'found network name %s' % name
|
||||
except ResourceNotFoundError:
|
||||
# json_output['d2'] = 'not found network name %s' % name
|
||||
pass
|
||||
except Exception as e:
|
||||
# json_output['d3'] = 'error with %s' % name
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
if network:
|
||||
# json_output['d4'] = 'deleting %s' % name
|
||||
gce.ex_destroy_network(network)
|
||||
# json_output['d5'] = 'deleted %s' % name
|
||||
changed = True
|
||||
|
||||
json_output['changed'] = changed
|
||||
print json.dumps(json_output)
|
||||
sys.exit(0)
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
|
||||
main()
|
@ -0,0 +1,248 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright 2013 Google Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: gce_pd
|
||||
short_description: utilize GCE persistent disk resources
|
||||
description:
|
||||
- This module can create and destroy unformatted GCE persistent disks
|
||||
U(https://developers.google.com/compute/docs/disks#persistentdisks).
|
||||
It also supports attaching and detaching disks from running instances
|
||||
but does not support creating boot disks from images or snapshots. The
|
||||
'gce' module supports creating instances with boot disks.
|
||||
Full install/configuration instructions for the gce* modules can
|
||||
be found in the comments of ansible/test/gce_tests.py.
|
||||
options:
|
||||
detach_only:
|
||||
description:
|
||||
- do not destroy the disk, merely detach it from an instance
|
||||
required: false
|
||||
default: "no"
|
||||
choices: ["yes", "no"]
|
||||
aliases: []
|
||||
instance_name:
|
||||
description:
|
||||
- instance name if you wish to attach or detach the disk
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
mode:
|
||||
description:
|
||||
- GCE mount mode of disk, READ_ONLY (default) or READ_WRITE
|
||||
required: false
|
||||
default: "READ_ONLY"
|
||||
choices: ["READ_WRITE", "READ_ONLY"]
|
||||
aliases: []
|
||||
name:
|
||||
description:
|
||||
- name of the disk
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
size_gb:
|
||||
description:
|
||||
- whole integer size of disk (in GB) to create, default is 10 GB
|
||||
required: false
|
||||
default: 10
|
||||
aliases: []
|
||||
state:
|
||||
description:
|
||||
- desired state of the persistent disk
|
||||
required: false
|
||||
default: "present"
|
||||
choices: ["active", "present", "absent", "deleted"]
|
||||
aliases: []
|
||||
zone:
|
||||
description:
|
||||
- zone in which to create the disk
|
||||
required: false
|
||||
default: "us-central1-b"
|
||||
aliases: []
|
||||
|
||||
requirements: [ "libcloud" ]
|
||||
author: Eric Johnson <erjohnso@google.com>
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Simple attachment action to an existing instance
|
||||
- local_action:
|
||||
module: gce_pd
|
||||
instance_name: notlocalhost
|
||||
size_gb: 5
|
||||
name: pd
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
from libcloud.compute.types import Provider
|
||||
from libcloud.compute.providers import get_driver
|
||||
from libcloud.common.google import GoogleBaseError, QuotaExceededError, \
|
||||
ResourceExistsError, ResourceNotFoundError, ResourceInUseError
|
||||
_ = Provider.GCE
|
||||
except ImportError:
|
||||
print("failed=True " + \
|
||||
"msg='libcloud with GCE support is required for this module.'")
|
||||
sys.exit(1)
|
||||
|
||||
# Load in the libcloud secrets file
|
||||
try:
|
||||
import secrets
|
||||
except ImportError:
|
||||
secrets = None
|
||||
ARGS = getattr(secrets, 'GCE_PARAMS', ())
|
||||
KWARGS = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
|
||||
|
||||
if not ARGS or not KWARGS.has_key('project'):
|
||||
print("failed=True " + \
|
||||
"msg='Missing GCE connection parameters in libcloud secrets file.'")
|
||||
sys.exit(1)
|
||||
|
||||
def unexpected_error_msg(error):
|
||||
msg='Unexpected response: HTTP return_code['
|
||||
msg+='%s], API error code[%s] and message: %s' % (
|
||||
error.http_code, error.code, str(error.value))
|
||||
return msg
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
detach_only = dict(choice=BOOLEANS),
|
||||
instance_name = dict(),
|
||||
mode = dict(default='READ_ONLY',
|
||||
choices=['READ_WRITE', 'READ_ONLY']),
|
||||
name = dict(required=True),
|
||||
size_gb = dict(default=10),
|
||||
state = dict(default='present'),
|
||||
zone = dict(default='us-central1-b'),
|
||||
)
|
||||
)
|
||||
|
||||
detach_only = module.params.get('detach_only')
|
||||
instance_name = module.params.get('instance_name')
|
||||
mode = module.params.get('mode')
|
||||
name = module.params.get('name')
|
||||
size_gb = module.params.get('size_gb')
|
||||
state = module.params.get('state')
|
||||
zone = module.params.get('zone')
|
||||
|
||||
if detach_only and not instance_name:
|
||||
module.fail_json(
|
||||
msg='Must specify an instance name when detaching a disk',
|
||||
changed=False)
|
||||
|
||||
try:
|
||||
gce = get_driver(Provider.GCE)(*ARGS, datacenter=zone, **KWARGS)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
|
||||
disk = inst = None
|
||||
changed = is_attached = False
|
||||
|
||||
json_output = { 'name': name, 'zone': zone, 'state': state }
|
||||
if detach_only:
|
||||
json_output['detach_only'] = True
|
||||
json_output['detached_from_instance'] = instance_name
|
||||
|
||||
if instance_name:
|
||||
# user wants to attach/detach from an existing instance
|
||||
try:
|
||||
inst = gce.ex_get_node(instance_name, zone)
|
||||
# is the disk attached?
|
||||
for d in inst.extra['disks']:
|
||||
if d['deviceName'] == name:
|
||||
is_attached = True
|
||||
json_output['attached_mode'] = d['mode']
|
||||
json_output['attached_to_instance'] = inst.name
|
||||
except:
|
||||
pass
|
||||
|
||||
# find disk if it already exists
|
||||
try:
|
||||
disk = gce.ex_get_volume(name)
|
||||
json_output['size_gb'] = int(disk.size)
|
||||
except ResourceNotFoundError:
|
||||
pass
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
|
||||
# user wants a disk to exist. If "instance_name" is supplied the user
|
||||
# also wants it attached
|
||||
if state in ['active', 'present']:
|
||||
|
||||
if not size_gb:
|
||||
module.fail_json(msg="Must supply a size_gb", changed=False)
|
||||
try:
|
||||
size_gb = int(round(float(size_gb)))
|
||||
if size_gb < 1: raise Exception
|
||||
except:
|
||||
module.fail_json(msg="Must supply a size_gb larger than 1 GB",
|
||||
changed=False)
|
||||
|
||||
if instance_name and inst is None:
|
||||
module.fail_json(msg='Instance %s does not exist in zone %s' % (
|
||||
instance_name, zone), changed=False)
|
||||
|
||||
if not disk:
|
||||
try:
|
||||
disk = gce.create_volume(size_gb, name, location=zone)
|
||||
except ResourceExistsError:
|
||||
pass
|
||||
except QuotaExceededError:
|
||||
module.fail_json(msg='Requested disk size exceeds quota',
|
||||
changed=False)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
json_output['size_gb'] = size_gb
|
||||
changed = True
|
||||
if inst and not is_attached:
|
||||
try:
|
||||
gce.attach_volume(inst, disk, device=name, ex_mode=mode)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
json_output['attached_to_instance'] = inst.name
|
||||
json_output['attached_mode'] = mode
|
||||
changed = True
|
||||
|
||||
# user wants to delete a disk (or perhaps just detach it).
|
||||
if state in ['absent', 'deleted'] and disk:
|
||||
|
||||
if inst and is_attached:
|
||||
try:
|
||||
gce.detach_volume(disk, ex_node=inst)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
changed = True
|
||||
if not detach_only:
|
||||
try:
|
||||
gce.destroy_volume(disk)
|
||||
except ResourceInUseError as e:
|
||||
module.fail_json(msg=str(e.value), changed=False)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
changed = True
|
||||
|
||||
json_output['changed'] = changed
|
||||
print json.dumps(json_output)
|
||||
sys.exit(0)
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
|
||||
main()
|
Loading…
Reference in New Issue