# -*- coding: utf-8 -*- # # (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. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # * 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. # # 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: from cs import CloudStack, CloudStackException, read_config has_lib_cs = True except ImportError: 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.result = { 'changed': False, } self.module = module self._connect() self.domain = None self.account = None self.project = None self.ip_address = None self.zone = None self.vm = None self.os_type = None self.hypervisor = None self.capabilities = 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') api_timeout = self.module.params.get('api_timeout') if api_key and api_secret and api_url: self.cs = CloudStack( endpoint=api_url, key=api_key, secret=api_secret, timeout=api_timeout, method=api_http_method ) else: self.cs = CloudStack(**read_config()) # TODO: for backward compatibility only, remove if not used anymore def _has_changed(self, want_dict, current_dict, only_keys=None): return self.has_changed(want_dict=want_dict, current_dict=current_dict, only_keys=only_keys) def has_changed(self, want_dict, current_dict, only_keys=None): for key, value in want_dict.iteritems(): # Optionally limit by a list of keys if only_keys and key not in only_keys: continue; # Skip None values if value is None: continue; if key in current_dict: # API returns string for int in some cases, just to make sure if isinstance(value, int): current_dict[key] = int(current_dict[key]) elif isinstance(value, str): current_dict[key] = str(current_dict[key]) # Only need to detect a singe change, not every item if value != current_dict[key]: return True return False def _get_by_key(self, key=None, my_dict={}): if key: if key in my_dict: return my_dict[key] self.module.fail_json(msg="Something went wrong: %s not found" % key) return my_dict def get_project(self, key=None): if self.project: return self._get_by_key(key, self.project) project = self.module.params.get('project') if not project: return None args = {} args['account'] = self.get_account(key='name') args['domainid'] = self.get_domain(key='id') projects = self.cs.listProjects(**args) if projects: for p in projects['project']: if project.lower() in [ p['name'].lower(), p['id'] ]: self.project = p return self._get_by_key(key, self.project) self.module.fail_json(msg="project '%s' not found" % project) def get_ip_address(self, key=None): if self.ip_address: return self._get_by_key(key, self.ip_address) 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['account'] = self.get_account(key='name') args['domainid'] = self.get_domain(key='id') args['projectid'] = self.get_project(key='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 = ip_addresses['publicipaddress'][0] return self._get_by_key(key, self.ip_address) def get_vm(self, key=None): if self.vm: return self._get_by_key(key, self.vm) vm = self.module.params.get('vm') if not vm: self.module.fail_json(msg="Virtual machine param 'vm' is required") args = {} args['account'] = self.get_account(key='name') args['domainid'] = self.get_domain(key='id') args['projectid'] = self.get_project(key='id') args['zoneid'] = self.get_zone(key='id') vms = self.cs.listVirtualMachines(**args) if vms: for v in vms['virtualmachine']: if vm in [ v['name'], v['displayname'], v['id'] ]: self.vm = v return self._get_by_key(key, self.vm) self.module.fail_json(msg="Virtual machine '%s' not found" % vm) def get_zone(self, key=None): if self.zone: return self._get_by_key(key, self.zone) zone = self.module.params.get('zone') zones = self.cs.listZones() # use the first zone if no zone param given if not zone: self.zone = zones['zone'][0] return self._get_by_key(key, self.zone) if zones: for z in zones['zone']: if zone in [ z['name'], z['id'] ]: self.zone = z return self._get_by_key(key, self.zone) self.module.fail_json(msg="zone '%s' not found" % zone) def get_os_type(self, key=None): if self.os_type: return self._get_by_key(key, self.zone) 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 = o return self._get_by_key(key, self.os_type) 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 get_account(self, key=None): if self.account: return self._get_by_key(key, self.account) account = self.module.params.get('account') if not account: return None domain = self.module.params.get('domain') if not domain: self.module.fail_json(msg="Account must be specified with Domain") args = {} args['name'] = account args['domainid'] = self.get_domain(key='id') args['listall'] = True accounts = self.cs.listAccounts(**args) if accounts: self.account = accounts['account'][0] return self._get_by_key(key, self.account) self.module.fail_json(msg="Account '%s' not found" % account) def get_domain(self, key=None): if self.domain: return self._get_by_key(key, self.domain) domain = self.module.params.get('domain') if not domain: return None args = {} args['name'] = domain args['listall'] = True domains = self.cs.listDomains(**args) if domains: self.domain = domains['domain'][0] return self._get_by_key(key, self.domain) self.module.fail_json(msg="Domain '%s' not found" % domain) def get_tags(self, resource=None): existing_tags = self.cs.listTags(resourceid=resource['id']) if existing_tags: return existing_tags['tag'] return [] def _delete_tags(self, resource, resource_type, tags): existing_tags = resource['tags'] tags_to_delete = [] for existing_tag in existing_tags: if existing_tag['key'] in tags: if existing_tag['value'] != tags[key]: tags_to_delete.append(existing_tag) else: tags_to_delete.append(existing_tag) if tags_to_delete: self.result['changed'] = True if not self.module.check_mode: args = {} args['resourceids'] = resource['id'] args['resourcetype'] = resource_type args['tags'] = tags_to_delete self.cs.deleteTags(**args) def _create_tags(self, resource, resource_type, tags): tags_to_create = [] for i, tag_entry in enumerate(tags): tag = { 'key': tag_entry['key'], 'value': tag_entry['value'], } tags_to_create.append(tag) if tags_to_create: self.result['changed'] = True if not self.module.check_mode: args = {} args['resourceids'] = resource['id'] args['resourcetype'] = resource_type args['tags'] = tags_to_create self.cs.createTags(**args) def ensure_tags(self, resource, resource_type=None): if not resource_type or not resource: self.module.fail_json(msg="Error: Missing resource or resource_type for tags.") if 'tags' in resource: tags = self.module.params.get('tags') if tags is not None: self._delete_tags(resource, resource_type, tags) self._create_tags(resource, resource_type, tags) resource['tags'] = self.get_tags(resource) return resource def get_capabilities(self, key=None): if self.capabilities: return self._get_by_key(key, self.capabilities) capabilities = self.cs.listCapabilities() self.capabilities = capabilities['capability'] return self._get_by_key(key, self.capabilities) # TODO: for backward compatibility only, remove if not used anymore def _poll_job(self, job=None, key=None): return self.poll_job(job=job, key=key) 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 and 'jobresult' in res: if 'errortext' in res['jobresult']: self.module.fail_json(msg="Failed: '%s'" % res['jobresult']['errortext']) if key and key in res['jobresult']: job = res['jobresult'][key] break time.sleep(2) return job