From 31520cdd178246f94921ba9d9866abf23b28e252 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Fri, 24 Apr 2015 18:58:57 +0200 Subject: [PATCH 01/23] 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 2c891434bd..627ef9655e 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/23] 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 627ef9655e..9ef9d229ba 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/23] 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 9ef9d229ba..d98d00b76c 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/23] 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 d98d00b76c..afffb061f5 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/23] 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 afffb061f5..74afc79836 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/23] 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 74afc79836..48f16a1399 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/23] 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 48f16a1399..0c7da28e2a 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 92a25b340bbd2e1db0c282576bfd26450f92e761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Moser?= Date: Wed, 17 Dec 2014 14:22:27 +0100 Subject: [PATCH 08/23] cloudstack: add dynamic inventory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René Moser --- plugins/inventory/cloudstack.ini | 5 + plugins/inventory/cloudstack.py | 226 +++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 plugins/inventory/cloudstack.ini create mode 100755 plugins/inventory/cloudstack.py diff --git a/plugins/inventory/cloudstack.ini b/plugins/inventory/cloudstack.ini new file mode 100644 index 0000000000..43777b593f --- /dev/null +++ b/plugins/inventory/cloudstack.ini @@ -0,0 +1,5 @@ +[cloudstack] +#endpoint = https://api.exoscale.ch/compute +endpoint = https://cloud.example.com/client/api +key = cloudstack api key +secret = cloudstack api secret diff --git a/plugins/inventory/cloudstack.py b/plugins/inventory/cloudstack.py new file mode 100755 index 0000000000..fdd166ec49 --- /dev/null +++ b/plugins/inventory/cloudstack.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# (c) 2014, 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 . + +###################################################################### + +""" +Ansible CloudStack external inventory script. +============================================= + +Generates Ansible inventory from CloudStack. Configuration is read from +'cloudstack.ini'. If you need to pass the project, write a simple wrapper +script, e.g. project_cloudstack.sh: + + #!/bin/bash + cloudstack.py --project $@ + + +When run against a specific host, this script returns the following attributes +based on the data obtained from CloudStack API: + + "web01": { + "cpu_number": 2, + "nic": [ + { + "ip": "10.102.76.98", + "mac": "02:00:50:99:00:01", + "type": "Isolated", + "netmask": "255.255.255.0", + "gateway": "10.102.76.1" + }, + { + "ip": "10.102.138.63", + "mac": "06:b7:5a:00:14:84", + "type": "Shared", + "netmask": "255.255.255.0", + "gateway": "10.102.138.1" + } + ], + "default_ip": "10.102.76.98", + "zone": "ZUERICH", + "created": "2014-07-02T07:53:50+0200", + "hypervisor": "VMware", + "memory": 2048, + "state": "Running", + "tags": [], + "cpu_speed": 1800, + "affinity_group": [], + "service_offering": "Small", + "cpu_used": "62%" + } + + +usage: cloudstack.py [--list] [--host HOST] [--project PROJECT] +""" + +import os, sys +import argparse + +try: + import json +except: + import simplejson as json + + +try: + from cs import CloudStack, CloudStackException, read_config +except ImportError: + print >> sys.stderr, "Error: CloudStack library must be installed: pip install cs." + sys.exit(1) + + +class CloudStackInventory(object): + def __init__(self): + + parser = argparse.ArgumentParser() + parser.add_argument('--host') + parser.add_argument('--list', action='store_true') + parser.add_argument('--project') + + options = parser.parse_args() + try: + self.cs = CloudStack(**read_config()) + except CloudStackException, e: + print >> sys.stderr, "Error: Could not connect to CloudStack API" + + project_id = '' + if options.project: + project_id = self.get_project_id(options.project) + + if options.host: + data = self.get_host(options.host) + print json.dumps(data, indent=2) + + elif options.list: + data = self.get_list() + print json.dumps(data, indent=2) + else: + print >> sys.stderr, "usage: --list | --host [--project ]" + sys.exit(1) + + + def get_project_id(self, project): + projects = self.cs.listProjects() + if projects: + for p in projects['project']: + if p['name'] == project or p['id'] == project: + return p['id'] + print >> sys.stderr, "Error: Project %s not found." % project + sys.exit(1) + + + def get_host(self, name, project_id=''): + hosts = self.cs.listVirtualMachines(projectid=project_id) + data = {} + for host in hosts['virtualmachine']: + host_name = host['displayname'] + if name == host_name: + data['zone'] = host['zonename'] + if 'group' in host: + data['group'] = host['group'] + data['state'] = host['state'] + data['service_offering'] = host['serviceofferingname'] + data['affinity_group'] = host['affinitygroup'] + data['security_group'] = host['securitygroup'] + data['cpu_number'] = host['cpunumber'] + data['cpu_speed'] = host['cpuspeed'] + if 'cpuused' in host: + data['cpu_used'] = host['cpuused'] + data['memory'] = host['memory'] + data['tags'] = host['tags'] + data['hypervisor'] = host['hypervisor'] + data['created'] = host['created'] + data['nic'] = [] + for nic in host['nic']: + data['nic'].append({ + 'ip': nic['ipaddress'], + 'mac': nic['macaddress'], + 'netmask': nic['netmask'], + 'gateway': nic['gateway'], + 'type': nic['type'], + }) + if nic['isdefault']: + data['default_ip'] = nic['ipaddress'] + break; + return data + + + def get_list(self, project_id=''): + data = { + 'all': { + 'hosts': [], + }, + '_meta': { + 'hostvars': {}, + }, + } + + groups = self.cs.listInstanceGroups(projectid=project_id) + for group in groups['instancegroup']: + group_name = group['name'] + if group_name and not group_name in data: + data[group_name] = { + 'hosts': [] + } + + hosts = self.cs.listVirtualMachines(projectid=project_id) + for host in hosts['virtualmachine']: + host_name = host['displayname'] + data['all']['hosts'].append(host_name) + data['_meta']['hostvars'][host_name] = {} + data['_meta']['hostvars'][host_name]['zone'] = host['zonename'] + if 'group' in host: + data['_meta']['hostvars'][host_name]['group'] = host['group'] + data['_meta']['hostvars'][host_name]['state'] = host['state'] + data['_meta']['hostvars'][host_name]['service_offering'] = host['serviceofferingname'] + data['_meta']['hostvars'][host_name]['affinity_group'] = host['affinitygroup'] + data['_meta']['hostvars'][host_name]['security_group'] = host['securitygroup'] + data['_meta']['hostvars'][host_name]['cpu_number'] = host['cpunumber'] + data['_meta']['hostvars'][host_name]['cpu_speed'] = host['cpuspeed'] + if 'cpuused' in host: + data['_meta']['hostvars'][host_name]['cpu_used'] = host['cpuused'] + data['_meta']['hostvars'][host_name]['created'] = host['created'] + data['_meta']['hostvars'][host_name]['memory'] = host['memory'] + data['_meta']['hostvars'][host_name]['tags'] = host['tags'] + data['_meta']['hostvars'][host_name]['hypervisor'] = host['hypervisor'] + data['_meta']['hostvars'][host_name]['created'] = host['created'] + data['_meta']['hostvars'][host_name]['nic'] = [] + for nic in host['nic']: + data['_meta']['hostvars'][host_name]['nic'].append({ + 'ip': nic['ipaddress'], + 'mac': nic['macaddress'], + 'netmask': nic['netmask'], + 'gateway': nic['gateway'], + 'type': nic['type'], + }) + if nic['isdefault']: + data['_meta']['hostvars'][host_name]['default_ip'] = nic['ipaddress'] + + group_name = '' + if 'group' in host: + group_name = host['group'] + + if group_name and group_name in data: + data[group_name]['hosts'].append(host_name) + return data + + +if __name__ == '__main__': + CloudStackInventory() From bfa71054f55865297a03ec9d66ce89e57b2824d8 Mon Sep 17 00:00:00 2001 From: Milamber Date: Sat, 3 Jan 2015 18:57:55 +0000 Subject: [PATCH 09/23] Fix an issue when the cloudstack installation don't have any instance group --- plugins/inventory/cloudstack.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/inventory/cloudstack.py b/plugins/inventory/cloudstack.py index fdd166ec49..4969b613fe 100755 --- a/plugins/inventory/cloudstack.py +++ b/plugins/inventory/cloudstack.py @@ -173,12 +173,13 @@ class CloudStackInventory(object): } groups = self.cs.listInstanceGroups(projectid=project_id) - for group in groups['instancegroup']: - group_name = group['name'] - if group_name and not group_name in data: - data[group_name] = { - 'hosts': [] - } + if groups: + for group in groups['instancegroup']: + group_name = group['name'] + if group_name and not group_name in data: + data[group_name] = { + 'hosts': [] + } hosts = self.cs.listVirtualMachines(projectid=project_id) for host in hosts['virtualmachine']: From 9e5a16703b81953f9ee0334ee52413533480f460 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Tue, 10 Feb 2015 09:50:41 +0100 Subject: [PATCH 10/23] cloudstack: add check for empty inventory --- plugins/inventory/cloudstack.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/inventory/cloudstack.py b/plugins/inventory/cloudstack.py index 4969b613fe..d0b2f042d3 100755 --- a/plugins/inventory/cloudstack.py +++ b/plugins/inventory/cloudstack.py @@ -129,6 +129,8 @@ class CloudStackInventory(object): def get_host(self, name, project_id=''): hosts = self.cs.listVirtualMachines(projectid=project_id) data = {} + if not hosts: + return data for host in hosts['virtualmachine']: host_name = host['displayname'] if name == host_name: @@ -182,6 +184,8 @@ class CloudStackInventory(object): } hosts = self.cs.listVirtualMachines(projectid=project_id) + if not hosts: + return data for host in hosts['virtualmachine']: host_name = host['displayname'] data['all']['hosts'].append(host_name) From d9633037d5ccd597e8e9ff76404edf6f4b1fb4dc Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Tue, 28 Apr 2015 09:20:48 +0200 Subject: [PATCH 11/23] cloudstack: update copyright in dynamic inventory --- plugins/inventory/cloudstack.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/inventory/cloudstack.py b/plugins/inventory/cloudstack.py index d0b2f042d3..426cf163fd 100755 --- a/plugins/inventory/cloudstack.py +++ b/plugins/inventory/cloudstack.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# (c) 2014, René Moser +# (c) 2015, René Moser # # This file is part of Ansible, # @@ -70,7 +70,8 @@ based on the data obtained from CloudStack API: usage: cloudstack.py [--list] [--host HOST] [--project PROJECT] """ -import os, sys +import os +import sys import argparse try: From 38465283669829a4b9255976a889d9d7aef093bc Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Tue, 28 Apr 2015 16:38:53 -0400 Subject: [PATCH 12/23] clarify role spec, dependencies and galaxy involvment. fixes #10832 --- docsite/rst/playbooks_roles.rst | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docsite/rst/playbooks_roles.rst b/docsite/rst/playbooks_roles.rst index 3ffabe835d..b46474a89a 100644 --- a/docsite/rst/playbooks_roles.rst +++ b/docsite/rst/playbooks_roles.rst @@ -301,12 +301,8 @@ Role dependencies can also be specified as a full path, just like top level role dependencies: - { role: '/path/to/common/roles/foo', x: 1 } -Role dependencies can also be installed from source control repos or tar files, using a comma separated format of path, an optional version (tag, commit, branch etc) and optional friendly role name (an attempt is made to derive a role name from the repo name or archive filename):: +Role dependencies can also be installed from source control repos or tar files (via `galaxy`) using comma separated format of path, an optional version (tag, commit, branch etc) and optional friendly role name (an attempt is made to derive a role name from the repo name or archive filename). Both through the command line or via a requirements.yml passed to ansible-galaxy. - --- - dependencies: - - { role: 'git+http://git.example.com/repos/role-foo,v1.1,foo' } - - { role: '/path/to/tar/file.tgz,,friendly-name' } Roles dependencies are always executed before the role that includes them, and are recursive. By default, roles can also only be added as a dependency once - if another role also lists it as a dependency it will From 288fe1179a827e0457f36d3b465d5e12bd48162d Mon Sep 17 00:00:00 2001 From: jaypei Date: Wed, 29 Apr 2015 19:39:39 +0800 Subject: [PATCH 13/23] Add lineinfile integration tests for quoted string Reference #10864 --- .../roles/test_lineinfile/tasks/main.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/integration/roles/test_lineinfile/tasks/main.yml b/test/integration/roles/test_lineinfile/tasks/main.yml index d809bf1983..0c018ccaa5 100644 --- a/test/integration/roles/test_lineinfile/tasks/main.yml +++ b/test/integration/roles/test_lineinfile/tasks/main.yml @@ -355,4 +355,22 @@ that: - "result.stat.checksum == '73b271c2cc1cef5663713bc0f00444b4bf9f4543'" +- name: insert a line into the quoted file with many double quotation strings + lineinfile: dest={{output_dir}}/test_quoting.txt line="\"quote\" and \"unquote\"" + register: result + +- name: assert that the quoted file was changed + assert: + that: + - result.changed + +- name: stat the quote test file + stat: path={{output_dir}}/test_quoting.txt + register: result + +- name: assert test checksum matches after backref line was replaced + assert: + that: + - "result.stat.checksum == 'b10ab2a3c3b6492680c8d0b1d6f35aa6b8f9e731'" + ################################################################### From b11cd73df1ac11b4718c882f4db3f8180f3121bf Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Wed, 29 Apr 2015 21:06:58 +0200 Subject: [PATCH 14/23] 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 0c7da28e2a..518ef7a732 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 15/23] 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 518ef7a732..7ea02d1be7 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 16/23] 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 7ea02d1be7..2396c49cae 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 17/23] 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 2396c49cae..f791b40326 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: From 0588a0fdd974cae3b667e09c861455fcbab02f11 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Sun, 3 May 2015 16:06:30 +0200 Subject: [PATCH 18/23] cloudstack: integration tests --- test/integration/Makefile | 5 + test/integration/cloudstack.yml | 13 ++ .../roles/test_cs_affinitygroup/meta/main.yml | 3 + .../test_cs_affinitygroup/tasks/main.yml | 58 ++++++ .../roles/test_cs_common/defaults/main.yml | 2 + .../roles/test_cs_instance/defaults/main.yml | 2 + .../roles/test_cs_instance/meta/main.yml | 3 + .../roles/test_cs_instance/tasks/absent.yml | 23 +++ .../roles/test_cs_instance/tasks/cleanup.yml | 36 ++++ .../roles/test_cs_instance/tasks/main.yml | 11 ++ .../roles/test_cs_instance/tasks/present.yml | 168 ++++++++++++++++++ .../roles/test_cs_instance/tasks/setup.yml | 32 ++++ .../roles/test_cs_instance/tasks/tags.yml | 82 +++++++++ .../roles/test_cs_instancegroup/meta/main.yml | 3 + .../test_cs_instancegroup/tasks/main.yml | 58 ++++++ .../roles/test_cs_securitygroup/meta/main.yml | 3 + .../test_cs_securitygroup/tasks/main.yml | 58 ++++++ .../test_cs_securitygroup_rule/meta/main.yml | 3 + .../tasks/absent.yml | 105 +++++++++++ .../tasks/cleanup.yml | 7 + .../test_cs_securitygroup_rule/tasks/main.yml | 4 + .../tasks/present.yml | 118 ++++++++++++ .../tasks/setup.yml | 56 ++++++ .../roles/test_cs_sshkeypair/meta/main.yml | 3 + .../roles/test_cs_sshkeypair/tasks/main.yml | 89 ++++++++++ 25 files changed, 945 insertions(+) create mode 100644 test/integration/cloudstack.yml create mode 100644 test/integration/roles/test_cs_affinitygroup/meta/main.yml create mode 100644 test/integration/roles/test_cs_affinitygroup/tasks/main.yml create mode 100644 test/integration/roles/test_cs_common/defaults/main.yml create mode 100644 test/integration/roles/test_cs_instance/defaults/main.yml create mode 100644 test/integration/roles/test_cs_instance/meta/main.yml create mode 100644 test/integration/roles/test_cs_instance/tasks/absent.yml create mode 100644 test/integration/roles/test_cs_instance/tasks/cleanup.yml create mode 100644 test/integration/roles/test_cs_instance/tasks/main.yml create mode 100644 test/integration/roles/test_cs_instance/tasks/present.yml create mode 100644 test/integration/roles/test_cs_instance/tasks/setup.yml create mode 100644 test/integration/roles/test_cs_instance/tasks/tags.yml create mode 100644 test/integration/roles/test_cs_instancegroup/meta/main.yml create mode 100644 test/integration/roles/test_cs_instancegroup/tasks/main.yml create mode 100644 test/integration/roles/test_cs_securitygroup/meta/main.yml create mode 100644 test/integration/roles/test_cs_securitygroup/tasks/main.yml create mode 100644 test/integration/roles/test_cs_securitygroup_rule/meta/main.yml create mode 100644 test/integration/roles/test_cs_securitygroup_rule/tasks/absent.yml create mode 100644 test/integration/roles/test_cs_securitygroup_rule/tasks/cleanup.yml create mode 100644 test/integration/roles/test_cs_securitygroup_rule/tasks/main.yml create mode 100644 test/integration/roles/test_cs_securitygroup_rule/tasks/present.yml create mode 100644 test/integration/roles/test_cs_securitygroup_rule/tasks/setup.yml create mode 100644 test/integration/roles/test_cs_sshkeypair/meta/main.yml create mode 100644 test/integration/roles/test_cs_sshkeypair/tasks/main.yml diff --git a/test/integration/Makefile b/test/integration/Makefile index 6e2acec341..28de76c7cd 100644 --- a/test/integration/Makefile +++ b/test/integration/Makefile @@ -144,6 +144,11 @@ rackspace: $(CREDENTIALS_FILE) CLOUD_RESOURCE_PREFIX="$(CLOUD_RESOURCE_PREFIX)" make rackspace_cleanup ; \ exit $$RC; +cloudstack: + ansible-playbook cloudstack.yml -i $(INVENTORY) -e @$(VARS_FILE) -e "resource_prefix=$(CLOUD_RESOURCE_PREFIX)" -v $(TEST_FLAGS) ; \ + RC=$$? ; \ + exit $$RC; + $(CONSUL_RUNNING): consul: diff --git a/test/integration/cloudstack.yml b/test/integration/cloudstack.yml new file mode 100644 index 0000000000..5f5e65c6cd --- /dev/null +++ b/test/integration/cloudstack.yml @@ -0,0 +1,13 @@ +--- +- hosts: localhost + connection: local + gather_facts: no + tags: + - cloudstack + roles: + - { role: test_cs_sshkeypair, tags: test_cs_sshkeypair } + - { role: test_cs_affinitygroup, tags: test_cs_affinitygroup } + - { role: test_cs_securitygroup, tags: test_cs_securitygroup } + - { role: test_cs_securitygroup_rule, tags: test_cs_securitygroup_rule } + - { role: test_cs_instance, tags: test_cs_instance } + - { role: test_cs_instancegroup, tags: test_cs_instancegroup } diff --git a/test/integration/roles/test_cs_affinitygroup/meta/main.yml b/test/integration/roles/test_cs_affinitygroup/meta/main.yml new file mode 100644 index 0000000000..03e38bd4f7 --- /dev/null +++ b/test/integration/roles/test_cs_affinitygroup/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - test_cs_common diff --git a/test/integration/roles/test_cs_affinitygroup/tasks/main.yml b/test/integration/roles/test_cs_affinitygroup/tasks/main.yml new file mode 100644 index 0000000000..7ebab20bad --- /dev/null +++ b/test/integration/roles/test_cs_affinitygroup/tasks/main.yml @@ -0,0 +1,58 @@ +--- +- name: setup + cs_affinitygroup: name={{ cs_resource_prefix }}_ag state=absent + register: ag +- name: verify setup + assert: + that: + - ag|success + +- name: test fail if missing name + action: cs_affinitygroup + register: ag + ignore_errors: true +- name: verify results of fail if missing name + assert: + that: + - ag|failed + - ag.msg == "missing required arguments: name" + +- name: test present affinity group + cs_affinitygroup: name={{ cs_resource_prefix }}_ag + register: ag +- name: verify results of create affinity group + assert: + that: + - ag|success + - ag|changed + - ag.name == "{{ cs_resource_prefix }}_ag" + +- name: test present affinity group is idempotence + cs_affinitygroup: name={{ cs_resource_prefix }}_ag + register: ag +- name: verify results present affinity group is idempotence + assert: + that: + - ag|success + - not ag|changed + - ag.name == "{{ cs_resource_prefix }}_ag" + +- name: test absent affinity group + cs_affinitygroup: name={{ cs_resource_prefix }}_ag state=absent + register: ag +- name: verify results of absent affinity group + assert: + that: + - ag|success + - ag|changed + - ag.name == "{{ cs_resource_prefix }}_ag" + +- name: test absent affinity group is idempotence + cs_affinitygroup: name={{ cs_resource_prefix }}_ag state=absent + register: ag +- name: verify results of absent affinity group is idempotence + assert: + that: + - ag|success + - not ag|changed + - ag.name is undefined diff --git a/test/integration/roles/test_cs_common/defaults/main.yml b/test/integration/roles/test_cs_common/defaults/main.yml new file mode 100644 index 0000000000..ba9674ac92 --- /dev/null +++ b/test/integration/roles/test_cs_common/defaults/main.yml @@ -0,0 +1,2 @@ +--- +cs_resource_prefix: cloudstack diff --git a/test/integration/roles/test_cs_instance/defaults/main.yml b/test/integration/roles/test_cs_instance/defaults/main.yml new file mode 100644 index 0000000000..585947ab43 --- /dev/null +++ b/test/integration/roles/test_cs_instance/defaults/main.yml @@ -0,0 +1,2 @@ +--- +instance_number: 1 diff --git a/test/integration/roles/test_cs_instance/meta/main.yml b/test/integration/roles/test_cs_instance/meta/main.yml new file mode 100644 index 0000000000..03e38bd4f7 --- /dev/null +++ b/test/integration/roles/test_cs_instance/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - test_cs_common diff --git a/test/integration/roles/test_cs_instance/tasks/absent.yml b/test/integration/roles/test_cs_instance/tasks/absent.yml new file mode 100644 index 0000000000..bafb3ec9e7 --- /dev/null +++ b/test/integration/roles/test_cs_instance/tasks/absent.yml @@ -0,0 +1,23 @@ +--- +- name: test destroy instance + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + state: absent + register: instance +- name: verify destroy instance + assert: + that: + - instance|success + - instance|changed + - instance.state == "Destroyed" + +- name: test destroy instance idempotence + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + state: absent + register: instance +- name: verify destroy instance idempotence + assert: + that: + - instance|success + - not instance|changed diff --git a/test/integration/roles/test_cs_instance/tasks/cleanup.yml b/test/integration/roles/test_cs_instance/tasks/cleanup.yml new file mode 100644 index 0000000000..63192dbd60 --- /dev/null +++ b/test/integration/roles/test_cs_instance/tasks/cleanup.yml @@ -0,0 +1,36 @@ +--- +- name: cleanup ssh key + cs_sshkeypair: name={{ cs_resource_prefix }}-sshkey state=absent + register: sshkey +- name: verify cleanup ssh key + assert: + that: + - sshkey|success + +- name: cleanup affinity group + cs_affinitygroup: name={{ cs_resource_prefix }}-ag state=absent + register: ag + until: ag|success + retries: 20 + delay: 5 +- name: verify cleanup affinity group + assert: + that: + - ag|success + +- name: cleanup security group ...take a while unless instance is expunged + cs_securitygroup: name={{ cs_resource_prefix }}-sg state=absent + register: sg + until: sg|success + retries: 100 + delay: 10 +- name: verify cleanup security group + assert: + that: + - sg|success + +# force expunge, only works with admin permissions +- cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + state: expunged + failed_when: false diff --git a/test/integration/roles/test_cs_instance/tasks/main.yml b/test/integration/roles/test_cs_instance/tasks/main.yml new file mode 100644 index 0000000000..479ea01c15 --- /dev/null +++ b/test/integration/roles/test_cs_instance/tasks/main.yml @@ -0,0 +1,11 @@ +--- +- include: setup.yml + tags: any +- include: present.yml + tags: test_cs_instance_present +#- include: tags.yml +# tags: test_cs_instance_tags +- include: absent.yml + tags: test_cs_instance_absent +- include: cleanup.yml + tags: test_cs_instance_cleanup diff --git a/test/integration/roles/test_cs_instance/tasks/present.yml b/test/integration/roles/test_cs_instance/tasks/present.yml new file mode 100644 index 0000000000..4337f0acf4 --- /dev/null +++ b/test/integration/roles/test_cs_instance/tasks/present.yml @@ -0,0 +1,168 @@ +--- +- name: test create instance + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + template: Linux Debian 7 64-bit + service_offering: Tiny + affinity_group: "{{ cs_resource_prefix }}-ag" + security_group: "{{ cs_resource_prefix }}-sg" + ssh_key: "{{ cs_resource_prefix }}-sshkey" + tags: [] + register: instance +- name: verify create instance + assert: + that: + - instance|success + - instance|changed + - instance.name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.display_name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.service_offering == "Tiny" + - instance.state == "Running" + - instance.ssh_key == "{{ cs_resource_prefix }}-sshkey" + - not instance.tags + + +- name: test create instance idempotence + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + template: Linux Debian 7 64-bit + service_offering: Tiny + affinity_group: "{{ cs_resource_prefix }}-ag" + security_group: "{{ cs_resource_prefix }}-sg" + ssh_key: "{{ cs_resource_prefix }}-sshkey" + tags: [] + register: instance +- name: verify create instance idempotence + assert: + that: + - instance|success + - not instance|changed + - instance.name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.display_name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.service_offering == "Tiny" + - instance.state == "Running" + - instance.ssh_key == "{{ cs_resource_prefix }}-sshkey" + - not instance.tags + + +- name: test running instance not updated + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + service_offering: Micro + register: instance +- name: verify running instance not updated + assert: + that: + - instance|success + - not instance|changed + - instance.name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.display_name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.service_offering == "Tiny" + - instance.state == "Running" + + +- name: test stopping instance + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + state: stopped + register: instance +- name: verify stopping instance + assert: + that: + - instance|success + - instance|changed + - instance.name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.display_name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.service_offering == "Tiny" + - instance.state == "Stopped" + + +- name: test stopping instance idempotence + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + state: stopped + register: instance +- name: verify stopping instance idempotence + assert: + that: + - instance|success + - not instance|changed + - instance.state == "Stopped" + + +- name: test updating stopped instance + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + display_name: "{{ cs_resource_prefix }}-display-{{ instance_number }}" + service_offering: Micro + register: instance +- name: verify updating stopped instance + assert: + that: + - instance|success + - instance|changed + - instance.name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.display_name == "{{ cs_resource_prefix }}-display-{{ instance_number }}" + - instance.service_offering == "Micro" + - instance.state == "Stopped" + + +- name: test starting instance + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + state: started + register: instance +- name: verify starting instance + assert: + that: + - instance|success + - instance|changed + - instance.name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.display_name == "{{ cs_resource_prefix }}-display-{{ instance_number }}" + - instance.service_offering == "Micro" + - instance.state == "Running" + + +- name: test starting instance idempotence + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + state: started + register: instance +- name: verify starting instance idempotence + assert: + that: + - instance|success + - not instance|changed + - instance.state == "Running" + +- name: test force update running instance + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + service_offering: Tiny + force: true + register: instance +- name: verify force update running instance + assert: + that: + - instance|success + - instance|changed + - instance.name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.display_name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.service_offering == "Tiny" + - instance.state == "Running" + +- name: test force update running instance idempotence + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + service_offering: Tiny + force: true + register: instance +- name: verify force update running instance idempotence + assert: + that: + - instance|success + - not instance|changed + - instance.name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.display_name == "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + - instance.service_offering == "Tiny" + - instance.state == "Running" diff --git a/test/integration/roles/test_cs_instance/tasks/setup.yml b/test/integration/roles/test_cs_instance/tasks/setup.yml new file mode 100644 index 0000000000..32f3ff13e2 --- /dev/null +++ b/test/integration/roles/test_cs_instance/tasks/setup.yml @@ -0,0 +1,32 @@ +--- +- name: setup ssh key + cs_sshkeypair: name={{ cs_resource_prefix }}-sshkey + register: sshkey +- name: verify setup ssh key + assert: + that: + - sshkey|success + +- name: setup affinity group + cs_affinitygroup: name={{ cs_resource_prefix }}-ag + register: ag +- name: verify setup affinity group + assert: + that: + - ag|success + +- name: setup security group + cs_securitygroup: name={{ cs_resource_prefix }}-sg + register: sg +- name: verify setup security group + assert: + that: + - sg|success + +- name: setup instance to be absent + cs_instance: name={{ cs_resource_prefix }}-vm-{{ instance_number }} state=absent + register: instance +- name: verify instance to be absent + assert: + that: + - instance|success diff --git a/test/integration/roles/test_cs_instance/tasks/tags.yml b/test/integration/roles/test_cs_instance/tasks/tags.yml new file mode 100644 index 0000000000..a86158df0f --- /dev/null +++ b/test/integration/roles/test_cs_instance/tasks/tags.yml @@ -0,0 +1,82 @@ +--- +- name: test add tags to instance + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + tags: + - { key: "{{ cs_resource_prefix }}-tag1", value: "{{ cs_resource_prefix }}-value1" } + - { key: "{{ cs_resource_prefix }}-tag2", value: "{{ cs_resource_prefix }}-value2" } + register: instance +- name: verify add tags to instance + assert: + that: + - instance|success + - instance|changed + - instance.tags|length == 2 + - instance.tags[0]['key'] == "{{ cs_resource_prefix }}-tag1" + - instance.tags[1]['key'] == "{{ cs_resource_prefix }}-tag2" + - instance.tags[0]['value'] == "{{ cs_resource_prefix }}-value1" + - instance.tags[1]['value'] == "{{ cs_resource_prefix }}-value2" + + +- name: test tags to instance idempotence + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + tags: + - { key: "{{ cs_resource_prefix }}-tag1", value: "{{ cs_resource_prefix }}-value1" } + - { key: "{{ cs_resource_prefix }}-tag2", value: "{{ cs_resource_prefix }}-value2" } + register: instance +- name: verify tags to instance idempotence + assert: + that: + - instance|success + - not instance|changed + - instance.tags|length == 2 + - instance.tags[0]['key'] == "{{ cs_resource_prefix }}-tag1" + - instance.tags[1]['key'] == "{{ cs_resource_prefix }}-tag2" + - instance.tags[0]['value'] == "{{ cs_resource_prefix }}-value1" + - instance.tags[1]['value'] == "{{ cs_resource_prefix }}-value2" + +- name: test change tags of instance + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + tags: + - { key: "{{ cs_resource_prefix }}-tag2", value: "{{ cs_resource_prefix }}-value2" } + - { key: "{{ cs_resource_prefix }}-tag3", value: "{{ cs_resource_prefix }}-value3" } + register: instance +- name: verify tags to instance idempotence + assert: + that: + - instance|success + - not instance|changed + - instance.tags|length == 2 + - instance.tags[0]['key'] == "{{ cs_resource_prefix }}-tag1" + - instance.tags[1]['key'] == "{{ cs_resource_prefix }}-tag3" + - instance.tags[0]['value'] == "{{ cs_resource_prefix }}-value1" + - instance.tags[1]['value'] == "{{ cs_resource_prefix }}-value3" + +- name: test not touch tags of instance if no param tags + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + register: instance +- name: verify not touch tags of instance if no param tags + assert: + that: + - instance|success + - not instance|changed + - instance.tags|length == 2 + - instance.tags[0]['key'] == "{{ cs_resource_prefix }}-tag1" + - instance.tags[1]['key'] == "{{ cs_resource_prefix }}-tag3" + - instance.tags[0]['value'] == "{{ cs_resource_prefix }}-value1" + - instance.tags[1]['value'] == "{{ cs_resource_prefix }}-value3" + +- name: test remove tags + cs_instance: + name: "{{ cs_resource_prefix }}-vm-{{ instance_number }}" + tags: [] + register: instance +- name: verify remove tags + assert: + that: + - instance|success + - not instance|changed + - instance.tags|length == 0 diff --git a/test/integration/roles/test_cs_instancegroup/meta/main.yml b/test/integration/roles/test_cs_instancegroup/meta/main.yml new file mode 100644 index 0000000000..03e38bd4f7 --- /dev/null +++ b/test/integration/roles/test_cs_instancegroup/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - test_cs_common diff --git a/test/integration/roles/test_cs_instancegroup/tasks/main.yml b/test/integration/roles/test_cs_instancegroup/tasks/main.yml new file mode 100644 index 0000000000..e3a726bf6f --- /dev/null +++ b/test/integration/roles/test_cs_instancegroup/tasks/main.yml @@ -0,0 +1,58 @@ +--- +- name: setup + cs_instancegroup: name={{ cs_resource_prefix }}_ig state=absent + register: ig +- name: verify setup + assert: + that: + - ig|success + +- name: test fail if missing name + action: cs_instancegroup + register: ig + ignore_errors: true +- name: verify results of fail if missing name + assert: + that: + - ig|failed + - ig.msg == "missing required arguments: name" + +- name: test present instance group + cs_instancegroup: name={{ cs_resource_prefix }}_ig + register: ig +- name: verify results of create instance group + assert: + that: + - ig|success + - ig|changed + - ig.name == "{{ cs_resource_prefix }}_ig" + +- name: test present instance group is idempotence + cs_instancegroup: name={{ cs_resource_prefix }}_ig + register: ig +- name: verify results present instance group is idempotence + assert: + that: + - ig|success + - not ig|changed + - ig.name == "{{ cs_resource_prefix }}_ig" + +- name: test absent instance group + cs_instancegroup: name={{ cs_resource_prefix }}_ig state=absent + register: ig +- name: verify results of absent instance group + assert: + that: + - ig|success + - ig|changed + - ig.name == "{{ cs_resource_prefix }}_ig" + +- name: test absent instance group is idempotence + cs_instancegroup: name={{ cs_resource_prefix }}_ig state=absent + register: ig +- name: verify results of absent instance group is idempotence + assert: + that: + - ig|success + - not ig|changed + - ig.name is undefined diff --git a/test/integration/roles/test_cs_securitygroup/meta/main.yml b/test/integration/roles/test_cs_securitygroup/meta/main.yml new file mode 100644 index 0000000000..03e38bd4f7 --- /dev/null +++ b/test/integration/roles/test_cs_securitygroup/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - test_cs_common diff --git a/test/integration/roles/test_cs_securitygroup/tasks/main.yml b/test/integration/roles/test_cs_securitygroup/tasks/main.yml new file mode 100644 index 0000000000..d22871739e --- /dev/null +++ b/test/integration/roles/test_cs_securitygroup/tasks/main.yml @@ -0,0 +1,58 @@ +--- +- name: setup + cs_securitygroup: name={{ cs_resource_prefix }}_sg state=absent + register: sg +- name: verify setup + assert: + that: + - sg|success + +- name: test fail if missing name + action: cs_securitygroup + register: sg + ignore_errors: true +- name: verify results of fail if missing name + assert: + that: + - sg|failed + - sg.msg == "missing required arguments: name" + +- name: test present security group + cs_securitygroup: name={{ cs_resource_prefix }}_sg + register: sg +- name: verify results of create security group + assert: + that: + - sg|success + - sg|changed + - sg.name == "{{ cs_resource_prefix }}_sg" + +- name: test present security group is idempotence + cs_securitygroup: name={{ cs_resource_prefix }}_sg + register: sg +- name: verify results present security group is idempotence + assert: + that: + - sg|success + - not sg|changed + - sg.name == "{{ cs_resource_prefix }}_sg" + +- name: test absent security group + cs_securitygroup: name={{ cs_resource_prefix }}_sg state=absent + register: sg +- name: verify results of absent security group + assert: + that: + - sg|success + - sg|changed + - sg.name == "{{ cs_resource_prefix }}_sg" + +- name: test absent security group is idempotence + cs_securitygroup: name={{ cs_resource_prefix }}_sg state=absent + register: sg +- name: verify results of absent security group is idempotence + assert: + that: + - sg|success + - not sg|changed + - sg.name is undefined diff --git a/test/integration/roles/test_cs_securitygroup_rule/meta/main.yml b/test/integration/roles/test_cs_securitygroup_rule/meta/main.yml new file mode 100644 index 0000000000..03e38bd4f7 --- /dev/null +++ b/test/integration/roles/test_cs_securitygroup_rule/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - test_cs_common diff --git a/test/integration/roles/test_cs_securitygroup_rule/tasks/absent.yml b/test/integration/roles/test_cs_securitygroup_rule/tasks/absent.yml new file mode 100644 index 0000000000..8ee080a8ae --- /dev/null +++ b/test/integration/roles/test_cs_securitygroup_rule/tasks/absent.yml @@ -0,0 +1,105 @@ +- name: test remove http range rule + cs_securitygroup_rule: + security_group: default + start_port: 8000 + end_port: 8888 + cidr: 1.2.3.4/32 + state: absent + register: sg_rule +- name: verify create http range rule + assert: + that: + - sg_rule|success + - sg_rule|changed + - sg_rule.type == 'ingress' + - sg_rule.security_group == 'default' + - sg_rule.protocol == 'tcp' + - sg_rule.start_port == 8000 + - sg_rule.end_port == 8888 + - sg_rule.cidr == '1.2.3.4/32' + +- name: test remove http range rule idempotence + cs_securitygroup_rule: + security_group: default + start_port: 8000 + end_port: 8888 + cidr: 1.2.3.4/32 + state: absent + register: sg_rule +- name: verify create http range rule idempotence + assert: + that: + - sg_rule|success + - not sg_rule|changed + +- name: test remove single port udp rule + cs_securitygroup_rule: + security_group: default + port: 5353 + protocol: udp + type: egress + user_security_group: '{{ cs_resource_prefix }}_sg' + state: absent + register: sg_rule +- name: verify remove single port udp rule + assert: + that: + - sg_rule|success + - sg_rule|changed + - sg_rule.type == 'egress' + - sg_rule.security_group == 'default' + - sg_rule.protocol == 'udp' + - sg_rule.start_port == 5353 + - sg_rule.end_port == 5353 + - sg_rule.user_security_group == '{{ cs_resource_prefix }}_sg' + +- name: test remove single port udp rule idempotence + cs_securitygroup_rule: + security_group: default + port: 5353 + protocol: udp + type: egress + user_security_group: '{{ cs_resource_prefix }}_sg' + state: absent + register: sg_rule +- name: verify remove single port udp rule idempotence + assert: + that: + - sg_rule|success + - not sg_rule|changed + +- name: test remove icmp rule + cs_securitygroup_rule: + security_group: default + protocol: icmp + type: ingress + icmp_type: -1 + icmp_code: -1 + state: absent + register: sg_rule +- name: verify icmp rule + assert: + that: + - sg_rule|success + - sg_rule|changed + - sg_rule.type == 'ingress' + - sg_rule.security_group == 'default' + - sg_rule.cidr == '0.0.0.0/0' + - sg_rule.protocol == 'icmp' + - sg_rule.icmp_code == -1 + - sg_rule.icmp_type == -1 + +- name: test remove icmp rule idempotence + cs_securitygroup_rule: + security_group: default + protocol: icmp + type: ingress + icmp_type: -1 + icmp_code: -1 + state: absent + register: sg_rule +- name: verify icmp rule idempotence + assert: + that: + - sg_rule|success + - not sg_rule|changed diff --git a/test/integration/roles/test_cs_securitygroup_rule/tasks/cleanup.yml b/test/integration/roles/test_cs_securitygroup_rule/tasks/cleanup.yml new file mode 100644 index 0000000000..712ab5c6ce --- /dev/null +++ b/test/integration/roles/test_cs_securitygroup_rule/tasks/cleanup.yml @@ -0,0 +1,7 @@ +- name: cleanup custom security group + cs_securitygroup: name={{ cs_resource_prefix }}_sg state=absent + register: sg +- name: verify setup + assert: + that: + - sg|success diff --git a/test/integration/roles/test_cs_securitygroup_rule/tasks/main.yml b/test/integration/roles/test_cs_securitygroup_rule/tasks/main.yml new file mode 100644 index 0000000000..e76745cb54 --- /dev/null +++ b/test/integration/roles/test_cs_securitygroup_rule/tasks/main.yml @@ -0,0 +1,4 @@ +- include: setup.yml +- include: present.yml +- include: absent.yml +- include: cleanup.yml diff --git a/test/integration/roles/test_cs_securitygroup_rule/tasks/present.yml b/test/integration/roles/test_cs_securitygroup_rule/tasks/present.yml new file mode 100644 index 0000000000..92973290d4 --- /dev/null +++ b/test/integration/roles/test_cs_securitygroup_rule/tasks/present.yml @@ -0,0 +1,118 @@ +- name: test create http range rule + cs_securitygroup_rule: + security_group: default + start_port: 8000 + end_port: 8888 + cidr: 1.2.3.4/32 + register: sg_rule +- name: verify create http range rule + assert: + that: + - sg_rule|success + - sg_rule|changed + - sg_rule.type == 'ingress' + - sg_rule.security_group == 'default' + - sg_rule.protocol == 'tcp' + - sg_rule.start_port == 8000 + - sg_rule.end_port == 8888 + - sg_rule.cidr == '1.2.3.4/32' + +- name: test create http range rule idempotence + cs_securitygroup_rule: + security_group: default + start_port: 8000 + end_port: 8888 + cidr: 1.2.3.4/32 + register: sg_rule +- name: verify create http range rule idempotence + assert: + that: + - sg_rule|success + - not sg_rule|changed + - sg_rule.type == 'ingress' + - sg_rule.security_group == 'default' + - sg_rule.protocol == 'tcp' + - sg_rule.start_port == 8000 + - sg_rule.end_port == 8888 + - sg_rule.cidr == '1.2.3.4/32' + +- name: test create single port udp rule + cs_securitygroup_rule: + security_group: default + port: 5353 + protocol: udp + type: egress + user_security_group: '{{ cs_resource_prefix }}_sg' + register: sg_rule +- name: verify create single port udp rule + assert: + that: + - sg_rule|success + - sg_rule|changed + - sg_rule.type == 'egress' + - sg_rule.security_group == 'default' + - sg_rule.protocol == 'udp' + - sg_rule.start_port == 5353 + - sg_rule.end_port == 5353 + - sg_rule.user_security_group == '{{ cs_resource_prefix }}_sg' + + +- name: test single port udp rule idempotence + cs_securitygroup_rule: + security_group: default + port: 5353 + protocol: udp + type: egress + user_security_group: '{{ cs_resource_prefix }}_sg' + register: sg_rule +- name: verify single port udp rule idempotence + assert: + that: + - sg_rule|success + - not sg_rule|changed + - sg_rule.type == 'egress' + - sg_rule.security_group == 'default' + - sg_rule.protocol == 'udp' + - sg_rule.start_port == 5353 + - sg_rule.end_port == 5353 + - sg_rule.user_security_group == '{{ cs_resource_prefix }}_sg' + +- name: test icmp rule + cs_securitygroup_rule: + security_group: default + protocol: icmp + type: ingress + icmp_type: -1 + icmp_code: -1 + register: sg_rule +- name: verify icmp rule + assert: + that: + - sg_rule|success + - sg_rule|changed + - sg_rule.type == 'ingress' + - sg_rule.security_group == 'default' + - sg_rule.cidr == '0.0.0.0/0' + - sg_rule.protocol == 'icmp' + - sg_rule.icmp_code == -1 + - sg_rule.icmp_type == -1 + +- name: test icmp rule idempotence + cs_securitygroup_rule: + security_group: default + protocol: icmp + type: ingress + icmp_type: -1 + icmp_code: -1 + register: sg_rule +- name: verify icmp rule idempotence + assert: + that: + - sg_rule|success + - not sg_rule|changed + - sg_rule.type == 'ingress' + - sg_rule.security_group == 'default' + - sg_rule.cidr == '0.0.0.0/0' + - sg_rule.protocol == 'icmp' + - sg_rule.icmp_code == -1 + - sg_rule.icmp_type == -1 diff --git a/test/integration/roles/test_cs_securitygroup_rule/tasks/setup.yml b/test/integration/roles/test_cs_securitygroup_rule/tasks/setup.yml new file mode 100644 index 0000000000..797330ebc1 --- /dev/null +++ b/test/integration/roles/test_cs_securitygroup_rule/tasks/setup.yml @@ -0,0 +1,56 @@ +- name: setup custom security group + cs_securitygroup: name={{ cs_resource_prefix }}_sg + register: sg +- name: verify setup + assert: + that: + - sg|success + +- name: setup default security group + cs_securitygroup: name=default + register: sg +- name: verify setup + assert: + that: + - sg|success + +- name: setup remove icmp rule + cs_securitygroup_rule: + security_group: default + protocol: icmp + type: ingress + icmp_type: -1 + icmp_code: -1 + state: absent + register: sg_rule +- name: verify remove icmp rule + assert: + that: + - sg_rule|success + +- name: setup remove http range rule + cs_securitygroup_rule: + security_group: default + start_port: 8000 + end_port: 8888 + cidr: 1.2.3.4/32 + state: absent + register: sg_rule +- name: verify remove http range rule + assert: + that: + - sg_rule|success + +- name: setup remove single port udp rule + cs_securitygroup_rule: + security_group: default + port: 5353 + protocol: udp + type: egress + user_security_group: '{{ cs_resource_prefix }}-user-sg' + state: absent + register: sg_rule +- name: verify remove single port udp rule + assert: + that: + - sg_rule|success diff --git a/test/integration/roles/test_cs_sshkeypair/meta/main.yml b/test/integration/roles/test_cs_sshkeypair/meta/main.yml new file mode 100644 index 0000000000..03e38bd4f7 --- /dev/null +++ b/test/integration/roles/test_cs_sshkeypair/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - test_cs_common diff --git a/test/integration/roles/test_cs_sshkeypair/tasks/main.yml b/test/integration/roles/test_cs_sshkeypair/tasks/main.yml new file mode 100644 index 0000000000..35023b38aa --- /dev/null +++ b/test/integration/roles/test_cs_sshkeypair/tasks/main.yml @@ -0,0 +1,89 @@ +--- +- name: setup cleanup + cs_sshkeypair: name={{ cs_resource_prefix }}-sshkey state=absent + +- name: test fail on missing name + action: cs_sshkeypair + ignore_errors: true + register: sshkey +- name: verify results of fail on missing name + assert: + that: + - sshkey|failed + - sshkey.msg == "missing required arguments: name" + +- name: test ssh key creation + cs_sshkeypair: name={{ cs_resource_prefix }}-sshkey + register: sshkey +- name: verify results of ssh key creation + assert: + that: + - sshkey|success + - sshkey|changed + - sshkey.fingerprint is defined and sshkey.fingerprint != "" + - sshkey.private_key is defined and sshkey.private_key != "" + - sshkey.name == "{{ cs_resource_prefix }}-sshkey" + +- name: test ssh key creation idempotence + cs_sshkeypair: name={{ cs_resource_prefix }}-sshkey + register: sshkey2 +- name: verify results of ssh key creation idempotence + assert: + that: + - sshkey2|success + - not sshkey2|changed + - sshkey2.fingerprint is defined and sshkey2.fingerprint == sshkey.fingerprint + - sshkey2.private_key is not defined + - sshkey2.name == "{{ cs_resource_prefix }}-sshkey" + +- name: test replace ssh public key + cs_sshkeypair: | + name={{ cs_resource_prefix }}-sshkey + public_key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDsTI7KJZ8tz/CwQIrSol41c6s3vzkGYCMI8o7P9Et48UG9eRoGaMaGYaTvBTj/VQrD7cfurI6Bn0HTT3FLK3OHOweyelm9rIiQ2hjkSl+2lIKWHu992GO58E5Gcy9yYW4sHGgGLNZkPBKrrj0w7lhmiHjPtVnf+2+7Ix1WOO2/HXPcAHhsX/AlyItDewIL4mr/BT83vq0202sPCiM2cFQJl+5WGwS1wYYK8d167cspsmdyX7OyAFCUB0vueuqjE8MFqJvyIJR9y8Lj9Ny71pSV5/QWrXUgELxMYOKSby3gHkxcIXgYBMFLl4DipRTO74OWQlRRaOlqXlOOQbikcY4T rene.moser@swisstxt.ch" + register: sshkey3 +- name: verify results of replace ssh public key + assert: + that: + - sshkey3|success + - sshkey3|changed + - sshkey3.fingerprint is defined and sshkey3.fingerprint != sshkey2.fingerprint + - sshkey3.private_key is not defined + - sshkey3.name == "{{ cs_resource_prefix }}-sshkey" + +- name: test replace ssh public key idempotence + cs_sshkeypair: | + name={{ cs_resource_prefix }}-sshkey + public_key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDsTI7KJZ8tz/CwQIrSol41c6s3vzkGYCMI8o7P9Et48UG9eRoGaMaGYaTvBTj/VQrD7cfurI6Bn0HTT3FLK3OHOweyelm9rIiQ2hjkSl+2lIKWHu992GO58E5Gcy9yYW4sHGgGLNZkPBKrrj0w7lhmiHjPtVnf+2+7Ix1WOO2/HXPcAHhsX/AlyItDewIL4mr/BT83vq0202sPCiM2cFQJl+5WGwS1wYYK8d167cspsmdyX7OyAFCUB0vueuqjE8MFqJvyIJR9y8Lj9Ny71pSV5/QWrXUgELxMYOKSby3gHkxcIXgYBMFLl4DipRTO74OWQlRRaOlqXlOOQbikcY4T rene.moser@swisstxt.ch" + register: sshkey4 +- name: verify results of ssh public key idempotence + assert: + that: + - sshkey4|success + - not sshkey4|changed + - sshkey4.fingerprint is defined and sshkey4.fingerprint == sshkey3.fingerprint + - sshkey4.private_key is not defined + - sshkey4.name == "{{ cs_resource_prefix }}-sshkey" + +- name: test ssh key absent + cs_sshkeypair: name={{ cs_resource_prefix }}-sshkey state=absent + register: sshkey5 +- name: verify result of key absent + assert: + that: + - sshkey5|success + - sshkey5|changed + - sshkey5.fingerprint is defined and sshkey5.fingerprint == sshkey3.fingerprint + - sshkey5.private_key is not defined + - sshkey5.name == "{{ cs_resource_prefix }}-sshkey" + +- name: test ssh key absent idempotence + cs_sshkeypair: name={{ cs_resource_prefix }}-sshkey state=absent + register: sshkey6 +- name: verify result of ssh key absent idempotence + assert: + that: + - sshkey6|success + - not sshkey6|changed + - sshkey6.fingerprint is not defined + - sshkey6.private_key is not defined + - sshkey6.name is not defined From 354bdaacb88129817e75477e00b2c864e2259a04 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Mon, 4 May 2015 11:19:20 -0400 Subject: [PATCH 19/23] added new cloud/open|stack modules to changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00d53e743a..f22a203de9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,12 +22,16 @@ New Modules: * cloudstack: cs_affinitygroup * cloudstack: cs_firewall * cloudstack: cs_iso + * cloudstack: cs_instance * cloudstack: cs_sshkeypair * cloudstack: cs_securitygroup * cloudstack: cs_securitygroup_rule * cloudstack: cs_vmsnapshot * maven_artifact * openstack: os_server_facts + * openstack: os_server_volume + * openstack: os_subnet + * openstack: os_volume * pushover * zabbix_host * zabbix_hostmacro From 8259f449de71fd716dc0f7b6e69e54146b686991 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Mon, 4 May 2015 11:34:02 -0400 Subject: [PATCH 20/23] added cloudstack inventory --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f22a203de9..9485fd2198 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ New Modules: * vmware_datacenter New Inventory scripts: + * cloudstack * fleetctl Other Notable Changes: From 2543403c21071b4a1b2647062c2720beb2f406ef Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Mon, 4 May 2015 12:04:18 -0400 Subject: [PATCH 21/23] deprecated nova_compute and added new os_server for openstack to changelog --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9485fd2198..92354cd852 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,9 @@ Major Changes: * template code now retains types for bools and Numbers instead of turning them into strings If you need the old behaviour, quote the value and it will get passed around as a string -Deprecated Modules: - * ec2_ami_search, in favor of the new ec2_ami_find +Deprecated Modules (new ones in parens): + * ec2_ami_search (ec2_ami_find) + * nova_compute (os_server) New Modules: * find @@ -28,6 +29,7 @@ New Modules: * cloudstack: cs_securitygroup_rule * cloudstack: cs_vmsnapshot * maven_artifact + * openstack: os_server * openstack: os_server_facts * openstack: os_server_volume * openstack: os_subnet From 61ec84ef717bade247590bda44ad5aa4372be2f9 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 4 May 2015 11:09:54 -0700 Subject: [PATCH 22/23] Update module refs --- lib/ansible/modules/core | 2 +- lib/ansible/modules/extras | 2 +- v2/ansible/modules/core | 2 +- v2/ansible/modules/extras | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ansible/modules/core b/lib/ansible/modules/core index 9028e9d4be..f444e49dfa 160000 --- a/lib/ansible/modules/core +++ b/lib/ansible/modules/core @@ -1 +1 @@ -Subproject commit 9028e9d4be8a3dbb96c81a799e18f3adf63d9fd0 +Subproject commit f444e49dfa652e0bec0a140efe69ac5372cac321 diff --git a/lib/ansible/modules/extras b/lib/ansible/modules/extras index dd80fa221c..70ea058563 160000 --- a/lib/ansible/modules/extras +++ b/lib/ansible/modules/extras @@ -1 +1 @@ -Subproject commit dd80fa221ce0adb3abd658fbd1aa09bf7cf8a6dc +Subproject commit 70ea05856356ad36f48b4bb7267d637efc56d292 diff --git a/v2/ansible/modules/core b/v2/ansible/modules/core index 0341ddd35e..85c8a892c8 160000 --- a/v2/ansible/modules/core +++ b/v2/ansible/modules/core @@ -1 +1 @@ -Subproject commit 0341ddd35ed5ff477ad5de2488d947255ce86259 +Subproject commit 85c8a892c80b92730831d95fa654ef6d35b0eca0 diff --git a/v2/ansible/modules/extras b/v2/ansible/modules/extras index dd80fa221c..70ea058563 160000 --- a/v2/ansible/modules/extras +++ b/v2/ansible/modules/extras @@ -1 +1 @@ -Subproject commit dd80fa221ce0adb3abd658fbd1aa09bf7cf8a6dc +Subproject commit 70ea05856356ad36f48b4bb7267d637efc56d292 From 99909b08bad5e7e2d859cf8a17467df6df4efcda Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Mon, 4 May 2015 15:06:00 -0500 Subject: [PATCH 23/23] Submodule update --- lib/ansible/modules/core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ansible/modules/core b/lib/ansible/modules/core index f444e49dfa..c4f6e63117 160000 --- a/lib/ansible/modules/core +++ b/lib/ansible/modules/core @@ -1 +1 @@ -Subproject commit f444e49dfa652e0bec0a140efe69ac5372cac321 +Subproject commit c4f6e63117cd378ed5b144bf6c8391420a2381ab