From d0382bda002e5862b2a4ce1035b126d0676eba57 Mon Sep 17 00:00:00 2001 From: Peter Tan Date: Tue, 27 Jan 2015 16:22:46 -0800 Subject: [PATCH 1/3] Add gce_img module for utilizing GCE image resources --- cloud/google/gce_img.py | 174 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 cloud/google/gce_img.py diff --git a/cloud/google/gce_img.py b/cloud/google/gce_img.py new file mode 100644 index 00000000000..460a55b41ad --- /dev/null +++ b/cloud/google/gce_img.py @@ -0,0 +1,174 @@ +#!/usr/bin/python +# Copyright 2015 Google Inc. All Rights Reserved. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +"""An Ansible module to utilize GCE image resources.""" + +import sys + +# import module snippets +from ansible.module_utils.basic import * +from ansible.module_utils.gce import * + +try: + from libcloud.compute.types import Provider + from libcloud.compute.providers import get_driver + from libcloud.common.google import GoogleBaseError + from libcloud.common.google import ResourceNotFoundError + _ = Provider.GCE +except ImportError: + print('failed=True ' + "msg='libcloud with GCE support is required for this module.'") + sys.exit(1) + +DOCUMENTATION = ''' +--- +module: gce_img +short_description: utilize GCE image resources +description: + - This module can create and delete GCE private images from gzipped + compressed tarball containing raw disk data or from existing detached + disks in any zone. U(https://cloud.google.com/compute/docs/images) +options: + name: + description: + - the name of the image to create + required: true + default: null + aliases: [] + source: + description: + - the source disk or the Google Cloud Storage URI to create the image from + required: false + default: null + aliases: [] + state: + description: + - desired state of the image + required: false + default: "present" + choices: ["active", "present", "absent", "deleted"] + aliases: [] + zone: + description: + - the zone of the disk specified by source + required: false + default: "us-central1-a" + aliases: [] + service_account_email: + version_added: "1.6" + description: + - service account email + required: false + default: null + aliases: [] + pem_file: + version_added: "1.6" + description: + - path to the pem file associated with the service account email + required: false + default: null + aliases: [] + project_id: + version_added: "1.6" + description: + - your GCE project ID + required: false + default: null + aliases: [] + +requirements: [ "libcloud" ] +author: Peter Tan +''' + +EXAMPLES = ''' +# Create an image named test-image from the disk 'test-disk' in zone us-central1-a. +- gce_img: + name: test-image + source: test-disk + zone: us-central1-a + state: present + +# Delete an image named test-image in zone us-central1-a. +- gce_img: + name: test-image + zone: us-central1-a + state: deleted +''' + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True), + source=dict(), + state=dict(default='present'), + zone=dict(default='us-central1-a'), + service_account_email=dict(), + pem_file=dict(), + project_id=dict(), + ) + ) + + gce = gce_connect(module) + + name = module.params.get('name') + source = module.params.get('source') + state = module.params.get('state') + zone = module.params.get('zone') + changed = False + + try: + image = gce.ex_get_image(name) + except GoogleBaseError, e: + module.fail_json(msg=str(e), changed=False) + + # user wants to create an image. + if state in ['active', 'present'] and not image: + if not source: + module.fail_json(msg='Must supply a source', changed=False) + + if source.startswith('https://storage.googleapis.com'): + # source is a Google Cloud Storage URI + volume = source + else: + try: + volume = gce.ex_get_volume(source, zone) + except ResourceNotFoundError: + module.fail_json(msg='Disk %s not found in zone %s' % (source, zone), + changed=False) + except GoogleBaseError, e: + module.fail_json(msg=str(e), changed=False) + + try: + image = gce.ex_create_image(name, volume) + changed = True + except GoogleBaseError, e: + module.fail_json(msg=str(e), changed=False) + + # user wants to delete the image. + if state in ['absent', 'deleted'] and image: + try: + gce.ex_delete_image(image) + changed = True + except GoogleBaseError, e: + module.fail_json(msg=str(e), changed=False) + + module.exit_json(changed=changed, name=name) + sys.exit(0) + +main() From 5ab2dcf76acf989ff886324fd329c0635a2e7e75 Mon Sep 17 00:00:00 2001 From: Peter Tan Date: Wed, 28 Jan 2015 11:19:06 -0800 Subject: [PATCH 2/3] Address review comments from @sivel and @erjohnso --- cloud/google/gce_img.py | 141 +++++++++++++++++++++++----------------- 1 file changed, 82 insertions(+), 59 deletions(-) diff --git a/cloud/google/gce_img.py b/cloud/google/gce_img.py index 460a55b41ad..22f9237ec90 100644 --- a/cloud/google/gce_img.py +++ b/cloud/google/gce_img.py @@ -18,26 +18,10 @@ """An Ansible module to utilize GCE image resources.""" -import sys - -# import module snippets -from ansible.module_utils.basic import * -from ansible.module_utils.gce import * - -try: - from libcloud.compute.types import Provider - from libcloud.compute.providers import get_driver - from libcloud.common.google import GoogleBaseError - from libcloud.common.google import ResourceNotFoundError - _ = Provider.GCE -except ImportError: - print('failed=True ' - "msg='libcloud with GCE support is required for this module.'") - sys.exit(1) - DOCUMENTATION = ''' --- module: gce_img +version_added: "1.9" short_description: utilize GCE image resources description: - This module can create and delete GCE private images from gzipped @@ -46,7 +30,7 @@ description: options: name: description: - - the name of the image to create + - the name of the image to create or delete required: true default: null aliases: [] @@ -61,7 +45,7 @@ options: - desired state of the image required: false default: "present" - choices: ["active", "present", "absent", "deleted"] + choices: ["present", "absent"] aliases: [] zone: description: @@ -70,21 +54,18 @@ options: default: "us-central1-a" aliases: [] service_account_email: - version_added: "1.6" description: - service account email required: false default: null aliases: [] pem_file: - version_added: "1.6" description: - path to the pem file associated with the service account email required: false default: null aliases: [] project_id: - version_added: "1.6" description: - your GCE project ID required: false @@ -103,20 +84,81 @@ EXAMPLES = ''' zone: us-central1-a state: present -# Delete an image named test-image in zone us-central1-a. +# Create an image named test-image from a tarball in Google Cloud Storage. - gce_img: name: test-image - zone: us-central1-a - state: deleted + source: https://storage.googleapis.com/bucket/path/to/image.tgz + +# Alternatively use the gs scheme +- gce_img: + name: test-image + source: gs://bucket/path/to/image.tgz + +# Delete an image named test-image. +- gce_img: + name: test-image + state: absent ''' +import sys + +try: + from libcloud.compute.types import Provider + from libcloud.compute.providers import get_driver + from libcloud.common.google import GoogleBaseError + from libcloud.common.google import ResourceNotFoundError + _ = Provider.GCE + has_libcloud = True +except ImportError: + has_libcloud = False + + +GCS_URI = 'https://storage.googleapis.com/' + + +def create_image(gce, name, module): + """Create an image with the specified name.""" + source = module.params.get('source') + zone = module.params.get('zone') + + if not source: + module.fail_json(msg='Must supply a source', changed=False) + + if source.startswith(GCS_URI): + # source is a Google Cloud Storage URI + volume = source + elif source.startswith('gs://'): + # libcloud only accepts https URI. + volume = source.replace('gs://', GCS_URI) + else: + try: + volume = gce.ex_get_volume(source, zone) + except ResourceNotFoundError: + module.fail_json(msg='Disk %s not found in zone %s' % (source, zone), + changed=False) + except GoogleBaseError, e: + module.fail_json(msg=str(e), changed=False) + + try: + gce.ex_create_image(name, volume) + except GoogleBaseError, e: + module.fail_json(msg=str(e), changed=False) + + +def delete_image(gce, image, module): + """Delete a specific image resource.""" + try: + gce.ex_delete_image(image) + except GoogleBaseError, e: + module.fail_json(msg=str(e), changed=False) + def main(): module = AnsibleModule( argument_spec=dict( name=dict(required=True), source=dict(), - state=dict(default='present'), + state=dict(default='present', choices=['present', 'absent']), zone=dict(default='us-central1-a'), service_account_email=dict(), pem_file=dict(), @@ -124,51 +166,32 @@ def main(): ) ) + if not has_libcloud: + module.fail_json(msg='libcloud with GCE support is required.') + gce = gce_connect(module) name = module.params.get('name') - source = module.params.get('source') state = module.params.get('state') - zone = module.params.get('zone') changed = False - try: - image = gce.ex_get_image(name) - except GoogleBaseError, e: - module.fail_json(msg=str(e), changed=False) + image = gce.ex_get_image(name) # user wants to create an image. - if state in ['active', 'present'] and not image: - if not source: - module.fail_json(msg='Must supply a source', changed=False) - - if source.startswith('https://storage.googleapis.com'): - # source is a Google Cloud Storage URI - volume = source - else: - try: - volume = gce.ex_get_volume(source, zone) - except ResourceNotFoundError: - module.fail_json(msg='Disk %s not found in zone %s' % (source, zone), - changed=False) - except GoogleBaseError, e: - module.fail_json(msg=str(e), changed=False) - - try: - image = gce.ex_create_image(name, volume) - changed = True - except GoogleBaseError, e: - module.fail_json(msg=str(e), changed=False) + if state == 'present' and not image: + create_image(gce, name, module) + changed = True # user wants to delete the image. - if state in ['absent', 'deleted'] and image: - try: - gce.ex_delete_image(image) - changed = True - except GoogleBaseError, e: - module.fail_json(msg=str(e), changed=False) + if state == 'absent' and image: + delete_image(gce, image, module) + changed = True module.exit_json(changed=changed, name=name) sys.exit(0) +# import module snippets +from ansible.module_utils.basic import * +from ansible.module_utils.gce import * + main() From 9b64cf6797362796080ded0d89b7eb3dc117d061 Mon Sep 17 00:00:00 2001 From: Peter Tan Date: Fri, 30 Jan 2015 00:47:47 -0800 Subject: [PATCH 3/3] Do not check for the image before calling gce.ex_create_image() or gce.ex_delete_image(), instead catching the ResourceExistsError or ResourceNotFoundError respectively. --- cloud/google/gce_img.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/cloud/google/gce_img.py b/cloud/google/gce_img.py index 22f9237ec90..3b2351b3752 100644 --- a/cloud/google/gce_img.py +++ b/cloud/google/gce_img.py @@ -34,6 +34,12 @@ options: required: true default: null aliases: [] + description: + description: + - an optional description + required: false + default: null + aliases: [] source: description: - the source disk or the Google Cloud Storage URI to create the image from @@ -106,6 +112,7 @@ try: from libcloud.compute.types import Provider from libcloud.compute.providers import get_driver from libcloud.common.google import GoogleBaseError + from libcloud.common.google import ResourceExistsError from libcloud.common.google import ResourceNotFoundError _ = Provider.GCE has_libcloud = True @@ -120,6 +127,7 @@ def create_image(gce, name, module): """Create an image with the specified name.""" source = module.params.get('source') zone = module.params.get('zone') + desc = module.params.get('description') if not source: module.fail_json(msg='Must supply a source', changed=False) @@ -140,15 +148,21 @@ def create_image(gce, name, module): module.fail_json(msg=str(e), changed=False) try: - gce.ex_create_image(name, volume) + gce.ex_create_image(name, volume, desc, False) + return True + except ResourceExistsError: + return False except GoogleBaseError, e: module.fail_json(msg=str(e), changed=False) -def delete_image(gce, image, module): - """Delete a specific image resource.""" +def delete_image(gce, name, module): + """Delete a specific image resource by name.""" try: - gce.ex_delete_image(image) + gce.ex_delete_image(name) + return True + except ResourceNotFoundError: + return False except GoogleBaseError, e: module.fail_json(msg=str(e), changed=False) @@ -157,6 +171,7 @@ def main(): module = AnsibleModule( argument_spec=dict( name=dict(required=True), + description=dict(), source=dict(), state=dict(default='present', choices=['present', 'absent']), zone=dict(default='us-central1-a'), @@ -175,17 +190,13 @@ def main(): state = module.params.get('state') changed = False - image = gce.ex_get_image(name) - # user wants to create an image. - if state == 'present' and not image: - create_image(gce, name, module) - changed = True + if state == 'present': + changed = create_image(gce, name, module) # user wants to delete the image. - if state == 'absent' and image: - delete_image(gce, image, module) - changed = True + if state == 'absent': + changed = delete_image(gce, name, module) module.exit_json(changed=changed, name=name) sys.exit(0)