diff --git a/CHANGELOG.md b/CHANGELOG.md index 00d53e743a..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 @@ -22,12 +23,17 @@ 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 * openstack: os_server_facts + * openstack: os_server_volume + * openstack: os_subnet + * openstack: os_volume * pushover * zabbix_host * zabbix_hostmacro @@ -40,6 +46,7 @@ New Modules: * vmware_datacenter New Inventory scripts: + * cloudstack * fleetctl Other Notable Changes: 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 diff --git a/lib/ansible/module_utils/cloudstack.py b/lib/ansible/module_utils/cloudstack.py index 2c891434bd..f791b40326 100644 --- a/lib/ansible/module_utils/cloudstack.py +++ b/lib/ansible/module_utils/cloudstack.py @@ -41,15 +41,22 @@ 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() - self.project_id = None - self.ip_address_id = None - self.zone_id = None - self.vm_id = None - self.os_type_id = None + self.domain = None + self.account = None + self.project = None + self.ip_address = None + self.zone = None + self.vm = None + self.os_type = None self.hypervisor = None + self.capabilities = None def _connect(self): @@ -68,27 +75,73 @@ 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(): + # Optionally limit by a list of keys + if only_keys and key not in only_keys: + continue; + + # Skip None values + if value is None: + continue; + + if key in current_dict: + + # API returns string for int in some cases, just to make sure + if isinstance(value, int): + current_dict[key] = int(current_dict[key]) + elif isinstance(value, str): + current_dict[key] = str(current_dict[key]) + + # Only need to detect a singe change, not every item + if value != current_dict[key]: + return True + return False + + + def _get_by_key(self, key=None, my_dict={}): + if key: + if key in my_dict: + return my_dict[key] + self.module.fail_json(msg="Something went wrong: %s not found" % key) + return my_dict + + + # TODO: for backward compatibility only, remove if not used anymore def get_project_id(self): - if self.project_id: - return self.project_id + return self.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: return None - - projects = self.cs.listProjects() + 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'] ]: - 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 self.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: @@ -96,58 +149,78 @@ class AnsibleCloudStack: args = {} args['ipaddress'] = ip_address - args['projectid'] = self.get_project_id() + args['account'] = self.get_account(key='name') + args['domainid'] = self.get_domain(key='id') + args['projectid'] = self.get_project(key='id') ip_addresses = self.cs.listPublicIpAddresses(**args) if not ip_addresses: self.module.fail_json(msg="IP address '%s' not found" % args['ipaddress']) - self.ip_address_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 self.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['account'] = self.get_account(key='name') + args['domainid'] = self.get_domain(key='id') + args['projectid'] = self.get_project(key='id') + args['zoneid'] = self.get_zone(key='id') vms = self.cs.listVirtualMachines(**args) if vms: for v in vms['virtualmachine']: - if vm in [ v['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 self.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 self.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: @@ -157,8 +230,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) @@ -181,6 +254,112 @@ 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 + + domain = self.module.params.get('domain') + if not domain: + self.module.fail_json(msg="Account must be specified with Domain") + + args = {} + args['name'] = account + args['domainid'] = self.get_domain(key='id') + args['listall'] = True + accounts = self.cs.listAccounts(**args) + if accounts: + self.account = accounts['account'][0] + return self._get_by_key(key, self.account) + self.module.fail_json(msg="Account '%s' not found" % account) + + + def get_domain(self, key=None): + if self.domain: + return self._get_by_key(key, self.domain) + + domain = self.module.params.get('domain') + if not domain: + return None + + args = {} + args['name'] = domain + args['listall'] = True + 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: + return existing_tags['tag'] + return [] + + + def _delete_tags(self, resource, resource_type, tags): + existing_tags = resource['tags'] + tags_to_delete = [] + for existing_tag in existing_tags: + if existing_tag['key'] in tags: + if existing_tag['value'] != tags[key]: + tags_to_delete.append(existing_tag) + else: + tags_to_delete.append(existing_tag) + if tags_to_delete: + self.result['changed'] = True + if not self.module.check_mode: + args = {} + args['resourceids'] = resource['id'] + args['resourcetype'] = resource_type + args['tags'] = tags_to_delete + self.cs.deleteTags(**args) + + + def _create_tags(self, resource, resource_type, tags): + tags_to_create = [] + for i, tag_entry in enumerate(tags): + tag = { + 'key': tag_entry['key'], + 'value': tag_entry['value'], + } + tags_to_create.append(tag) + if tags_to_create: + self.result['changed'] = True + if not self.module.check_mode: + args = {} + args['resourceids'] = resource['id'] + args['resourcetype'] = resource_type + args['tags'] = tags_to_create + self.cs.createTags(**args) + + + def ensure_tags(self, resource, resource_type=None): + if not resource_type or not resource: + self.module.fail_json(msg="Error: Missing resource or resource_type for tags.") + + if 'tags' in resource: + tags = self.module.params.get('tags') + if tags is not None: + self._delete_tags(resource, resource_type, tags) + self._create_tags(resource, resource_type, tags) + resource['tags'] = self.get_tags(resource) + return resource + + + def get_capabilities(self, key=None): + if self.capabilities: + return self._get_by_key(key, self.capabilities) + capabilities = self.cs.listCapabilities() + self.capabilities = capabilities['capability'] + return self._get_by_key(key, self.capabilities) + + # TODO: rename to poll_job() def _poll_job(self, job=None, key=None): if 'jobid' in job: while True: diff --git a/lib/ansible/modules/core b/lib/ansible/modules/core index 0341ddd35e..85c8a892c8 160000 --- a/lib/ansible/modules/core +++ b/lib/ansible/modules/core @@ -1 +1 @@ -Subproject commit 0341ddd35ed5ff477ad5de2488d947255ce86259 +Subproject commit 85c8a892c80b92730831d95fa654ef6d35b0eca0 diff --git a/lib/ansible/modules/extras b/lib/ansible/modules/extras index 495ad450e5..2690f096a4 160000 --- a/lib/ansible/modules/extras +++ b/lib/ansible/modules/extras @@ -1 +1 @@ -Subproject commit 495ad450e53feb1cd26218dc68056cc34d1ea9ff +Subproject commit 2690f096a47646cd17db135648def88afc40d92c 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..426cf163fd --- /dev/null +++ b/plugins/inventory/cloudstack.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# (c) 2015, René Moser +# +# This file is part of Ansible, +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +###################################################################### + +""" +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 +import 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 = {} + if not hosts: + return 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) + 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) + if not hosts: + return data + 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() 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 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'" + ###################################################################