From 31520cdd178246f94921ba9d9866abf23b28e252 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Fri, 24 Apr 2015 18:58:57 +0200 Subject: [PATCH 01/11] cloudstack: fix other projects not found --- lib/ansible/module_utils/cloudstack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index 2c891434bde..627ef9655e2 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -77,7 +77,7 @@ class AnsibleCloudStack: if not project: return None - projects = self.cs.listProjects() + projects = self.cs.listProjects(listall=True) if projects: for p in projects['project']: if project in [ p['name'], p['displaytext'], p['id'] ]: From 88540d3cdcef13775664b83b717b32c41137dd38 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Fri, 24 Apr 2015 19:04:33 +0200 Subject: [PATCH 02/11] cloudstack: add _get_by_key() to utils Generic method to get the whole dict or just a singe value by key if found. --- lib/ansible/module_utils/cloudstack.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index 627ef9655e2..9ef9d229ba7 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -69,6 +69,14 @@ class AnsibleCloudStack: self.cs = CloudStack(**read_config()) + 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_id(self): if self.project_id: return self.project_id From 765c8fe36871751c2d5d8c2d1c9362d5b571629d Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Fri, 24 Apr 2015 19:09:30 +0200 Subject: [PATCH 03/11] cloudstack: use _get_by_key in get_...() methods in utils But also add backward compatibility for existing modules in extras. --- lib/ansible/module_utils/cloudstack.py | 85 +++++++++++++++++--------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index 9ef9d229ba7..d98d00b76ce 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -44,11 +44,11 @@ class AnsibleCloudStack: 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.project = None + self.ip_address = None + self.zone = None + self.vm = None + self.os_type = None self.hypervisor = None @@ -77,9 +77,14 @@ class AnsibleCloudStack: return my_dict + # TODO: for backward compatibility only, remove if not used anymore def get_project_id(self): - if self.project_id: - return self.project_id + return get_project(key='id') + + + 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: @@ -89,14 +94,19 @@ class AnsibleCloudStack: 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.project = p + return self._get_by_key(key, self.project) self.module.fail_json(msg="project '%s' not found" % project) + # TODO: for backward compatibility only, remove if not used anymore def get_ip_address_id(self): - if self.ip_address_id: - return self.ip_address_id + return get_ip_address(key='id') + + + 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: @@ -104,58 +114,73 @@ class AnsibleCloudStack: args = {} args['ipaddress'] = ip_address - args['projectid'] = self.get_project_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_id = ip_addresses['publicipaddress'][0]['id'] - return self.ip_address_id + self.ip_address = ip_addresses['publicipaddress'][0] + return self._get_by_key(key, self.ip_address) + # TODO: for backward compatibility only, remove if not used anymore def get_vm_id(self): - if self.vm_id: - return self.vm_id + return get_vm(key='id') + + + 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['projectid'] = self.get_project_id() + args['projectid'] = self.get_project(key='id') vms = self.cs.listVirtualMachines(**args) if vms: for v in vms['virtualmachine']: - if vm in [ v['displayname'], v['name'], v['id'] ]: - self.vm_id = v['id'] - return self.vm_id + 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) + # TODO: for backward compatibility only, remove if not used anymore def get_zone_id(self): - if self.zone_id: - return self.zone_id + return get_zone(key='id') + + + 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_id = zones['zone'][0]['id'] - return self.zone_id + 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_id = z['id'] - return self.zone_id + self.zone = z + return self._get_by_key(key, self.zone) self.module.fail_json(msg="zone '%s' not found" % zone) + # TODO: for backward compatibility only, remove if not used anymore def get_os_type_id(self): - if self.os_type_id: - return self.os_type_id + return get_os_type(key='id') + + + 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: @@ -165,8 +190,8 @@ class AnsibleCloudStack: 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.os_type = o + return self._get_by_key(key, self.os_type) self.module.fail_json(msg="OS type '%s' not found" % os_type) From 6354ca07189e7d21a31722f6216231f61221c995 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Fri, 24 Apr 2015 19:16:42 +0200 Subject: [PATCH 04/11] cloudstack: add _has_changed() to utils Generic method to compare values in dict. --- lib/ansible/module_utils/cloudstack.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index d98d00b76ce..afffb061f55 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -69,6 +69,27 @@ class AnsibleCloudStack: self.cs = CloudStack(**read_config()) + 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; + + 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: From 3c0e406f5db4c61dd38e505061145b4f1e02f518 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Fri, 24 Apr 2015 20:25:19 +0200 Subject: [PATCH 05/11] cloudstack: fix missing self. in cloudstack utils --- lib/ansible/module_utils/cloudstack.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index afffb061f55..74afc798361 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -100,7 +100,7 @@ class AnsibleCloudStack: # TODO: for backward compatibility only, remove if not used anymore def get_project_id(self): - return get_project(key='id') + return self.get_project(key='id') def get_project(self, key=None): @@ -122,7 +122,7 @@ class AnsibleCloudStack: # TODO: for backward compatibility only, remove if not used anymore def get_ip_address_id(self): - return get_ip_address(key='id') + return self.get_ip_address(key='id') def get_ip_address(self, key=None): @@ -147,7 +147,7 @@ class AnsibleCloudStack: # TODO: for backward compatibility only, remove if not used anymore def get_vm_id(self): - return get_vm(key='id') + return self.get_vm(key='id') def get_vm(self, key=None): @@ -171,7 +171,7 @@ class AnsibleCloudStack: # TODO: for backward compatibility only, remove if not used anymore def get_zone_id(self): - return get_zone(key='id') + return self.get_zone(key='id') def get_zone(self, key=None): @@ -196,7 +196,7 @@ class AnsibleCloudStack: # TODO: for backward compatibility only, remove if not used anymore def get_os_type_id(self): - return get_os_type(key='id') + return self.get_os_type(key='id') def get_os_type(self, key=None): From ca88189bf765a1f519733706e299f45bd2dc3ccd Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Sat, 25 Apr 2015 18:31:58 +0200 Subject: [PATCH 06/11] cloudstack: add method to to get infos of API get_capabilities() allows you to get infos e.g. `cloudstackversion` to compare functionality of the API in your modules. --- lib/ansible/module_utils/cloudstack.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index 74afc798361..48f16a13992 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -50,6 +50,7 @@ class AnsibleCloudStack: self.vm = None self.os_type = None self.hypervisor = None + self.capabilities = None def _connect(self): @@ -235,6 +236,14 @@ class AnsibleCloudStack: self.module.fail_json(msg="Hypervisor '%s' not found" % hypervisor) + 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) + + def _poll_job(self, job=None, key=None): if 'jobid' in job: while True: From 2f255f5b967ac4d8ddba53af21adf192f2330a53 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Sun, 26 Apr 2015 23:09:33 +0200 Subject: [PATCH 07/11] cloudstack: get_vm(): fix missing zone Fixes returning wrong VM having identical name in different zone. --- lib/ansible/module_utils/cloudstack.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index 48f16a13992..0c7da28e2a7 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -161,6 +161,7 @@ class AnsibleCloudStack: args = {} 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']: From b11cd73df1ac11b4718c882f4db3f8180f3121bf Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Wed, 29 Apr 2015 21:06:58 +0200 Subject: [PATCH 08/11] cloudstack: add tag support in utils --- lib/ansible/module_utils/cloudstack.py | 61 ++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index 0c7da28e2a7..518ef7a7326 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -41,6 +41,10 @@ class AnsibleCloudStack: 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() @@ -237,6 +241,63 @@ class AnsibleCloudStack: self.module.fail_json(msg="Hypervisor '%s' not found" % hypervisor) + 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) From 034ac8ae78553678716682cd4cd68cfb61873fe9 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Fri, 1 May 2015 17:25:06 +0200 Subject: [PATCH 09/11] cloudstack: _has_changed() should not compare None values --- lib/ansible/module_utils/cloudstack.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index 518ef7a7326..7ea02d1be7b 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -81,6 +81,10 @@ class AnsibleCloudStack: 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 From af74d7f1a961f2d1cccb06f1d911864c16ef9e86 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Sun, 3 May 2015 14:34:25 +0200 Subject: [PATCH 10/11] cloudstack: add get_domain() and get_account() to utils --- lib/ansible/module_utils/cloudstack.py | 42 ++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index 7ea02d1be7b..2396c49caec 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -48,6 +48,8 @@ class AnsibleCloudStack: self.module = module self._connect() + self.domain = None + self.account = None self.project = None self.ip_address = None self.zone = None @@ -73,7 +75,7 @@ class AnsibleCloudStack: else: self.cs = CloudStack(**read_config()) - + # TODO: rename to has_changed() def _has_changed(self, want_dict, current_dict, only_keys=None): for key, value in want_dict.iteritems(): @@ -245,6 +247,42 @@ class AnsibleCloudStack: 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 + + args = {} + args['name'] = account + 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 + domain = 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: @@ -309,7 +347,7 @@ class AnsibleCloudStack: self.capabilities = capabilities['capability'] return self._get_by_key(key, self.capabilities) - + # TODO: rename to poll_job() def _poll_job(self, job=None, key=None): if 'jobid' in job: while True: From 333c623b35c62f9199cdbdb4684e58789497f80a Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Sun, 3 May 2015 14:59:02 +0200 Subject: [PATCH 11/11] cloudstack: implement account und domain support in utils --- lib/ansible/module_utils/cloudstack.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index 2396c49caec..f791b403263 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -121,8 +121,11 @@ class AnsibleCloudStack: project = self.module.params.get('project') if not project: return None - - projects = self.cs.listProjects(listall=True) + args = {} + args['listall'] = True + 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 in [ p['name'], p['displaytext'], p['id'] ]: @@ -146,6 +149,8 @@ class AnsibleCloudStack: 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) @@ -170,6 +175,8 @@ class AnsibleCloudStack: 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) @@ -255,8 +262,13 @@ class AnsibleCloudStack: 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: