From bb6d983290e030502bd407ba800ba0eb2f60209c Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Thu, 26 Mar 2015 10:26:33 +0100 Subject: [PATCH 1/4] cloudstack: add utils for common functionality --- lib/ansible/module_utils/cloudstack.py | 182 +++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 lib/ansible/module_utils/cloudstack.py diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py new file mode 100644 index 00000000000..cb482ae9932 --- /dev/null +++ b/lib/ansible/module_utils/cloudstack.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- +# +# (c) 2015, René Moser +# +# This code is part of Ansible, but is an independent component. +# +# 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 . + +import sys + +try: + from cs import CloudStack, CloudStackException, read_config +except ImportError: + print("failed=True " + \ + "msg='python library cs required: pip install cs'") + sys.exit(1) + + +class AnsibleCloudStack: + + def __init__(self, module): + self.module = module + self._connect() + + self.project_id = None + self.ip_address_id = None + self.zone_id = None + self.vm_id = None + self.os_type_id = None + self.hypervisor = None + + + def _connect(self): + api_key = self.module.params.get('api_key') + api_secret = self.module.params.get('secret_key') + api_url = self.module.params.get('api_url') + api_http_method = self.module.params.get('api_http_method') + + if api_key and api_secret and api_url: + self.cs = CloudStack( + endpoint=api_url, + key=api_key, + secret=api_secret, + method=api_http_method + ) + else: + self.cs = CloudStack(**read_config()) + + + def get_project_id(self): + if self.project_id: + return self.project_id + + project = self.module.params.get('project') + if not project: + return None + + projects = self.cs.listProjects() + if projects: + for p in projects['project']: + if project in [ p['name'], p['displaytext'], p['id'] ]: + self.project_id = p['id'] + return self.project_id + self.module.fail_json(msg="project '%s' not found" % project) + + + def get_ip_address_id(self): + if self.ip_address_id: + return self.ip_address_id + + ip_address = self.module.params.get('ip_address') + if not ip_address: + self.module.fail_json(msg="IP address param 'ip_address' is required") + + args = {} + args['ipaddress'] = ip_address + args['projectid'] = self.get_project_id() + ip_addresses = self.cs.listPublicIpAddresses(**args) + + if not ip_addresses: + self.module.fail_json(msg="IP address '%s' not found" % args['ipaddress']) + + self.ip_address_id = ip_addresses['publicipaddress'][0]['id'] + return self.ip_address_id + + + def get_vm_id(self): + if self.vm_id: + return self.vm_id + + vm = self.module.params.get('vm') + if not vm: + self.module.fail_json(msg="Virtual machine param 'vm' is required") + + args = {} + args['projectid'] = self.get_project_id() + vms = self.cs.listVirtualMachines(**args) + if vms: + for v in vms['virtualmachine']: + if vm in [ v['name'], v['id'] ]: + self.vm_id = v['id'] + return self.vm_id + self.module.fail_json(msg="Virtual machine '%s' not found" % vm) + + + def get_zone_id(self): + if self.zone_id: + return self.zone_id + + zone = self.module.params.get('zone') + zones = self.cs.listZones() + + # use the first zone if no zone param given + if not zone: + self.zone_id = zones['zone'][0]['id'] + return self.zone_id + + if zones: + for z in zones['zone']: + if zone in [ z['name'], z['id'] ]: + self.zone_id = z['id'] + return self.zone_id + self.module.fail_json(msg="zone '%s' not found" % zone) + + + def get_os_type_id(self): + if self.os_type_id: + return self.os_type_id + + os_type = self.module.params.get('os_type') + if not os_type: + return None + + os_types = self.cs.listOsTypes() + if os_types: + for o in os_types['ostype']: + if os_type in [ o['description'], o['id'] ]: + self.os_type_id = o['id'] + return self.os_type_id + self.module.fail_json(msg="OS type '%s' not found" % os_type) + + + def get_hypervisor(self): + if self.hypervisor: + return self.hypervisor + + hypervisor = self.module.params.get('hypervisor') + hypervisors = self.cs.listHypervisors() + + # use the first hypervisor if no hypervisor param given + if not hypervisor: + self.hypervisor = hypervisors['hypervisor'][0]['name'] + return self.hypervisor + + for h in hypervisors['hypervisor']: + if hypervisor.lower() == h['name'].lower(): + self.hypervisor = h['name'] + return self.hypervisor + self.module.fail_json(msg="Hypervisor '%s' not found" % hypervisor) + + + def _poll_job(self, job=None, key=None): + if 'jobid' in job: + while True: + res = self.cs.queryAsyncJobResult(jobid=job['jobid']) + if res['jobstatus'] != 0: + if 'jobresult' in res and key is not None and key in res['jobresult']: + job = res['jobresult'][key] + break + time.sleep(2) + return job From 1ba05dd3a298ccc0a377f718046dc80aeaea5860 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Thu, 26 Mar 2015 14:10:18 +0100 Subject: [PATCH 2/4] cloudstack: add doc fragment --- .../utils/module_docs_fragments/cloudstack.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 lib/ansible/utils/module_docs_fragments/cloudstack.py diff --git a/lib/ansible/utils/module_docs_fragments/cloudstack.py b/lib/ansible/utils/module_docs_fragments/cloudstack.py new file mode 100644 index 00000000000..8d173ea756f --- /dev/null +++ b/lib/ansible/utils/module_docs_fragments/cloudstack.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015 René Moser +# +# 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 . + + +class ModuleDocFragment(object): + + # Standard cloudstack documentation fragment + DOCUMENTATION = ''' +options: + api_key: + description: + - API key of the CloudStack API. + required: false + default: null + aliases: [] + api_secret: + description: + - Secret key of the CloudStack API. + required: false + default: null + aliases: [] + api_url: + description: + - URL of the CloudStack API e.g. https://cloud.example.com/client/api. + required: false + default: null + aliases: [] + api_http_method: + description: + - HTTP method used. + required: false + default: 'get' + aliases: [] +requirements: + - cs +notes: + - Ansible uses the C(cs) library's configuration method if credentials are not + provided by the options C(api_url), C(api_key), C(api_secret). + Configuration is read from several locations, in the following order: + - The C(CLOUDSTACK_ENDPOINT), C(CLOUDSTACK_KEY), C(CLOUDSTACK_SECRET) and + C(CLOUDSTACK_METHOD) environment variables. + - A C(CLOUDSTACK_CONFIG) environment variable pointing to an C(.ini) file, + - A C(cloudstack.ini) file in the current working directory. + - A C(.cloudstack.ini) file in the users home directory. + See https://github.com/exoscale/cs for more information. + - This module supports check mode. +''' From c066a60b7c48c9a31b51834d49bccfd0b00dd2e5 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Thu, 26 Mar 2015 15:32:58 +0100 Subject: [PATCH 3/4] cloudstack: fail_json() if library cs is not found --- lib/ansible/module_utils/cloudstack.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index cb482ae9932..ab72f2c7894 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -17,19 +17,20 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . -import sys try: from cs import CloudStack, CloudStackException, read_config + has_lib_cs = True except ImportError: - print("failed=True " + \ - "msg='python library cs required: pip install cs'") - sys.exit(1) + has_lib_cs = False class AnsibleCloudStack: def __init__(self, module): + if not has_lib_cs: + module.fail_json(msg="python library cs required: pip install cs") + self.module = module self._connect() From 3e7d959c9d398d5cbe02b72d4717d86cc45b310a Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Thu, 26 Mar 2015 15:39:02 +0100 Subject: [PATCH 4/4] cloudstack: module utils are BSD licensed --- lib/ansible/module_utils/cloudstack.py | 30 +++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index ab72f2c7894..f72d270d30b 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -3,19 +3,29 @@ # (c) 2015, René Moser # # This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. # -# 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. +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: # -# 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. +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. try: