[cloud] New module: gce_template (#20918)
* add gce_template.py gce template manage GCE Instance Templates in Google Cloud Plateform. * change gce_template on style/formating Apply change on style/formating from reviewer request. * change gce_template on style/formating again. * Rename gce_template.py to gce_instance_template.py * update gce_instance_template.py to pass CI Oops :) ERROR: Import found before documentation variables. All imports must appear below DOCUMENTATION/EXAMPLES/RETURN/ANSIBLE_METADATA. * Update gce_instance_template.py change documentation replace gce_template by gce_instance_template * Update gce_instance_template.py Sorry for the typography fault. The day begins badly. * Update gce_instance_template.py apply text change s/Compte/Compute/ s/Plateform/Platform/ s/forword/forward/ change documentation for subnetwork, subnetwork is name. add mutually_exclusive in AnsibleModule arguments. change disk_type as an option type. * Update gce_instance_template.py change the documentation. * Update gce_instance_template.py RETURN is required for all new modules. Is empty because no new return variable returned. * Update gce_instance_template.py Ansible will verify that only present/absent are passed as the state. This else is not needed.
This commit is contained in:
parent
f0de1e6c85
commit
17a4a655a3
1 changed files with 562 additions and 0 deletions
562
lib/ansible/modules/cloud/google/gce_instance_template.py
Normal file
562
lib/ansible/modules/cloud/google/gce_instance_template.py
Normal file
|
@ -0,0 +1,562 @@
|
|||
#!/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/>.
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: gce_instance_template
|
||||
version_added: "2.3"
|
||||
short_description: create or destroy intance templates of Compute Engine of GCP.
|
||||
description:
|
||||
- Creates or destroy Google instance templates
|
||||
of Compute Engine of Google Cloud Platform.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- The desired state for the instance template.
|
||||
default: "present"
|
||||
choices: ["present", "absent"]
|
||||
name:
|
||||
description:
|
||||
- The name of the GCE instance template.
|
||||
required: true
|
||||
default: null
|
||||
size:
|
||||
description:
|
||||
- The desired machine type for the instance template.
|
||||
default: "f1-micro"
|
||||
source:
|
||||
description:
|
||||
- A source disk to attach to the instance.
|
||||
Cannot specify both I(image) and I(source).
|
||||
default: null
|
||||
image:
|
||||
description:
|
||||
- The image to use to create the instance.
|
||||
Cannot specify both both I(image) and I(source).
|
||||
default: null
|
||||
image_family:
|
||||
description:
|
||||
- The image family to use to create the instance.
|
||||
If I(image) has been used I(image_family) is ignored.
|
||||
Cannot specify both I(image) and I(source).
|
||||
default: null
|
||||
disk_type:
|
||||
description:
|
||||
- Specify a C(pd-standard) disk or C(pd-ssd)
|
||||
for an SSD disk.
|
||||
default: pd-standard
|
||||
disk_auto_delete:
|
||||
description:
|
||||
- Indicate that the boot disk should be
|
||||
deleted when the Node is deleted.
|
||||
default: true
|
||||
network:
|
||||
description:
|
||||
- The network to associate with the instance.
|
||||
default: "default"
|
||||
subnetwork:
|
||||
description:
|
||||
- The Subnetwork resource name for this instance.
|
||||
default: null
|
||||
can_ip_forward:
|
||||
description:
|
||||
- Set to True to allow instance to
|
||||
send/receive non-matching src/dst packets.
|
||||
default: false
|
||||
external_ip:
|
||||
description:
|
||||
- The external IP address to use.
|
||||
If C(ephemeral), a new non-static address will be
|
||||
used. If C(None), then no external address will
|
||||
be used. To use an existing static IP address
|
||||
specify adress name.
|
||||
default: "ephemeral"
|
||||
service_account_email:
|
||||
description:
|
||||
- service account email
|
||||
default: null
|
||||
service_account_permissions:
|
||||
description:
|
||||
- service account permissions (see
|
||||
U(https://cloud.google.com/sdk/gcloud/reference/compute/instances/create),
|
||||
--scopes section for detailed information)
|
||||
default: null
|
||||
choices: [
|
||||
"bigquery", "cloud-platform", "compute-ro", "compute-rw",
|
||||
"useraccounts-ro", "useraccounts-rw", "datastore", "logging-write",
|
||||
"monitoring", "sql-admin", "storage-full", "storage-ro",
|
||||
"storage-rw", "taskqueue", "userinfo-email"
|
||||
]
|
||||
automatic_restart:
|
||||
description:
|
||||
- Defines whether the instance should be
|
||||
automatically restarted when it is
|
||||
terminated by Compute Engine.
|
||||
default: null
|
||||
preemptible:
|
||||
description:
|
||||
- Defines whether the instance is preemptible.
|
||||
default: null
|
||||
tags:
|
||||
description:
|
||||
- a comma-separated list of tags to associate with the instance
|
||||
default: null
|
||||
metadata:
|
||||
description:
|
||||
- a hash/dictionary of custom data for the instance;
|
||||
'{"key":"value", ...}'
|
||||
default: null
|
||||
description:
|
||||
description:
|
||||
- description of instance template
|
||||
default: null
|
||||
disks:
|
||||
description:
|
||||
- a list of persistent disks to attach to the instance; a string value
|
||||
gives the name of the disk; alternatively, a dictionary value can
|
||||
define 'name' and 'mode' ('READ_ONLY' or 'READ_WRITE'). The first entry
|
||||
will be the boot disk (which must be READ_WRITE).
|
||||
default: null
|
||||
nic_gce_struct:
|
||||
description:
|
||||
- Support passing in the GCE-specific
|
||||
formatted networkInterfaces[] structure.
|
||||
default: null
|
||||
project_id:
|
||||
description:
|
||||
- your GCE project ID
|
||||
default: null
|
||||
pem_file:
|
||||
description:
|
||||
- path to the pem file associated with the service account email
|
||||
This option is deprecated. Use 'credentials_file'.
|
||||
default: null
|
||||
credentials_file:
|
||||
description:
|
||||
- path to the JSON file associated with the service account email
|
||||
default: null
|
||||
requirements:
|
||||
- "python >= 2.6"
|
||||
- "apache-libcloud >= 0.13.3, >= 0.17.0 if using JSON credentials,
|
||||
>= 0.20.0 if using preemptible option"
|
||||
notes:
|
||||
- JSON credentials strongly preferred.
|
||||
author: "Gwenael Pellen (@GwenaelPellenArkeup) <gwenael.pellen@arkeup.com>"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Usage
|
||||
- name: create instance template named foo
|
||||
gce_instance_template:
|
||||
name: foo
|
||||
size: n1-standard-1
|
||||
image_family: ubuntu-1604-lts
|
||||
state: present
|
||||
project_id: "your-project-name"
|
||||
credentials_file: "/path/to/your-key.json"
|
||||
service_account_email: "your-sa@your-project-name.iam.gserviceaccount.com"
|
||||
|
||||
# Example Playbook
|
||||
- name: Compute Engine Instance Template Examples
|
||||
hosts: localhost
|
||||
vars:
|
||||
service_account_email: "your-sa@your-project-name.iam.gserviceaccount.com"
|
||||
credentials_file: "/path/to/your-key.json"
|
||||
project_id: "your-project-name"
|
||||
tasks:
|
||||
- name: create instance template
|
||||
gce_instance_template:
|
||||
name: my-test-instance-template
|
||||
size: n1-standard-1
|
||||
image_family: ubuntu-1604-lts
|
||||
state: present
|
||||
project_id: "{{ project_id }}"
|
||||
credentials_file: "{{ credentials_file }}"
|
||||
service_account_email: "{{ service_account_email }}"
|
||||
- name: delete instance template
|
||||
gce_instance_template:
|
||||
name: my-test-instance-template
|
||||
size: n1-standard-1
|
||||
image_family: ubuntu-1604-lts
|
||||
state: absent
|
||||
project_id: "{{ project_id }}"
|
||||
credentials_file: "{{ credentials_file }}"
|
||||
service_account_email: "{{ service_account_email }}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.gce import gce_connect
|
||||
|
||||
try:
|
||||
import libcloud
|
||||
from libcloud.compute.types import Provider
|
||||
from libcloud.compute.providers import get_driver
|
||||
from libcloud.common.google import GoogleBaseError, QuotaExceededError, \
|
||||
ResourceExistsError, ResourceInUseError, ResourceNotFoundError
|
||||
from libcloud.compute.drivers.gce import GCEAddress
|
||||
_ = Provider.GCE
|
||||
HAS_LIBCLOUD = True
|
||||
except ImportError:
|
||||
HAS_LIBCLOUD = False
|
||||
|
||||
try:
|
||||
from ast import literal_eval
|
||||
HAS_PYTHON26 = True
|
||||
except ImportError:
|
||||
HAS_PYTHON26 = False
|
||||
|
||||
|
||||
def get_info(inst):
|
||||
"""Retrieves instance template information
|
||||
|
||||
"""
|
||||
return({
|
||||
'name': inst.name,
|
||||
'extra': inst.extra,
|
||||
})
|
||||
|
||||
|
||||
def create_instance_template(module, gce):
|
||||
"""Create an instance template
|
||||
module : AnsibleModule object
|
||||
gce: authenticated GCE libcloud driver
|
||||
Returns:
|
||||
instance template information
|
||||
"""
|
||||
# get info from module
|
||||
name = module.params.get('name')
|
||||
size = module.params.get('size')
|
||||
source = module.params.get('source')
|
||||
image = module.params.get('image')
|
||||
image_family = module.params.get('image_family')
|
||||
disk_type = module.params.get('disk_type')
|
||||
disk_auto_delete = module.params.get('disk_auto_delete')
|
||||
network = module.params.get('network')
|
||||
subnetwork = module.params.get('subnetwork')
|
||||
can_ip_forward = module.params.get('can_ip_forward')
|
||||
external_ip = module.params.get('external_ip')
|
||||
service_account_email = module.params.get('service_account_email')
|
||||
service_account_permissions = module.params.get(
|
||||
'service_account_permissions')
|
||||
on_host_maintenance = module.params.get('on_host_maintenance')
|
||||
automatic_restart = module.params.get('automatic_restart')
|
||||
preemptible = module.params.get('preemptible')
|
||||
tags = module.params.get('tags')
|
||||
metadata = module.params.get('metadata')
|
||||
description = module.params.get('description')
|
||||
disks = module.params.get('disks')
|
||||
|
||||
changed = False
|
||||
|
||||
# args of ex_create_instancetemplate
|
||||
gce_args = dict(
|
||||
name="instance",
|
||||
size="f1-micro",
|
||||
source=None,
|
||||
image=None,
|
||||
disk_type='pd-standard',
|
||||
disk_auto_delete=True,
|
||||
network='default',
|
||||
subnetwork=None,
|
||||
can_ip_forward=None,
|
||||
external_ip='ephemeral',
|
||||
service_accounts=None,
|
||||
on_host_maintenance=None,
|
||||
automatic_restart=None,
|
||||
preemptible=None,
|
||||
tags=None,
|
||||
metadata=None,
|
||||
description=None,
|
||||
disks_gce_struct=None,
|
||||
nic_gce_struct=None
|
||||
)
|
||||
|
||||
gce_args['name'] = name
|
||||
gce_args['size'] = size
|
||||
|
||||
if source is not None:
|
||||
gce_args['source'] = source
|
||||
|
||||
if image:
|
||||
gce_args['image'] = image
|
||||
else:
|
||||
if image_family:
|
||||
image = gce.ex_get_image_from_family(image_family)
|
||||
gce_args['image'] = image
|
||||
else:
|
||||
gce_args['image'] = "debian-8"
|
||||
|
||||
gce_args['disk_type'] = disk_type
|
||||
gce_args['disk_auto_delete'] = disk_auto_delete
|
||||
|
||||
gce_network = gce.ex_get_network(network)
|
||||
gce_args['network'] = gce_network
|
||||
|
||||
if subnetwork is not None:
|
||||
gce_args['subnetwork'] = subnetwork
|
||||
|
||||
if can_ip_forward is not None:
|
||||
gce_args['can_ip_forward'] = can_ip_forward
|
||||
|
||||
if external_ip == "ephemeral":
|
||||
instance_external_ip = external_ip
|
||||
elif external_ip == "none":
|
||||
instance_external_ip = None
|
||||
else:
|
||||
try:
|
||||
instance_external_ip = gce.ex_get_address(external_ip)
|
||||
except GoogleBaseError as err:
|
||||
# external_ip is name ?
|
||||
instance_external_ip = external_ip
|
||||
gce_args['external_ip'] = instance_external_ip
|
||||
|
||||
ex_sa_perms = []
|
||||
bad_perms = []
|
||||
if service_account_permissions:
|
||||
for perm in service_account_permissions:
|
||||
if perm not in gce.SA_SCOPES_MAP:
|
||||
bad_perms.append(perm)
|
||||
if len(bad_perms) > 0:
|
||||
module.fail_json(msg='bad permissions: %s' % str(bad_perms))
|
||||
ex_sa_perms.append({'email': "default"})
|
||||
ex_sa_perms[0]['scopes'] = service_account_permissions
|
||||
gce_args['service_accounts'] = ex_sa_perms
|
||||
|
||||
if on_host_maintenance is not None:
|
||||
gce_args['on_host_maintenance'] = on_host_maintenance
|
||||
|
||||
if automatic_restart is not None:
|
||||
gce_args['automatic_restart'] = automatic_restart
|
||||
|
||||
if preemptible is not None:
|
||||
gce_args['preemptible'] = preemptible
|
||||
|
||||
if tags is not None:
|
||||
gce_args['tags'] = tags
|
||||
|
||||
# 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:
|
||||
if isinstance(metadata, dict):
|
||||
md = metadata
|
||||
else:
|
||||
try:
|
||||
md = literal_eval(str(metadata))
|
||||
if not isinstance(md, dict):
|
||||
raise ValueError('metadata must be a dict')
|
||||
except ValueError as e:
|
||||
module.fail_json(msg='bad metadata: %s' % str(e))
|
||||
except SyntaxError as e:
|
||||
module.fail_json(msg='bad metadata syntax')
|
||||
|
||||
if hasattr(libcloud, '__version__') and libcloud.__version__ < '0.15':
|
||||
items = []
|
||||
for k, v in md.items():
|
||||
items.append({"key": k, "value": v})
|
||||
metadata = {'items': items}
|
||||
else:
|
||||
metadata = md
|
||||
gce_args['metadata'] = metadata
|
||||
|
||||
if description is not None:
|
||||
gce_args['description'] = description
|
||||
|
||||
instance = None
|
||||
try:
|
||||
instance = gce.ex_get_instancetemplate(name)
|
||||
except ResourceNotFoundError:
|
||||
try:
|
||||
instance = gce.ex_create_instancetemplate(**gce_args)
|
||||
changed = True
|
||||
except GoogleBaseError as err:
|
||||
module.fail_json(
|
||||
msg='Unexpected error attempting to create instance {}, error: {}'
|
||||
.format(
|
||||
instance,
|
||||
err.value
|
||||
)
|
||||
)
|
||||
|
||||
if instance:
|
||||
json_data = get_info(instance)
|
||||
else:
|
||||
module.fail_json(msg="no instance template!")
|
||||
|
||||
return (changed, json_data, name)
|
||||
|
||||
|
||||
def delete_instance_template(module, gce):
|
||||
""" Delete instance template.
|
||||
module : AnsibleModule object
|
||||
gce: authenticated GCE libcloud driver
|
||||
Returns:
|
||||
instance template information
|
||||
"""
|
||||
name = module.params.get('name')
|
||||
current_state = "absent"
|
||||
changed = False
|
||||
|
||||
# get instance template
|
||||
instance = None
|
||||
try:
|
||||
instance = gce.ex_get_instancetemplate(name)
|
||||
current_state = "present"
|
||||
except GoogleBaseError as err:
|
||||
json_data = dict(msg='instance template not exists')
|
||||
|
||||
if current_state == "present":
|
||||
rc = instance.destroy()
|
||||
if rc:
|
||||
changed = True
|
||||
else:
|
||||
module.fail_json(
|
||||
msg='instance template destroy failed'
|
||||
)
|
||||
|
||||
json_data = {}
|
||||
return (changed, json_data, name)
|
||||
|
||||
|
||||
def module_controller(module, gce):
|
||||
''' Control module state parameter.
|
||||
module : AnsibleModule object
|
||||
gce: authenticated GCE libcloud driver
|
||||
Returns:
|
||||
nothing
|
||||
Exit:
|
||||
AnsibleModule object exit with json data.
|
||||
'''
|
||||
json_output = dict()
|
||||
state = module.params.get("state")
|
||||
if state == "present":
|
||||
(changed, output, name) = create_instance_template(module, gce)
|
||||
json_output['changed'] = changed
|
||||
json_output['msg'] = output
|
||||
elif state == "absent":
|
||||
(changed, output, name) = delete_instance_template(module, gce)
|
||||
json_output['changed'] = changed
|
||||
json_output['msg'] = output
|
||||
|
||||
module.exit_json(**json_output)
|
||||
|
||||
|
||||
def check_if_system_state_would_be_changed(module, gce):
|
||||
''' check_if_system_state_would_be_changed !
|
||||
module : AnsibleModule object
|
||||
gce: authenticated GCE libcloud driver
|
||||
Returns:
|
||||
system_state changed
|
||||
'''
|
||||
changed = False
|
||||
current_state = "absent"
|
||||
|
||||
state = module.params.get("state")
|
||||
name = module.params.get("name")
|
||||
|
||||
instance = None
|
||||
try:
|
||||
instance = gce.ex_get_instancetemplate(name)
|
||||
current_state = "present"
|
||||
except GoogleBaseError as err:
|
||||
module.fail_json(msg='GCE get instancetemplate problem')
|
||||
|
||||
if current_state != state:
|
||||
changed = True
|
||||
|
||||
if current_state == "absent":
|
||||
if changed:
|
||||
output = 'instance template {} will be created'.format(name)
|
||||
else:
|
||||
output = 'nothing to do for instance template {} '.format(name)
|
||||
if current_state == "present":
|
||||
if changed:
|
||||
output = 'instance template {} will be detroyed'.format(name)
|
||||
else:
|
||||
output = 'nothing to do for instance template {} '.format(name)
|
||||
|
||||
return (changed, output)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
name=dict(require=True, aliases=['base_name']),
|
||||
size=dict(default='f1-micro'),
|
||||
source=dict(),
|
||||
image=dict(),
|
||||
image_family=dict(default='debian-8'),
|
||||
disk_type=dict(choices=['pd-standard', 'pd-ssd'], default='pd-standard', type='str'),
|
||||
disk_auto_delete=dict(type='bool', default=True),
|
||||
network=dict(default='default'),
|
||||
subnetwork=dict(),
|
||||
can_ip_forward=dict(type='bool', default=False),
|
||||
external_ip=dict(default='ephemeral'),
|
||||
service_account_email=dict(),
|
||||
service_account_permissions=dict(type='list'),
|
||||
automatic_restart=dict(type='bool', default=None),
|
||||
preemptible=dict(type='bool', default=None),
|
||||
tags=dict(type='list'),
|
||||
metadata=dict(),
|
||||
description=dict(),
|
||||
disks=dict(type='list'),
|
||||
nic_gce_struct=dict(type='list'),
|
||||
project_id=dict(),
|
||||
pem_file=dict(type='path'),
|
||||
credentials_file=dict(type='path'),
|
||||
),
|
||||
mutually_exclusive=[['source', 'image']],
|
||||
required_one_of=[['image', 'image_family']],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not HAS_PYTHON26:
|
||||
module.fail_json(
|
||||
msg="GCE module requires python's 'ast' module, python v2.6+")
|
||||
if not HAS_LIBCLOUD:
|
||||
module.fail_json(
|
||||
msg='libcloud with GCE support (0.17.0+) required for this module')
|
||||
|
||||
try:
|
||||
gce = gce_connect(module)
|
||||
except GoogleBaseError as err:
|
||||
module.fail_json(msg='GCE Connexion failed')
|
||||
|
||||
if module.check_mode:
|
||||
(changed, output) = check_if_system_state_would_be_changed(module, gce)
|
||||
module.exit_json(
|
||||
changed=changed,
|
||||
msg=output
|
||||
)
|
||||
else:
|
||||
module_controller(module, gce)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in a new issue