1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

cloudscale migrated to cloudscale_ch.cloud (#517)

This commit is contained in:
René Moser 2020-06-16 17:12:47 +02:00 committed by GitHub
parent e4522e1517
commit e5bb9dab9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 0 additions and 3657 deletions

14
.github/BOTMETA.yml vendored
View file

@ -131,9 +131,6 @@ files:
labels: infoblox networking
$module_utils/:
labels: module_utils
$module_utils/cloudscale.py:
maintainers: $team_cloudscale
labels: cloudscale
$module_utils/docker/:
maintainers: $team_docker
labels: cloud
@ -203,16 +200,6 @@ files:
authors: krsacme
$modules/cloud/centurylink/:
authors: clc-runner
$modules/cloud/cloudscale/cloudscale_floating_ip.py:
authors: gaudenz href
maintainers: resmo
$modules/cloud/cloudscale/cloudscale_server.py:
authors: gaudenz href resmo
$modules/cloud/cloudscale/cloudscale_server_group.py:
authors: href resmo
maintainers: gaudenz
$modules/cloud/cloudscale/cloudscale_volume.py:
authors: gaudenz href resmo
$modules/cloud/digital_ocean/digital_ocean.py:
authors: zbal
$modules/cloud/digital_ocean/:
@ -1383,7 +1370,6 @@ macros:
terminals: plugins/terminal
team_aix: MorrisA bcoca d-little flynn1973 gforster kairoaraujo marvin-sinister mator molekuul ramooncamacho wtcross
team_bsd: JoergFiedler MacLemon bcoca dch jasperla mekanix opoplawski overhacked tuxillo
team_cloudscale: gaudenz resmo
team_cyberark_conjur: jvanderhoof ryanprior
team_digital_ocean: BondAnthony mgregson
team_docker: DBendit WojciechowskiPiotr akshay196 danihodovic dariko felixfontein jwitko kassiansun tbouvet

View file

@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, René Moser <mail@renemoser.net>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class ModuleDocFragment(object):
# Standard cloudstack documentation fragment
DOCUMENTATION = '''
options:
api_token:
description:
- cloudscale.ch API token.
- This can also be passed in the C(CLOUDSCALE_API_TOKEN) environment variable.
type: str
api_timeout:
description:
- Timeout in seconds for calls to the cloudscale.ch API.
default: 30
type: int
notes:
- Instead of the api_token parameter the C(CLOUDSCALE_API_TOKEN) environment variable can be used.
- All operations are performed using the cloudscale.ch public API v1.
- "For details consult the full API documentation: U(https://www.cloudscale.ch/en/api/v1)."
- A valid API token is required for all operations. You can create as many tokens as you like using the cloudscale.ch control panel at
U(https://control.cloudscale.ch).
'''

View file

@ -1,211 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2018, Gaudenz Steinlin <gaudenz.steinlin@cloudscale.ch>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
---
name: cloudscale
plugin_type: inventory
author:
- Gaudenz Steinlin (@gaudenz)
short_description: cloudscale.ch inventory source
description:
- Get inventory hosts from cloudscale.ch API
- Uses an YAML configuration file ending with either I(cloudscale.yml) or I(cloudscale.yaml) to set parameter values (also see examples).
extends_documentation_fragment:
- constructed
options:
plugin:
description: |
Token that ensures this is a source file for the 'cloudscale'
plugin.
required: True
choices: ['cloudscale']
inventory_hostname:
description: |
What to register as the inventory hostname.
If set to 'uuid' the uuid of the server will be used and a
group will be created for the server name.
If set to 'name' the name of the server will be used unless
there are more than one server with the same name in which
case the 'uuid' logic will be used.
type: str
choices:
- name
- uuid
default: "name"
ansible_host:
description: |
Which IP address to register as the ansible_host. If the
requested value does not exist or this is set to 'none', no
ansible_host will be set.
type: str
choices:
- public_v4
- public_v6
- private
- none
default: public_v4
api_token:
description: cloudscale.ch API token
env:
- name: CLOUDSCALE_API_TOKEN
type: str
api_timeout:
description: Timeout in seconds for calls to the cloudscale.ch API.
default: 30
type: int
'''
EXAMPLES = r'''
# cloudscale.yml name ending file in YAML format
# Example command line: ansible-inventory --list -i inventory_cloudscale.yml
plugin: cloudscale
# Example grouping by tag key "project"
plugin: cloudscale
keyed_groups:
- prefix: project
key: cloudscale.tags.project
# Example grouping by key "operating_system" lowercased and prefixed with "os"
plugin: cloudscale
keyed_groups:
- prefix: os
key: cloudscale.image.operating_system | lower
'''
from collections import defaultdict
from json import loads
from ansible.errors import AnsibleError
from ansible_collections.community.general.plugins.module_utils.cloudscale import API_URL
from ansible.module_utils.urls import open_url
from ansible.inventory.group import to_safe_group_name
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
iface_type_map = {
'public_v4': ('public', 4),
'public_v6': ('public', 6),
'private': ('private', 4),
'none': (None, None),
}
class InventoryModule(BaseInventoryPlugin, Constructable):
NAME = 'community.general.cloudscale'
def _get_server_list(self):
# Get list of servers from cloudscale.ch API
response = open_url(
API_URL + '/servers',
headers={'Authorization': 'Bearer %s' % self._token}
)
return loads(response.read())
def verify_file(self, path):
'''
:param path: the path to the inventory config file
:return the contents of the config file
'''
if super(InventoryModule, self).verify_file(path):
if path.endswith(('cloudscale.yml', 'cloudscale.yaml')):
return True
self.display.debug(
"cloudscale inventory filename must end with 'cloudscale.yml' or 'cloudscale.yaml'"
)
return False
def parse(self, inventory, loader, path, cache=True):
super(InventoryModule, self).parse(inventory, loader, path)
self._read_config_data(path)
self._token = self.get_option('api_token')
if not self._token:
raise AnsibleError('Could not find an API token. Set the '
'CLOUDSCALE_API_TOKEN environment variable.')
inventory_hostname = self.get_option('inventory_hostname')
if inventory_hostname not in ('name', 'uuid'):
raise AnsibleError('Invalid value for option inventory_hostname: %s'
% inventory_hostname)
ansible_host = self.get_option('ansible_host')
if ansible_host not in iface_type_map:
raise AnsibleError('Invalid value for option ansible_host: %s'
% ansible_host)
# Merge servers with the same name
firstpass = defaultdict(list)
for server in self._get_server_list():
firstpass[server['name']].append(server)
# Add servers to inventory
for name, servers in firstpass.items():
if len(servers) == 1 and inventory_hostname == 'name':
self.inventory.add_host(name)
servers[0]['inventory_hostname'] = name
else:
# Two servers with the same name exist, create a group
# with this name and add the servers by UUID
group_name = to_safe_group_name(name)
if group_name not in self.inventory.groups:
self.inventory.add_group(group_name)
for server in servers:
self.inventory.add_host(server['uuid'], group_name)
server['inventory_hostname'] = server['uuid']
# Set variables
iface_type, iface_version = iface_type_map[ansible_host]
for server in servers:
hostname = server.pop('inventory_hostname')
if ansible_host != 'none':
addresses = [address['address']
for interface in server['interfaces']
for address in interface['addresses']
if interface['type'] == iface_type
and address['version'] == iface_version]
if len(addresses) > 0:
self.inventory.set_variable(
hostname,
'ansible_host',
addresses[0],
)
self.inventory.set_variable(
hostname,
'cloudscale',
server,
)
variables = self.inventory.hosts[hostname].get_vars()
# Set composed variables
self._set_composite_vars(
self.get_option('compose'),
variables,
hostname,
self.get_option('strict'),
)
# Add host to composed groups
self._add_host_to_composed_groups(
self.get_option('groups'),
variables,
hostname,
self.get_option('strict'),
)
# Add host to keyed groups
self._add_host_to_keyed_groups(
self.get_option('keyed_groups'),
variables,
hostname,
self.get_option('strict'),
)

View file

@ -1,132 +0,0 @@
# -*- coding: utf-8 -*-
#
# (c) 2017, Gaudenz Steinlin <gaudenz.steinlin@cloudscale.ch>
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.urls import fetch_url
from ansible.module_utils._text import to_text
API_URL = 'https://api.cloudscale.ch/v1/'
def cloudscale_argument_spec():
return dict(
api_token=dict(fallback=(env_fallback, ['CLOUDSCALE_API_TOKEN']),
no_log=True,
required=True,
type='str'),
api_timeout=dict(default=30, type='int'),
)
class AnsibleCloudscaleBase(object):
def __init__(self, module):
self._module = module
self._auth_header = {'Authorization': 'Bearer %s' % module.params['api_token']}
self._result = {
'changed': False,
'diff': dict(before=dict(), after=dict()),
}
def _get(self, api_call):
resp, info = fetch_url(self._module, API_URL + api_call,
headers=self._auth_header,
timeout=self._module.params['api_timeout'])
if info['status'] == 200:
return self._module.from_json(to_text(resp.read(), errors='surrogate_or_strict'))
elif info['status'] == 404:
return None
else:
self._module.fail_json(msg='Failure while calling the cloudscale.ch API with GET for '
'"%s".' % api_call, fetch_url_info=info)
def _post_or_patch(self, api_call, method, data):
# This helps with tags when we have the full API resource href to update.
if API_URL not in api_call:
api_endpoint = API_URL + api_call
else:
api_endpoint = api_call
headers = self._auth_header.copy()
if data is not None:
# Sanitize data dictionary
# Deepcopy: Duplicate the data object for iteration, because
# iterating an object and changing it at the same time is insecure
for k, v in deepcopy(data).items():
if v is None:
del data[k]
data = self._module.jsonify(data)
headers['Content-type'] = 'application/json'
resp, info = fetch_url(self._module,
api_endpoint,
headers=headers,
method=method,
data=data,
timeout=self._module.params['api_timeout'])
if info['status'] in (200, 201):
return self._module.from_json(to_text(resp.read(), errors='surrogate_or_strict'))
elif info['status'] == 204:
return None
else:
self._module.fail_json(msg='Failure while calling the cloudscale.ch API with %s for '
'"%s".' % (method, api_call), fetch_url_info=info)
def _post(self, api_call, data=None):
return self._post_or_patch(api_call, 'POST', data)
def _patch(self, api_call, data=None):
return self._post_or_patch(api_call, 'PATCH', data)
def _delete(self, api_call):
resp, info = fetch_url(self._module,
API_URL + api_call,
headers=self._auth_header,
method='DELETE',
timeout=self._module.params['api_timeout'])
if info['status'] == 204:
return None
else:
self._module.fail_json(msg='Failure while calling the cloudscale.ch API with DELETE for '
'"%s".' % api_call, fetch_url_info=info)
def _param_updated(self, key, resource):
param = self._module.params.get(key)
if param is None:
return False
if resource and key in resource:
if param != resource[key]:
self._result['changed'] = True
patch_data = {
key: param
}
self._result['diff']['before'].update({key: resource[key]})
self._result['diff']['after'].update(patch_data)
if not self._module.check_mode:
href = resource.get('href')
if not href:
self._module.fail_json(msg='Unable to update %s, no href found.' % key)
self._patch(href, patch_data)
return True
return False
def get_result(self, resource):
if resource:
for k, v in resource.items():
self._result[k] = v
return self._result

View file

@ -1,296 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017, Gaudenz Steinlin <gaudenz.steinlin@cloudscale.ch>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: cloudscale_floating_ip
short_description: Manages floating IPs on the cloudscale.ch IaaS service
description:
- Create, assign and delete floating IPs on the cloudscale.ch IaaS service.
notes:
- To create a new floating IP at least the C(ip_version) and C(server) options are required.
- Once a floating_ip is created all parameters except C(server) are read-only.
- It's not possible to request a floating IP without associating it with a server at the same time.
- This module requires the ipaddress python library. This library is included in Python since version 3.3. It is available as a
module on PyPI for earlier versions.
author:
- Gaudenz Steinlin (@gaudenz)
- Denis Krienbühl (@href)
options:
state:
description:
- State of the floating IP.
default: present
choices: [ present, absent ]
type: str
ip:
description:
- Floating IP address to change.
- Required to assign the IP to a different server or if I(state) is absent.
aliases: [ network ]
type: str
ip_version:
description:
- IP protocol version of the floating IP.
choices: [ 4, 6 ]
type: int
server:
description:
- UUID of the server assigned to this floating IP.
- Required unless I(state) is absent.
type: str
type:
description:
- The type of the floating IP.
choices: [ regional, global ]
type: str
default: regional
region:
description:
- Region in which the floating IP resides (e.g. C(lgp) or C(rma)).
If omitted, the region of the project default zone is used.
This parameter must be omitted if I(type) is set to C(global).
type: str
version_added: '0.2.0'
prefix_length:
description:
- Only valid if I(ip_version) is 6.
- Prefix length for the IPv6 network. Currently only a prefix of /56 can be requested. If no I(prefix_length) is present, a
single address is created.
choices: [ 56 ]
type: int
reverse_ptr:
description:
- Reverse PTR entry for this address.
- You cannot set a reverse PTR entry for IPv6 floating networks. Reverse PTR entries are only allowed for single addresses.
type: str
extends_documentation_fragment:
- community.general.cloudscale
'''
EXAMPLES = '''
# Request a new floating IP
- name: Request a floating IP
cloudscale_floating_ip:
ip_version: 4
server: 47cec963-fcd2-482f-bdb6-24461b2d47b1
reverse_ptr: my-server.example.com
api_token: xxxxxx
register: floating_ip
# Assign an existing floating IP to a different server
- name: Move floating IP to backup server
cloudscale_floating_ip:
ip: 192.0.2.123
server: ea3b39a3-77a8-4d0b-881d-0bb00a1e7f48
api_token: xxxxxx
# Request a new floating IPv6 network
- name: Request a floating IP
cloudscale_floating_ip:
ip_version: 6
prefix_length: 56
server: 47cec963-fcd2-482f-bdb6-24461b2d47b1
api_token: xxxxxx
region: lpg1
register: floating_ip
# Assign an existing floating network to a different server
- name: Move floating IP to backup server
cloudscale_floating_ip:
ip: '{{ floating_ip.network | ip }}'
server: ea3b39a3-77a8-4d0b-881d-0bb00a1e7f48
api_token: xxxxxx
# Release a floating IP
- name: Release floating IP
cloudscale_floating_ip:
ip: 192.0.2.123
state: absent
api_token: xxxxxx
'''
RETURN = '''
href:
description: The API URL to get details about this floating IP.
returned: success when state == present
type: str
sample: https://api.cloudscale.ch/v1/floating-ips/2001:db8::cafe
network:
description: The CIDR notation of the network that is routed to your server.
returned: success when state == present
type: str
sample: 2001:db8::cafe/128
next_hop:
description: Your floating IP is routed to this IP address.
returned: success when state == present
type: str
sample: 2001:db8:dead:beef::42
reverse_ptr:
description: The reverse pointer for this floating IP address.
returned: success when state == present
type: str
sample: 185-98-122-176.cust.cloudscale.ch
server:
description: The floating IP is routed to this server.
returned: success when state == present
type: str
sample: 47cec963-fcd2-482f-bdb6-24461b2d47b1
ip:
description: The floating IP address or network. This is always present and used to identify floating IPs after creation.
returned: success
type: str
sample: 185.98.122.176
region:
description: The region of the floating IP.
returned: success when state == present
type: dict
sample: {'slug': 'lpg'}
version_added: '0.2.0'
state:
description: The current status of the floating IP.
returned: success
type: str
sample: present
'''
import traceback
IPADDRESS_IMP_ERR = None
try:
from ipaddress import ip_network
HAS_IPADDRESS = True
except ImportError:
IPADDRESS_IMP_ERR = traceback.format_exc()
HAS_IPADDRESS = False
from ansible.module_utils.basic import AnsibleModule, env_fallback, missing_required_lib
from ansible_collections.community.general.plugins.module_utils.cloudscale import AnsibleCloudscaleBase, cloudscale_argument_spec
class AnsibleCloudscaleFloatingIP(AnsibleCloudscaleBase):
def __init__(self, module):
super(AnsibleCloudscaleFloatingIP, self).__init__(module)
# Initialize info dict
# Set state to absent, will be updated by self.update_info()
self.info = {'state': 'absent'}
if self._module.params['ip']:
self.update_info()
@staticmethod
def _resp2info(resp):
# If the API response has some content, the floating IP must exist
resp['state'] = 'present'
# Add the IP address to the response, otherwise handling get's to complicated as this
# has to be converted from the network all the time.
resp['ip'] = str(ip_network(resp['network']).network_address)
# Replace the server with just the UUID, the href to the server is useless and just makes
# things more complicated
if resp['server'] is not None:
resp['server'] = resp['server']['uuid']
return resp
def update_info(self):
resp = self._get('floating-ips/' + self._module.params['ip'])
if resp:
self.info = self._resp2info(resp)
else:
self.info = {'ip': self._module.params['ip'],
'state': 'absent'}
def request_floating_ip(self):
params = self._module.params
# check for required parameters to request a floating IP
missing_parameters = []
for p in ('ip_version', 'server'):
if p not in params or not params[p]:
missing_parameters.append(p)
if len(missing_parameters) > 0:
self._module.fail_json(msg='Missing required parameter(s) to request a floating IP: %s.' %
' '.join(missing_parameters))
data = {'ip_version': params['ip_version'],
'server': params['server']}
for p in ('prefix_length', 'reverse_ptr', 'type', 'region'):
if params[p]:
data[p] = params[p]
self.info = self._resp2info(self._post('floating-ips', data))
def release_floating_ip(self):
self._delete('floating-ips/%s' % self._module.params['ip'])
self.info = {'ip': self.info['ip'], 'state': 'absent'}
def update_floating_ip(self):
params = self._module.params
if 'server' not in params or not params['server']:
self._module.fail_json(msg='Missing required parameter to update a floating IP: server.')
self.info = self._resp2info(self._post('floating-ips/%s' % params['ip'], {'server': params['server']}))
def main():
argument_spec = cloudscale_argument_spec()
argument_spec.update(dict(
state=dict(default='present', choices=('present', 'absent'), type='str'),
ip=dict(aliases=('network', ), type='str'),
ip_version=dict(choices=(4, 6), type='int'),
server=dict(type='str'),
type=dict(type='str', choices=('regional', 'global'), default='regional'),
region=dict(type='str'),
prefix_length=dict(choices=(56,), type='int'),
reverse_ptr=dict(type='str'),
))
module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=(('ip', 'ip_version'),),
supports_check_mode=True,
)
if not HAS_IPADDRESS:
module.fail_json(msg=missing_required_lib('ipaddress'), exception=IPADDRESS_IMP_ERR)
target_state = module.params['state']
target_server = module.params['server']
floating_ip = AnsibleCloudscaleFloatingIP(module)
current_state = floating_ip.info['state']
current_server = floating_ip.info['server'] if 'server' in floating_ip.info else None
if module.check_mode:
module.exit_json(changed=not target_state == current_state or
(current_state == 'present' and current_server != target_server),
**floating_ip.info)
changed = False
if current_state == 'absent' and target_state == 'present':
floating_ip.request_floating_ip()
changed = True
elif current_state == 'present' and target_state == 'absent':
floating_ip.release_floating_ip()
changed = True
elif current_state == 'present' and current_server != target_server:
floating_ip.update_floating_ip()
changed = True
module.exit_json(changed=changed, **floating_ip.info)
if __name__ == '__main__':
main()

View file

@ -1,549 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2017, Gaudenz Steinlin <gaudenz.steinlin@cloudscale.ch>
# Copyright: (c) 2019, René Moser <mail@renemoser.net>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: cloudscale_server
short_description: Manages servers on the cloudscale.ch IaaS service
description:
- Create, update, start, stop and delete servers on the cloudscale.ch IaaS service.
notes:
- Since version 2.8, I(uuid) and I(name) or not mutually exclusive anymore.
- If I(uuid) option is provided, it takes precedence over I(name) for server selection. This allows to update the server's name.
- If no I(uuid) option is provided, I(name) is used for server selection. If more than one server with this name exists, execution is aborted.
- Only the I(name) and I(flavor) are evaluated for the update.
- The option I(force=true) must be given to allow the reboot of existing running servers for applying the changes.
author:
- Gaudenz Steinlin (@gaudenz)
- René Moser (@resmo)
- Denis Krienbühl (@href)
options:
state:
description:
- State of the server.
choices: [ running, stopped, absent ]
default: running
type: str
name:
description:
- Name of the Server.
- Either I(name) or I(uuid) are required.
type: str
uuid:
description:
- UUID of the server.
- Either I(name) or I(uuid) are required.
type: str
flavor:
description:
- Flavor of the server.
type: str
image:
description:
- Image used to create the server.
type: str
zone:
description:
- Zone in which the server resides (e.g. C(lgp1) or C(rma1)).
type: str
version_added: '0.2.0'
volume_size_gb:
description:
- Size of the root volume in GB.
default: 10
type: int
bulk_volume_size_gb:
description:
- Size of the bulk storage volume in GB.
- No bulk storage volume if not set.
type: int
ssh_keys:
description:
- List of SSH public keys.
- Use the full content of your .pub file here.
type: list
password:
description:
- Password for the server.
type: str
use_public_network:
description:
- Attach a public network interface to the server.
default: yes
type: bool
use_private_network:
description:
- Attach a private network interface to the server.
default: no
type: bool
use_ipv6:
description:
- Enable IPv6 on the public network interface.
default: yes
type: bool
anti_affinity_with:
description:
- UUID of another server to create an anti-affinity group with.
- Mutually exclusive with I(server_groups).
- Deprecated, removed in community.general 2.0.0.
type: str
server_groups:
description:
- List of UUID or names of server groups.
- Mutually exclusive with I(anti_affinity_with).
type: list
user_data:
description:
- Cloud-init configuration (cloud-config) data to use for the server.
type: str
force:
description:
- Allow to stop the running server for updating if necessary.
default: no
type: bool
tags:
description:
- Tags assosiated with the servers. Set this to C({}) to clear any tags.
type: dict
extends_documentation_fragment:
- community.general.cloudscale
'''
EXAMPLES = '''
# Create and start a server with an existing server group (shiny-group)
- name: Start cloudscale.ch server
cloudscale_server:
name: my-shiny-cloudscale-server
image: debian-8
flavor: flex-4
ssh_keys: ssh-rsa XXXXXXXXXX...XXXX ansible@cloudscale
server_groups: shiny-group
zone: lpg1
use_private_network: True
bulk_volume_size_gb: 100
api_token: xxxxxx
# Start another server in anti-affinity (server group shiny-group)
- name: Start second cloudscale.ch server
cloudscale_server:
name: my-other-shiny-server
image: ubuntu-16.04
flavor: flex-8
ssh_keys: ssh-rsa XXXXXXXXXXX ansible@cloudscale
server_groups: shiny-group
zone: lpg1
api_token: xxxxxx
# Force to update the flavor of a running server
- name: Start cloudscale.ch server
cloudscale_server:
name: my-shiny-cloudscale-server
image: debian-8
flavor: flex-8
force: yes
ssh_keys: ssh-rsa XXXXXXXXXX...XXXX ansible@cloudscale
use_private_network: True
bulk_volume_size_gb: 100
api_token: xxxxxx
register: server1
# Stop the first server
- name: Stop my first server
cloudscale_server:
uuid: '{{ server1.uuid }}'
state: stopped
api_token: xxxxxx
# Delete my second server
- name: Delete my second server
cloudscale_server:
name: my-other-shiny-server
state: absent
api_token: xxxxxx
# Start a server and wait for the SSH host keys to be generated
- name: Start server and wait for SSH host keys
cloudscale_server:
name: my-cloudscale-server-with-ssh-key
image: debian-8
flavor: flex-4
ssh_keys: ssh-rsa XXXXXXXXXXX ansible@cloudscale
api_token: xxxxxx
register: server
until: server.ssh_fingerprints is defined and server.ssh_fingerprints
retries: 60
delay: 2
'''
RETURN = '''
href:
description: API URL to get details about this server
returned: success when not state == absent
type: str
sample: https://api.cloudscale.ch/v1/servers/cfde831a-4e87-4a75-960f-89b0148aa2cc
uuid:
description: The unique identifier for this server
returned: success
type: str
sample: cfde831a-4e87-4a75-960f-89b0148aa2cc
name:
description: The display name of the server
returned: success
type: str
sample: its-a-me-mario.cloudscale.ch
state:
description: The current status of the server
returned: success
type: str
sample: running
flavor:
description: The flavor that has been used for this server
returned: success when not state == absent
type: dict
sample: { "slug": "flex-4", "name": "Flex-4", "vcpu_count": 2, "memory_gb": 4 }
image:
description: The image used for booting this server
returned: success when not state == absent
type: dict
sample: { "default_username": "ubuntu", "name": "Ubuntu 18.04 LTS", "operating_system": "Ubuntu", "slug": "ubuntu-18.04" }
zone:
description: The zone used for booting this server
returned: success when not state == absent
type: dict
sample: { 'slug': 'lpg1' }
version_added: '0.2.0'
volumes:
description: List of volumes attached to the server
returned: success when not state == absent
type: list
sample: [ {"type": "ssd", "device": "/dev/vda", "size_gb": "50"} ]
interfaces:
description: List of network ports attached to the server
returned: success when not state == absent
type: list
sample: [ { "type": "public", "addresses": [ ... ] } ]
ssh_fingerprints:
description: A list of SSH host key fingerprints. Will be null until the host keys could be retrieved from the server.
returned: success when not state == absent
type: list
sample: ["ecdsa-sha2-nistp256 SHA256:XXXX", ... ]
ssh_host_keys:
description: A list of SSH host keys. Will be null until the host keys could be retrieved from the server.
returned: success when not state == absent
type: list
sample: ["ecdsa-sha2-nistp256 XXXXX", ... ]
anti_affinity_with:
description:
- List of servers in the same anti-affinity group
- Deprecated, removed in community.general 2.0.0.
returned: success when not state == absent
type: list
sample: []
server_groups:
description: List of server groups
returned: success when not state == absent
type: list
sample: [ {"href": "https://api.cloudscale.ch/v1/server-groups/...", "uuid": "...", "name": "db-group"} ]
tags:
description: Tags assosiated with the volume.
returned: success
type: dict
sample: { 'project': 'my project' }
'''
from datetime import datetime, timedelta
from time import sleep
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.cloudscale import AnsibleCloudscaleBase, cloudscale_argument_spec
ALLOWED_STATES = ('running',
'stopped',
'absent',
)
class AnsibleCloudscaleServer(AnsibleCloudscaleBase):
def __init__(self, module):
super(AnsibleCloudscaleServer, self).__init__(module)
# Initialize server dictionary
self._info = {}
def _init_server_container(self):
return {
'uuid': self._module.params.get('uuid') or self._info.get('uuid'),
'name': self._module.params.get('name') or self._info.get('name'),
'state': 'absent',
}
def _get_server_info(self, refresh=False):
if self._info and not refresh:
return self._info
self._info = self._init_server_container()
uuid = self._info.get('uuid')
if uuid is not None:
server_info = self._get('servers/%s' % uuid)
if server_info:
self._info = self._transform_state(server_info)
else:
name = self._info.get('name')
if name is not None:
servers = self._get('servers') or []
matching_server = []
for server in servers:
if server['name'] == name:
matching_server.append(server)
if len(matching_server) == 1:
self._info = self._transform_state(matching_server[0])
elif len(matching_server) > 1:
self._module.fail_json(msg="More than one server with name '%s' exists. "
"Use the 'uuid' parameter to identify the server." % name)
return self._info
@staticmethod
def _transform_state(server):
if 'status' in server:
server['state'] = server['status']
del server['status']
else:
server['state'] = 'absent'
return server
def _wait_for_state(self, states):
start = datetime.now()
timeout = self._module.params['api_timeout'] * 2
while datetime.now() - start < timedelta(seconds=timeout):
server_info = self._get_server_info(refresh=True)
if server_info.get('state') in states:
return server_info
sleep(1)
# Timeout succeeded
if server_info.get('name') is not None:
msg = "Timeout while waiting for a state change on server %s to states %s. " \
"Current state is %s." % (server_info.get('name'), states, server_info.get('state'))
else:
name_uuid = self._module.params.get('name') or self._module.params.get('uuid')
msg = 'Timeout while waiting to find the server %s' % name_uuid
self._module.fail_json(msg=msg)
def _start_stop_server(self, server_info, target_state="running", ignore_diff=False):
actions = {
'stopped': 'stop',
'running': 'start',
}
server_state = server_info.get('state')
if server_state != target_state:
self._result['changed'] = True
if not ignore_diff:
self._result['diff']['before'].update({
'state': server_info.get('state'),
})
self._result['diff']['after'].update({
'state': target_state,
})
if not self._module.check_mode:
self._post('servers/%s/%s' % (server_info['uuid'], actions[target_state]))
server_info = self._wait_for_state((target_state, ))
return server_info
def _update_param(self, param_key, server_info, requires_stop=False):
param_value = self._module.params.get(param_key)
if param_value is None:
return server_info
if 'slug' in server_info[param_key]:
server_v = server_info[param_key]['slug']
else:
server_v = server_info[param_key]
if server_v != param_value:
# Set the diff output
self._result['diff']['before'].update({param_key: server_v})
self._result['diff']['after'].update({param_key: param_value})
if server_info.get('state') == "running":
if requires_stop and not self._module.params.get('force'):
self._module.warn("Some changes won't be applied to running servers. "
"Use force=yes to allow the server '%s' to be stopped/started." % server_info['name'])
return server_info
# Either the server is stopped or change is forced
self._result['changed'] = True
if not self._module.check_mode:
if requires_stop:
self._start_stop_server(server_info, target_state="stopped", ignore_diff=True)
patch_data = {
param_key: param_value,
}
# Response is 204: No Content
self._patch('servers/%s' % server_info['uuid'], patch_data)
# State changes to "changing" after update, waiting for stopped/running
server_info = self._wait_for_state(('stopped', 'running'))
return server_info
def _get_server_group_ids(self):
server_group_params = self._module.params['server_groups']
if not server_group_params:
return None
matching_group_names = []
results = []
server_groups = self._get('server-groups')
for server_group in server_groups:
if server_group['uuid'] in server_group_params:
results.append(server_group['uuid'])
server_group_params.remove(server_group['uuid'])
elif server_group['name'] in server_group_params:
results.append(server_group['uuid'])
server_group_params.remove(server_group['name'])
# Remember the names found
matching_group_names.append(server_group['name'])
# Names are not unique, verify if name already found in previous iterations
elif server_group['name'] in matching_group_names:
self._module.fail_json(msg="More than one server group with name exists: '%s'. "
"Use the 'uuid' parameter to identify the server group." % server_group['name'])
if server_group_params:
self._module.fail_json(msg="Server group name or UUID not found: %s" % ', '.join(server_group_params))
return results
def _create_server(self, server_info):
self._result['changed'] = True
data = deepcopy(self._module.params)
for i in ('uuid', 'state', 'force', 'api_timeout', 'api_token'):
del data[i]
data['server_groups'] = self._get_server_group_ids()
self._result['diff']['before'] = self._init_server_container()
self._result['diff']['after'] = deepcopy(data)
if not self._module.check_mode:
self._post('servers', data)
server_info = self._wait_for_state(('running', ))
return server_info
def _update_server(self, server_info):
previous_state = server_info.get('state')
# The API doesn't support to update server groups.
# Show a warning to the user if the desired state does not match.
desired_server_group_ids = self._get_server_group_ids()
if desired_server_group_ids is not None:
current_server_group_ids = [grp['uuid'] for grp in server_info['server_groups']]
if desired_server_group_ids != current_server_group_ids:
self._module.warn("Server groups can not be mutated, server needs redeployment to change groups.")
server_info = self._update_param('flavor', server_info, requires_stop=True)
server_info = self._update_param('name', server_info)
server_info = self._update_param('tags', server_info)
if previous_state == "running":
server_info = self._start_stop_server(server_info, target_state="running", ignore_diff=True)
return server_info
def present_server(self):
server_info = self._get_server_info()
if server_info.get('state') != "absent":
# If target state is stopped, stop before an potential update and force would not be required
if self._module.params.get('state') == "stopped":
server_info = self._start_stop_server(server_info, target_state="stopped")
server_info = self._update_server(server_info)
if self._module.params.get('state') == "running":
server_info = self._start_stop_server(server_info, target_state="running")
else:
server_info = self._create_server(server_info)
server_info = self._start_stop_server(server_info, target_state=self._module.params.get('state'))
return server_info
def absent_server(self):
server_info = self._get_server_info()
if server_info.get('state') != "absent":
self._result['changed'] = True
self._result['diff']['before'] = deepcopy(server_info)
self._result['diff']['after'] = self._init_server_container()
if not self._module.check_mode:
self._delete('servers/%s' % server_info['uuid'])
server_info = self._wait_for_state(('absent', ))
return server_info
def main():
argument_spec = cloudscale_argument_spec()
argument_spec.update(dict(
state=dict(default='running', choices=ALLOWED_STATES),
name=dict(),
uuid=dict(),
flavor=dict(),
image=dict(),
zone=dict(),
volume_size_gb=dict(type='int', default=10),
bulk_volume_size_gb=dict(type='int'),
ssh_keys=dict(type='list'),
password=dict(no_log=True),
use_public_network=dict(type='bool', default=True),
use_private_network=dict(type='bool', default=False),
use_ipv6=dict(type='bool', default=True),
anti_affinity_with=dict(removed_in_version='2.0.0', removed_from_collection='community.general'), # was Ansible 2.11
server_groups=dict(type='list'),
user_data=dict(),
force=dict(type='bool', default=False),
tags=dict(type='dict'),
))
module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=(('name', 'uuid'),),
mutually_exclusive=(('anti_affinity_with', 'server_groups'),),
supports_check_mode=True,
)
cloudscale_server = AnsibleCloudscaleServer(module)
if module.params['state'] == "absent":
server = cloudscale_server.absent_server()
else:
server = cloudscale_server.present_server()
result = cloudscale_server.get_result(server)
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -1,232 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019, René Moser <mail@renemoser.net>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: cloudscale_server_group
short_description: Manages server groups on the cloudscale.ch IaaS service
description:
- Create, update and remove server groups.
author:
- René Moser (@resmo)
- Denis Krienbühl (@href)
options:
name:
description:
- Name of the server group.
- Either I(name) or I(uuid) is required. These options are mutually exclusive.
type: str
uuid:
description:
- UUID of the server group.
- Either I(name) or I(uuid) is required. These options are mutually exclusive.
type: str
type:
description:
- Type of the server group.
default: anti-affinity
type: str
zone:
description:
- Zone slug of the server group (e.g. C(lgp1) or C(rma1)).
type: str
version_added: '0.2.0'
state:
description:
- State of the server group.
choices: [ present, absent ]
default: present
type: str
tags:
description:
- Tags assosiated with the server groups. Set this to C({}) to clear any tags.
type: dict
extends_documentation_fragment:
- community.general.cloudscale
'''
EXAMPLES = '''
---
- name: Ensure server group exists
cloudscale_server_group:
name: my-name
type: anti-affinity
api_token: xxxxxx
- name: Ensure server group in a specific zone
cloudscale_server_group:
name: my-rma-group
type: anti-affinity
zone: lpg1
api_token: xxxxxx
- name: Ensure a server group is absent
cloudscale_server_group:
name: my-name
state: absent
api_token: xxxxxx
'''
RETURN = '''
---
href:
description: API URL to get details about this server group
returned: if available
type: str
sample: https://api.cloudscale.ch/v1/server-group/cfde831a-4e87-4a75-960f-89b0148aa2cc
uuid:
description: The unique identifier for this server
returned: always
type: str
sample: cfde831a-4e87-4a75-960f-89b0148aa2cc
name:
description: The display name of the server group
returned: always
type: str
sample: load balancers
type:
description: The type the server group
returned: if available
type: str
sample: anti-affinity
zone:
description: The zone of the server group
returned: success
type: dict
sample: { 'slug': 'rma1' }
version_added: '0.2.0'
servers:
description: A list of servers that are part of the server group.
returned: if available
type: list
sample: []
state:
description: State of the server group.
returned: always
type: str
sample: present
tags:
description: Tags assosiated with the server group.
returned: success
type: dict
sample: { 'project': 'my project' }
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.cloudscale import AnsibleCloudscaleBase, cloudscale_argument_spec
class AnsibleCloudscaleServerGroup(AnsibleCloudscaleBase):
def __init__(self, module, namespace):
super(AnsibleCloudscaleServerGroup, self).__init__(module)
self._info = {}
def _init_container(self):
return {
'uuid': self._module.params.get('uuid') or self._info.get('uuid'),
'name': self._module.params.get('name') or self._info.get('name'),
'state': 'absent',
}
def _create_server_group(self, server_group):
self._module.fail_on_missing_params(['name'])
self._result['changed'] = True
data = {
'name': self._module.params.get('name'),
'type': self._module.params.get('type'),
'zone': self._module.params.get('zone'),
'tags': self._module.params.get('tags'),
}
if not self._module.check_mode:
server_group = self._post('server-groups', data)
return server_group
def _update_server_group(self, server_group):
updated = self._param_updated('name', server_group)
updated = self._param_updated('tags', server_group) or updated
# Refresh if resource was updated in live mode
if updated and not self._module.check_mode:
server_group = self.get_server_group()
return server_group
def get_server_group(self):
self._info = self._init_container()
uuid = self._info.get('uuid')
if uuid is not None:
server_group = self._get('server-groups/%s' % uuid)
if server_group:
self._info.update(server_group)
self._info.update(dict(state='present'))
else:
name = self._info.get('name')
matching_server_groups = []
for server_group in self._get('server-groups'):
if server_group['name'] == name:
matching_server_groups.append(server_group)
if len(matching_server_groups) > 1:
self._module.fail_json(msg="More than one server group with name exists: '%s'. "
"Use the 'uuid' parameter to identify the server group." % name)
elif len(matching_server_groups) == 1:
self._info.update(matching_server_groups[0])
self._info.update(dict(state='present'))
return self._info
def present_group(self):
server_group = self.get_server_group()
if server_group.get('state') == 'absent':
server_group = self._create_server_group(server_group)
else:
server_group = self._update_server_group(server_group)
return server_group
def absent_group(self):
server_group = self.get_server_group()
if server_group.get('state') != 'absent':
self._result['changed'] = True
if not self._module.check_mode:
self._delete('server-groups/%s' % server_group['uuid'])
return server_group
def main():
argument_spec = cloudscale_argument_spec()
argument_spec.update(dict(
name=dict(),
uuid=dict(),
type=dict(default='anti-affinity'),
zone=dict(),
tags=dict(type='dict'),
state=dict(default='present', choices=['absent', 'present']),
))
module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=(('name', 'uuid'),),
supports_check_mode=True,
)
cloudscale_server_group = AnsibleCloudscaleServerGroup(module, 'cloudscale_server_group')
if module.params['state'] == 'absent':
server_group = cloudscale_server_group.absent_group()
else:
server_group = cloudscale_server_group.present_group()
result = cloudscale_server_group.get_result(server_group)
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -1,300 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2018, Gaudenz Steinlin <gaudenz.steinlin@cloudscale.ch>
# Copyright (c) 2019, René Moser <mail@renemoser.net>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: cloudscale_volume
short_description: Manages volumes on the cloudscale.ch IaaS service.
description:
- Create, attach/detach, update and delete volumes on the cloudscale.ch IaaS service.
notes:
- To create a new volume at least the I(name) and I(size_gb) options
are required.
- A volume can be created and attached to a server in the same task.
author:
- Gaudenz Steinlin (@gaudenz)
- René Moser (@resmo)
- Denis Krienbühl (@href)
options:
state:
description:
- State of the volume.
default: present
choices: [ present, absent ]
type: str
name:
description:
- Name of the volume. Either name or UUID must be present to change an
existing volume.
type: str
uuid:
description:
- UUID of the volume. Either name or UUID must be present to change an
existing volume.
type: str
size_gb:
description:
- Size of the volume in GB.
type: int
type:
description:
- Type of the volume. Cannot be changed after creating the volume.
Defaults to C(ssd) on volume creation.
choices: [ ssd, bulk ]
type: str
zone:
description:
- Zone in which the volume resides (e.g. C(lgp1) or C(rma1)). Cannot be
changed after creating the volume. Defaults to the project default zone.
type: str
version_added: '0.2.0'
server_uuids:
description:
- UUIDs of the servers this volume is attached to. Set this to C([]) to
detach the volume. Currently a volume can only be attached to a
single server.
aliases: [ server_uuid ]
type: list
tags:
description:
- Tags associated with the volume. Set this to C({}) to clear any tags.
type: dict
extends_documentation_fragment:
- community.general.cloudscale
'''
EXAMPLES = '''
# Create a new SSD volume
- name: Create an SSD volume
cloudscale_volume:
name: my_ssd_volume
zone: 'lpg1'
size_gb: 50
api_token: xxxxxx
register: my_ssd_volume
# Attach an existing volume to a server
- name: Attach volume to server
cloudscale_volume:
uuid: my_ssd_volume.uuid
server_uuids:
- ea3b39a3-77a8-4d0b-881d-0bb00a1e7f48
api_token: xxxxxx
# Create and attach a volume to a server
- name: Create and attach volume to server
cloudscale_volume:
name: my_ssd_volume
zone: 'lpg1'
size_gb: 50
server_uuids:
- ea3b39a3-77a8-4d0b-881d-0bb00a1e7f48
api_token: xxxxxx
# Detach volume from server
- name: Detach volume from server
cloudscale_volume:
uuid: my_ssd_volume.uuid
server_uuids: []
api_token: xxxxxx
# Delete a volume
- name: Delete volume
cloudscale_volume:
name: my_ssd_volume
state: absent
api_token: xxxxxx
'''
RETURN = '''
href:
description: The API URL to get details about this volume.
returned: state == present
type: str
sample: https://api.cloudscale.ch/v1/volumes/2db69ba3-1864-4608-853a-0771b6885a3a
uuid:
description: The unique identifier for this volume.
returned: state == present
type: str
sample: 2db69ba3-1864-4608-853a-0771b6885a3a
name:
description: The display name of the volume.
returned: state == present
type: str
sample: my_ssd_volume
size_gb:
description: The size of the volume in GB.
returned: state == present
type: str
sample: 50
type:
description: The type of the volume.
returned: state == present
type: str
sample: bulk
zone:
description: The zone of the volume.
returned: state == present
type: dict
sample: {'slug': 'lpg1'}
version_added: '0.2.0'
server_uuids:
description: The UUIDs of the servers this volume is attached to.
returned: state == present
type: list
sample: ['47cec963-fcd2-482f-bdb6-24461b2d47b1']
state:
description: The current status of the volume.
returned: success
type: str
sample: present
tags:
description: Tags associated with the volume.
returned: state == present
type: dict
sample: { 'project': 'my project' }
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.cloudscale import (AnsibleCloudscaleBase,
cloudscale_argument_spec,
)
class AnsibleCloudscaleVolume(AnsibleCloudscaleBase):
def __init__(self, module):
super(AnsibleCloudscaleVolume, self).__init__(module)
self._info = {}
def _init_container(self):
return {
'uuid': self._module.params.get('uuid') or self._info.get('uuid'),
'name': self._module.params.get('name') or self._info.get('name'),
'state': 'absent',
}
def _create(self, volume):
# Fail when missing params for creation
self._module.fail_on_missing_params(['name', 'size_gb'])
# Fail if a user uses a UUID and state=present but the volume was not found.
if self._module.params.get('uuid'):
self._module.fail_json(msg="The volume with UUID '%s' was not found "
"and we would create a new one with different UUID, "
"this is probably not want you have asked for." % self._module.params.get('uuid'))
self._result['changed'] = True
data = {
'name': self._module.params.get('name'),
'type': self._module.params.get('type'),
'zone': self._module.params.get('zone'),
'size_gb': self._module.params.get('size_gb') or 'ssd',
'server_uuids': self._module.params.get('server_uuids') or [],
'tags': self._module.params.get('tags'),
}
if not self._module.check_mode:
volume = self._post('volumes', data)
return volume
def _update(self, volume):
update_params = (
'name',
'size_gb',
'server_uuids',
'tags',
)
updated = False
for param in update_params:
updated = self._param_updated(param, volume) or updated
# Refresh if resource was updated in live mode
if updated and not self._module.check_mode:
volume = self.get_volume()
return volume
def get_volume(self):
self._info = self._init_container()
uuid = self._info.get('uuid')
if uuid is not None:
volume = self._get('volumes/%s' % uuid)
if volume:
self._info.update(volume)
self._info['state'] = 'present'
else:
name = self._info.get('name')
matching_volumes = []
for volume in self._get('volumes'):
if volume['name'] == name:
matching_volumes.append(volume)
if len(matching_volumes) > 1:
self._module.fail_json(msg="More than one volume with name exists: '%s'. "
"Use the 'uuid' parameter to identify the volume." % name)
elif len(matching_volumes) == 1:
self._info.update(matching_volumes[0])
self._info['state'] = 'present'
return self._info
def present(self):
volume = self.get_volume()
if volume.get('state') == 'absent':
volume = self._create(volume)
else:
volume = self._update(volume)
return volume
def absent(self):
volume = self.get_volume()
if volume.get('state') != 'absent':
self._result['changed'] = True
if not self._module.check_mode:
volume['state'] = "absent"
self._delete('volumes/%s' % volume['uuid'])
return volume
def main():
argument_spec = cloudscale_argument_spec()
argument_spec.update(dict(
state=dict(default='present', choices=('present', 'absent')),
name=dict(),
uuid=dict(),
zone=dict(),
size_gb=dict(type='int'),
type=dict(choices=('ssd', 'bulk')),
server_uuids=dict(type='list', aliases=['server_uuid']),
tags=dict(type='dict'),
))
module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=(('name', 'uuid'),),
supports_check_mode=True,
)
cloudscale_volume = AnsibleCloudscaleVolume(module)
if module.params['state'] == 'absent':
server_group = cloudscale_volume.absent()
else:
server_group = cloudscale_volume.present()
result = cloudscale_volume.get_result(server_group)
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -1 +0,0 @@
./cloud/cloudscale/cloudscale_floating_ip.py

View file

@ -1 +0,0 @@
./cloud/cloudscale/cloudscale_server.py

View file

@ -1 +0,0 @@
./cloud/cloudscale/cloudscale_server_group.py

View file

@ -1 +0,0 @@
./cloud/cloudscale/cloudscale_volume.py

View file

@ -1 +0,0 @@
hidden

View file

@ -1,19 +0,0 @@
---
# The image to use for test servers
cloudscale_test_image: 'debian-9'
# Alternate test image to use if a different image is required
cloudscale_alt_test_image: 'ubuntu-18.04'
# The flavor to use for test servers
cloudscale_test_flavor: 'flex-2'
# SSH key to use for test servers
cloudscale_test_ssh_key: |
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSPmiqkvDH1/+MDAVDZT8381aYqp73Odz8cnD5hegNhqtXajqtiH0umVg7HybX3wt1HjcrwKJovZURcIbbcDvzdH2bnYbF93T4OLXA0bIfuIp6M86x1iutFtXdpN3TTicINrmSXEE2Ydm51iMu77B08ZERjVaToya2F7vC+egfoPvibf7OLxE336a5tPCywavvNihQjL8sjgpDT5AAScjb3YqK/6VLeQ18Ggt8/ufINsYkb+9/Ji/3OcGFeflnDXq80vPUyF3u4iIylob6RSZenC38cXmQB05tRNxS1B6BXCjMRdy0v4pa7oKM2GA4ADKpNrr0RI9ed+peRFwmsclH test@ansible
# The zone to use to test servers
cloudscale_test_zone: 'lpg1'
# The region to use to request floating IPs
cloudscale_test_region: 'lpg'

View file

@ -1,17 +0,0 @@
---
- name: List all floating IPs
uri:
url: 'https://api.cloudscale.ch/v1/floating-ips'
headers:
Authorization: 'Bearer {{ cloudscale_api_token }}'
status_code: 200
register: floating_ip_list
- name: Remove all floating IPs created by this test run
cloudscale_floating_ip:
ip: '{{ item.network | ansible.netcommon.ipaddr("address") }}'
state: 'absent'
when: cloudscale_resource_prefix in (item.reverse_ptr | string )
with_items: '{{ floating_ip_list.json }}'
loop_control:
label: '{{ item.reverse_ptr }} ({{ item.network }})'

View file

@ -1,17 +0,0 @@
---
- name: List all server groups
uri:
url: 'https://api.cloudscale.ch/v1/server-groups'
headers:
Authorization: 'Bearer {{ cloudscale_api_token }}'
status_code: 200
register: server_group_list
- name: Remove all server groups created by this test run
cloudscale_server_group:
uuid: '{{ item.uuid }}'
state: absent
when: cloudscale_resource_prefix in item.name
with_items: '{{ server_group_list.json }}'
loop_control:
label: '{{ item.name }} ({{ item.uuid }})'

View file

@ -1,17 +0,0 @@
---
- name: List all servers
uri:
url: 'https://api.cloudscale.ch/v1/servers'
headers:
Authorization: 'Bearer {{ cloudscale_api_token }}'
status_code: 200
register: server_list
- name: Remove all servers created by this test run
cloudscale_server:
uuid: '{{ item.uuid }}'
state: 'absent'
when: cloudscale_resource_prefix in item.name
with_items: '{{ server_list.json }}'
loop_control:
label: '{{ item.name }} ({{ item.uuid }})'

View file

@ -1,17 +0,0 @@
---
- name: List all volumes
uri:
url: 'https://api.cloudscale.ch/v1/volumes'
headers:
Authorization: 'Bearer {{ cloudscale_api_token }}'
status_code: 200
register: volume_list
- name: Remove all volumes created by this test run
cloudscale_volume:
uuid: '{{ item.uuid }}'
state: 'absent'
when: cloudscale_resource_prefix in item.name
with_items: '{{ volume_list.json }}'
loop_control:
label: '{{ item.name }} ({{ item.uuid }})'

View file

@ -1,6 +0,0 @@
---
# Password to use for test server
# This has to be set as a fact, otherwise a new password will be generated
# on every variable access.
- set_fact:
cloudscale_test_password: "{{ lookup('password', '/dev/null length=15 chars=ascii_letters') }}"

View file

@ -1,2 +0,0 @@
cloud/cloudscale
unsupported

View file

@ -1,3 +0,0 @@
---
dependencies:
- cloudscale_common

View file

@ -1,129 +0,0 @@
- name: Request regional floating IP
cloudscale_floating_ip:
server: '{{ test01.uuid }}'
ip_version: '{{ item.ip_version }}'
reverse_ptr: '{{ item.reverse_ptr | default(omit) }}'
prefix_length: '{{ item.prefix_length | default(omit) }}'
region: '{{ cloudscale_test_region }}'
register: floating_ip
- name: Verify request floating IP
assert:
that:
- floating_ip is successful
- floating_ip is changed
- floating_ip.region.slug == '{{ cloudscale_test_region }}'
- (item.ip_version == 4 and floating_ip.ip | ipv4) or (item.ip_version == 6 and floating_ip.ip | ipv6)
- floating_ip.server == test01.uuid
- name: Check floating IP idempotence
cloudscale_floating_ip:
server: '{{ test01.uuid }}'
ip: '{{ floating_ip.ip }}'
region: '{{ cloudscale_test_region }}'
register: floating_ip_idempotence
- name: Verify floating IP idempotence
assert:
that:
- floating_ip_idempotence is successful
- floating_ip_idempotence is not changed
- floating_ip_idempotence.server == test01.uuid
- floating_ip.region.slug == '{{ cloudscale_test_region }}'
- name: Request global floating IP
cloudscale_floating_ip:
server: '{{ test01.uuid }}'
ip_version: '{{ item.ip_version }}'
reverse_ptr: '{{ item.reverse_ptr | default(omit) }}'
prefix_length: '{{ item.prefix_length | default(omit) }}'
type: 'global'
register: global_floating_ip
- name: Verify global floating IP
assert:
that:
- global_floating_ip is successful
- global_floating_ip is changed
- global_floating_ip.region == None
- global_floating_ip.type == 'global'
- (item.ip_version == 4 and global_floating_ip.ip | ipv4) or (item.ip_version == 6 and global_floating_ip.ip | ipv6)
- global_floating_ip.server == test01.uuid
- name: Release global floating IP
cloudscale_floating_ip:
ip: '{{ global_floating_ip.ip }}'
state: 'absent'
register: global_floating_ip
- name: Verify release of global floating IP
assert:
that:
- global_floating_ip is successful
- global_floating_ip is changed
- global_floating_ip.state == 'absent'
- name: Check network parameter alias
cloudscale_floating_ip:
server: '{{ test01.uuid }}'
network: '{{ floating_ip.ip }}'
register: floating_ip_network
- name: Verify network parameter alias
assert:
that:
- floating_ip_network is successful
- name: Move floating IP to second server
cloudscale_floating_ip:
server: '{{ test02.uuid }}'
ip: '{{ floating_ip.ip }}'
register: move_ip
- name: Verify move floating IPv4 to second server
assert:
that:
- move_ip is successful
- move_ip is changed
- move_ip.server == test02.uuid
- name: Fail if server is missing on update
cloudscale_floating_ip:
ip: '{{ floating_ip.ip }}'
register: update_failed
ignore_errors: True
- name: Verify fail if server is missing on update
assert:
that:
- update_failed is failed
- "'Missing required parameter' in update_failed.msg"
- name: Release floating IP
cloudscale_floating_ip:
ip: '{{ floating_ip.ip }}'
state: 'absent'
register: release_ip
- name: Verify release floating IPs
assert:
that:
- release_ip is successful
- release_ip is changed
- release_ip.state == 'absent'
- name: Release floating IP idempotence
cloudscale_floating_ip:
ip: '{{ floating_ip.ip }}'
state: 'absent'
register: release_ip
- name: Verify release floating IPs idempotence
assert:
that:
- release_ip is successful
- release_ip is not changed
- release_ip.state == 'absent'
- name: Fail if server is missing on request
cloudscale_floating_ip:
ip_version: 6
register: request_failed
ignore_errors: True
- name: Verify fail if server is missing on request
assert:
that:
- request_failed is failed
- "'Missing required parameter' in request_failed.msg"

View file

@ -1,35 +0,0 @@
- name: Cloudscale floating IP tests
block:
- name: Create a server
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test01'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
zone: '{{ cloudscale_test_zone }}'
register: test01
- name: Create a second server
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test02'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
zone: '{{ cloudscale_test_zone }}'
register: test02
- include_tasks: floating_ip.yml
loop:
- { 'ip_version': 4, 'reverse_ptr': '{{ cloudscale_resource_prefix }}-4.example.com' }
- { 'ip_version': 6, 'reverse_ptr': '{{ cloudscale_resource_prefix }}-6.example.com' }
- { 'ip_version': 6, 'prefix_length': 56 }
- import_tasks: unassigned.yml
always:
- import_role:
name: cloudscale_common
tasks_from: cleanup_servers
- import_role:
name: cloudscale_common
tasks_from: cleanup_floating_ips

View file

@ -1,26 +0,0 @@
---
- name: Assign Floating IP to server test01
cloudscale_floating_ip:
ip_version: 6
server: '{{ test01.uuid }}'
reverse_ptr: '{{ cloudscale_resource_prefix }}-unassigned.example.com'
region: '{{ cloudscale_test_region }}'
register: floating_ip
# The only way to have an unassigned floating IP is to delete the server
# where the floating IP is currently assigned.
- name: Delete server test01
cloudscale_server:
uuid: '{{ test01.uuid }}'
state: 'absent'
- name: Do not fail if floating IP is unassigned
cloudscale_floating_ip:
ip: '{{ floating_ip.ip }}'
register: floating_ip_not_fail
- name: Verify do not fail if floating IP is unassigned
assert:
that:
- floating_ip_not_fail is successful
- floating_ip_not_fail is not changed
- floating_ip_not_fail.server == None

View file

@ -1,2 +0,0 @@
cloud/cloudscale
unsupported

View file

@ -1,2 +0,0 @@
---
cloudscale_test_flavor_2: flex-4

View file

@ -1,3 +0,0 @@
---
dependencies:
- cloudscale_common

View file

@ -1,53 +0,0 @@
---
- name: Fail missing params
cloudscale_server:
register: srv
ignore_errors: True
- name: 'VERIFY: Fail name and UUID'
assert:
that:
- srv is failed
- name: Fail unexisting server group
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-group'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
password: '{{ cloudscale_test_password }}'
server_groups: '{{ cloudscale_resource_prefix }}-unexist-group'
ignore_errors: True
register: srv
- name: 'VERIFY: Fail unexisting server group'
assert:
that:
- srv is failed
- srv.msg.startswith('Server group name or UUID not found')
- name: Create two server groups with the same name
uri:
url: https://api.cloudscale.ch/v1/server-groups
method: POST
headers:
Authorization: 'Bearer {{ cloudscale_api_token }}'
body:
name: '{{ cloudscale_resource_prefix }}-duplicate'
type: anti-affinity
body_format: json
status_code: 201
register: duplicate
with_sequence: count=2
- name: Try to use server groups with identical name
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-group'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
password: '{{ cloudscale_test_password }}'
server_groups: '{{ cloudscale_resource_prefix }}-duplicate'
ignore_errors: True
register: srv
- name: 'VERIFY: Fail unexisting server group'
assert:
that:
- srv is failed
- srv.msg.startswith('More than one server group with name exists')

View file

@ -1,11 +0,0 @@
---
- block:
- import_tasks: failures.yml
- import_tasks: tests.yml
always:
- import_role:
name: cloudscale_common
tasks_from: cleanup_servers
- import_role:
name: cloudscale_common
tasks_from: cleanup_server_groups

View file

@ -1,676 +0,0 @@
---
- name: Setup server groups
cloudscale_server_group:
name: '{{ cloudscale_resource_prefix }}-group-{{ item }}'
type: anti-affinity
zone: '{{ cloudscale_test_zone }}'
with_sequence: count=2
- name: Test create a running server in check mode
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
server_groups: '{{ cloudscale_resource_prefix }}-group-1'
zone: '{{ cloudscale_test_zone }}'
tags:
project: ansible-test
stage: production
sla: 24-7
register: server
check_mode: yes
- name: Verify create a running server in check mode
assert:
that:
- server is changed
- server.state == 'absent'
- name: Test create a running server
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
server_groups: '{{ cloudscale_resource_prefix }}-group-1'
zone: '{{ cloudscale_test_zone }}'
tags:
project: ansible-test
stage: production
sla: 24-7
register: server
- name: Verify create a running server
assert:
that:
- server is changed
- server.state == 'running'
- server.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- server.zone.slug == '{{ cloudscale_test_zone }}'
- server.tags.project == 'ansible-test'
- server.tags.stage == 'production'
- server.tags.sla == '24-7'
- name: Test create a running server idempotence
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
server_groups: '{{ cloudscale_resource_prefix }}-group-1'
zone: '{{ cloudscale_test_zone }}'
tags:
project: ansible-test
stage: production
sla: 24-7
register: server
- name: Verify create a running server idempotence
assert:
that:
- server is not changed
- server.state == 'running'
- server.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- server.zone.slug == '{{ cloudscale_test_zone }}'
- server.tags.project == 'ansible-test'
- server.tags.stage == 'production'
- server.tags.sla == '24-7'
- name: Test update tags in check mode
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
server_groups: '{{ cloudscale_resource_prefix }}-group-1'
tags:
project: ansible-test
stage: staging
sla: 8-5
register: server
check_mode: yes
- name: Verify update tags in check mode
assert:
that:
- server is changed
- server.state == 'running'
- server.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- server.tags.project == 'ansible-test'
- server.tags.stage == 'production'
- server.tags.sla == '24-7'
- name: Test update tags
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
server_groups: '{{ cloudscale_resource_prefix }}-group-1'
tags:
project: ansible-test
stage: staging
sla: 8-5
register: server
- name: Verify update tags
assert:
that:
- server is changed
- server.state == 'running'
- server.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- server.tags.project == 'ansible-test'
- server.tags.stage == 'staging'
- server.tags.sla == '8-5'
- name: Test update tags idempotence
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
server_groups: '{{ cloudscale_resource_prefix }}-group-1'
tags:
project: ansible-test
stage: staging
sla: 8-5
register: server
- name: Verify update tags idempotence
assert:
that:
- server is not changed
- server.state == 'running'
- server.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- server.tags.project == 'ansible-test'
- server.tags.stage == 'staging'
- server.tags.sla == '8-5'
- name: Test omit tags idempotence
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
server_groups: '{{ cloudscale_resource_prefix }}-group-1'
register: server
- name: Verify update tags idempotence
assert:
that:
- server is not changed
- server.state == 'running'
- server.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- server.tags.project == 'ansible-test'
- server.tags.stage == 'staging'
- server.tags.sla == '8-5'
- name: Test delete tags
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
server_groups: '{{ cloudscale_resource_prefix }}-group-1'
tags: {}
register: server
- name: Verify delete tags
assert:
that:
- server is changed
- server.state == 'running'
- server.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- not server.tags
- name: Test delete tags idempotence
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
server_groups: '{{ cloudscale_resource_prefix }}-group-1'
tags: {}
register: server
- name: Verify delete tags idempotence
assert:
that:
- server is not changed
- server.state == 'running'
- server.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- not server.tags
- name: Test update flavor of a running server without force in check mode
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: no
register: server
check_mode: yes
- name: Verify update flavor of a running server without force in check mode
assert:
that:
- server is not changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- name: Test update flavor of a running server without force
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: no
register: server
- name: Verify update flavor of a running server without force
assert:
that:
- server is not changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- name: Test update flavor of a running server without force idempotence
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: no
register: server
- name: Verify update flavor of a running server without force idempotence
assert:
that:
- server is not changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- name: Test update flavor and name of a running server without force in check mode
cloudscale_server:
uuid: '{{ server.uuid }}'
name: '{{ cloudscale_resource_prefix }}-test-renamed'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: no
register: server
check_mode: yes
- name: Verify update flavor and name of a running server without force in check mode
assert:
that:
- server is changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.name == '{{ cloudscale_resource_prefix }}-test'
- name: Test update flavor and name of a running server without force
cloudscale_server:
uuid: '{{ server.uuid }}'
name: '{{ cloudscale_resource_prefix }}-test-renamed'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: no
register: server
- name: Verify update flavor and name of a running server without force
assert:
that:
- server is changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.name == '{{ cloudscale_resource_prefix }}-test-renamed'
- name: Test update flavor and name of a running server without force idempotence
cloudscale_server:
uuid: '{{ server.uuid }}'
name: '{{ cloudscale_resource_prefix }}-test-renamed'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: no
register: server
- name: Verify update flavor and name of a running server without force idempotence
assert:
that:
- server is not changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.name == '{{ cloudscale_resource_prefix }}-test-renamed'
- name: Test update flavor of a running server with force in check mode
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-renamed'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: yes
register: server
check_mode: yes
- name: Verify update flavor of a running server with force in check mode
assert:
that:
- server is changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.name == '{{ cloudscale_resource_prefix }}-test-renamed'
- name: Test update flavor of a running server with force
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-renamed'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: yes
register: server
- name: Verify update flavor of a running server with force
assert:
that:
- server is changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor_2 }}'
- server.name == '{{ cloudscale_resource_prefix }}-test-renamed'
- name: Test update a running server with force idempotence
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-renamed'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: yes
register: server
- name: Verify update flavor of a running server with force idempotence
assert:
that:
- server is not changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor_2 }}'
- server.name == '{{ cloudscale_resource_prefix }}-test-renamed'
- name: Remember uuid of running server for anti affinity
set_fact:
running_server_uuid: '{{ server.uuid }}'
- name: Test create server stopped in anti affinity and private network only in check mode
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-stopped'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
server_groups: '{{ cloudscale_resource_prefix }}-group-1'
zone: '{{ cloudscale_test_zone }}'
use_public_network: no
use_private_network: yes
state: stopped
check_mode: yes
register: server_stopped
- name: Verify create server stopped in anti affinity and private network only in check mode
assert:
that:
- server_stopped is changed
- server_stopped.state == 'absent'
- name: Test create server stopped in anti affinity and private network only
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-stopped'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
server_groups: '{{ cloudscale_resource_prefix }}-group-1'
zone: '{{ cloudscale_test_zone }}'
use_public_network: no
use_private_network: yes
state: stopped
register: server_stopped
- name: Verify create server stopped in anti affinity and private network only
assert:
that:
- server_stopped is changed
- server_stopped.state == 'stopped'
- server_stopped.zone.slug == '{{ cloudscale_test_zone }}'
- server_stopped.anti_affinity_with.0.uuid == running_server_uuid
- server_stopped.interfaces.0.type == 'private'
- server_stopped.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- name: Test create server stopped in anti affinity and private network only idempotence
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-stopped'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
server_groups: '{{ cloudscale_resource_prefix }}-group-1'
zone: '{{ cloudscale_test_zone }}'
use_public_network: no
use_private_network: yes
state: stopped
register: server_stopped
- name: Verify create server stopped in anti affinity and private network only idempotence
assert:
that:
- server_stopped is not changed
- server_stopped.state == 'stopped'
- server_stopped.zone.slug == '{{ cloudscale_test_zone }}'
- server_stopped.anti_affinity_with.0.uuid == running_server_uuid
- server_stopped.interfaces.0.type == 'private'
- server_stopped.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- name: Test change server group not changed
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-stopped'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
server_groups: '{{ cloudscale_resource_prefix }}-group-2'
use_public_network: no
use_private_network: yes
state: stopped
register: server_stopped
- name: Verify Test update server group not changed
assert:
that:
- server_stopped is not changed
- server_stopped.state == 'stopped'
- server_stopped.zone.slug == '{{ cloudscale_test_zone }}'
- server_stopped.anti_affinity_with.0.uuid == running_server_uuid
- server_stopped.interfaces.0.type == 'private'
- server_stopped.server_groups.0.name == '{{ cloudscale_resource_prefix }}-group-1'
- name: Test create server with password in check mode
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-password'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
password: '{{ cloudscale_test_password }}'
check_mode: yes
register: server_password
- name: Verify create server with password in check mode
assert:
that:
- server_password is changed
- server_password.state == 'absent'
# Verify password is not logged
- server_password.diff.after.password != cloudscale_test_password
- name: Test create server with password
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-password'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
password: '{{ cloudscale_test_password }}'
register: server_password
- name: Verify create server with password
assert:
that:
- server_password is changed
- server_password.state == 'running'
# Verify password is not logged
- server_password.diff.after.password != cloudscale_test_password
- name: Test create server with password idempotence
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-password'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
password: '{{ cloudscale_test_password }}'
register: server_password
- name: Verify create server with password idempotence
assert:
that:
- server_password is not changed
- server_password.state == 'running'
- name: Test create server failure without required parameters
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-failed'
register: server_failed
ignore_errors: yes
- name: Verify create server failure without required parameters
assert:
that:
- server_failed is failed
- "'Failure while calling the cloudscale.ch API with POST for \"servers\".' in server_failed.msg"
- "'This field is required.' in server_failed.fetch_url_info.body"
- name: Test stop running server in check mode
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-renamed'
state: stopped
check_mode: yes
register: server
- name: Verify stop running server in check mode
assert:
that:
- server is changed
- server.state == 'running'
- name: Test stop running server
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-renamed'
state: stopped
register: server
- name: Verify stop running server
assert:
that:
- server is changed
- server.state == 'stopped'
- name: Test stop running server idempotence
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-renamed'
state: 'stopped'
register: server
- name: Verify stop running server idempotence
assert:
that:
- server is not changed
- server.state == 'stopped'
- name: Test update a stopped server in check mode
cloudscale_server:
uuid: '{{ server.uuid }}'
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
state: stopped
register: server
check_mode: yes
- name: Verify update a stopped server in check mode
assert:
that:
- server is changed
- server.state == 'stopped'
- server.flavor.slug == '{{ cloudscale_test_flavor_2 }}'
- server.name == '{{ cloudscale_resource_prefix }}-test-renamed'
- name: Test update a stopped server without force
cloudscale_server:
uuid: '{{ server.uuid }}'
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
state: stopped
register: server
- name: Verify update a stopped server without force
assert:
that:
- server is changed
- server.state == 'stopped'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.name == '{{ cloudscale_resource_prefix }}-test'
- name: Test update a stopped server idempotence
cloudscale_server:
uuid: '{{ server.uuid }}'
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
state: stopped
register: server
- name: Verify update a stopped server idempotence
assert:
that:
- server is not changed
- server.state == 'stopped'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.name == '{{ cloudscale_resource_prefix }}-test'
- name: Test server running in check mode
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
state: running
register: server
check_mode: yes
- name: Verify server running in check mode
assert:
that:
- server is changed
- server.state == 'stopped'
- name: Test server running
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
state: running
register: server
- name: Verify server running
assert:
that:
- server is changed
- server.state == 'running'
- name: Test server running idempotence
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
state: running
register: server
- name: Verify server running idempotence
assert:
that:
- server is not changed
- server.state == 'running'
- name: Test running server deletion by name in check mode
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
state: absent
register: server
check_mode: yes
- name: Verify running server deletion by name in check mode
assert:
that:
- server is changed
- server.state == 'running'
- name: Test running server deletion by name
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
state: absent
register: server
- name: Verify running server deletion by name
assert:
that:
- server is changed
- server.state == 'absent'
- name: Test running server deletion by name idempotence
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
state: absent
register: server
- name: Verify running server deletion by name idempotence
assert:
that:
- server is not changed
- server.state == 'absent'
- name: Test stopped server deletion by uuid in check mode
cloudscale_server:
uuid: '{{ server_stopped.uuid }}'
state: absent
register: server_stopped
check_mode: yes
- name: Verify stopped server deletion by uuid in check mode
assert:
that:
- server_stopped is changed
- server_stopped.state == 'stopped'
- name: Test stopped server deletion by uuid
cloudscale_server:
uuid: '{{ server_stopped.uuid }}'
state: absent
register: server_stopped
- name: Verify stopped server deletion by uuid
assert:
that:
- server_stopped is changed
- server_stopped.state == 'absent'
- name: Test stopped server deletion by uuid idempotence
cloudscale_server:
uuid: '{{ server_stopped.uuid }}'
state: absent
register: server_stopped
- name: Verify stopped server deletion by uuid idempotence
assert:
that:
- server_stopped is not changed
- server_stopped.state == 'absent'

View file

@ -1,2 +0,0 @@
cloud/cloudscale
unsupported

View file

@ -1,3 +0,0 @@
---
dependencies:
- cloudscale_common

View file

@ -1,45 +0,0 @@
---
- name: Fail missing params
cloudscale_server_group:
register: grp
ignore_errors: True
- name: 'VERIFY: Fail name and UUID'
assert:
that:
- grp is failed
- name: Create two server groups with the same name
uri:
url: 'https://api.cloudscale.ch/v1/server-groups'
method: POST
headers:
Authorization: 'Bearer {{ cloudscale_api_token }}'
body:
name: '{{ cloudscale_resource_prefix }}-duplicate'
type: 'anti-affinity'
body_format: json
status_code: 201
register: duplicate
with_sequence: count=2
- name: Try access to duplicate name
cloudscale_server_group:
name: '{{ cloudscale_resource_prefix }}-duplicate'
register: grp
ignore_errors: True
- name: 'VERIFY: Try access to duplicate name'
assert:
that:
- grp is failed
- grp.msg.startswith('More than one server group with name exists')
- name: Fail server group creation with UUID
cloudscale_server_group:
uuid: ea3b39a3-77a8-4d0b-881d-0bb00a1e7f48
register: grp
ignore_errors: True
- name: 'VERIFY: Fail server group creation with UUID'
assert:
that:
- grp is failed
- grp.msg.startswith('missing required arguments')

View file

@ -1,8 +0,0 @@
---
- block:
- import_tasks: failures.yml
- import_tasks: tests.yml
always:
- import_role:
name: cloudscale_common
tasks_from: cleanup_server_groups

View file

@ -1,159 +0,0 @@
---
- name: Create server group in check mode
cloudscale_server_group:
name: '{{ cloudscale_resource_prefix }}-grp'
tags:
project: ansible-test
stage: production
sla: 24-7
register: grp
check_mode: yes
- name: 'VERIFY: Create server group in check mode'
assert:
that:
- grp is changed
- grp.name == '{{ cloudscale_resource_prefix }}-grp'
- not grp.uuid
- name: Create server group
cloudscale_server_group:
name: '{{ cloudscale_resource_prefix }}-grp'
zone: '{{ cloudscale_test_zone }}'
tags:
project: ansible-test
stage: production
sla: 24-7
register: grp
- name: 'VERIFY: Create server group'
assert:
that:
- grp is changed
- grp.type == 'anti-affinity'
- grp.name == '{{ cloudscale_resource_prefix }}-grp'
- grp.zone.slug == '{{ cloudscale_test_zone }}'
- grp.uuid
- grp.tags.project == 'ansible-test'
- grp.tags.stage == 'production'
- grp.tags.sla == '24-7'
- name: Remember uuid
set_fact:
server_group_uuid: '{{ grp.uuid }}'
- name: Create server group idempotence
cloudscale_server_group:
name: '{{ cloudscale_resource_prefix }}-grp'
zone: '{{ cloudscale_test_zone }}'
tags:
project: ansible-test
stage: production
sla: 24-7
register: grp
- name: 'VERIFY: Create server group idempotence'
assert:
that:
- grp is not changed
- grp.name == '{{ cloudscale_resource_prefix }}-grp'
- grp.zone.slug == '{{ cloudscale_test_zone }}'
- grp.uuid == server_group_uuid
- grp.tags.project == 'ansible-test'
- grp.tags.stage == 'production'
- grp.tags.sla == '24-7'
- name: Update server group in check mode
cloudscale_server_group:
uuid: '{{ server_group_uuid }}'
name: '{{ cloudscale_resource_prefix }}-grp2'
tags:
project: ansible-test
stage: staging
sla: 8-5
register: grp
check_mode: yes
- name: 'VERIFY: Update server group in check mode'
assert:
that:
- grp is changed
- grp.name == '{{ cloudscale_resource_prefix }}-grp'
- grp.uuid == server_group_uuid
- grp.zone.slug == '{{ cloudscale_test_zone }}'
- grp.tags.project == 'ansible-test'
- grp.tags.stage == 'production'
- grp.tags.sla == '24-7'
- name: Update server group
cloudscale_server_group:
uuid: '{{ server_group_uuid }}'
name: '{{ cloudscale_resource_prefix }}-grp2'
tags:
project: ansible-test
stage: staging
sla: 8-5
register: grp
- name: 'VERIFY: Update server group'
assert:
that:
- grp is changed
- grp.name == '{{ cloudscale_resource_prefix }}-grp2'
- grp.uuid == server_group_uuid
- grp.zone.slug == '{{ cloudscale_test_zone }}'
- grp.tags.project == 'ansible-test'
- grp.tags.stage == 'staging'
- grp.tags.sla == '8-5'
- name: Update server group idempotence
cloudscale_server_group:
uuid: '{{ server_group_uuid }}'
name: '{{ cloudscale_resource_prefix }}-grp2'
tags:
project: ansible-test
stage: staging
sla: 8-5
register: grp
- name: 'VERIFY: Update server group idempotence'
assert:
that:
- grp is not changed
- grp.name == '{{ cloudscale_resource_prefix }}-grp2'
- grp.uuid == server_group_uuid
- grp.zone.slug == '{{ cloudscale_test_zone }}'
- grp.tags.project == 'ansible-test'
- grp.tags.stage == 'staging'
- grp.tags.sla == '8-5'
- name: Delete server group in check mode
cloudscale_server_group:
name: '{{ cloudscale_resource_prefix }}-grp2'
state: absent
register: grp
check_mode: yes
- name: 'VERIFY: Delete server group in check mode'
assert:
that:
- grp is changed
- grp.name == '{{ cloudscale_resource_prefix }}-grp2'
- grp.uuid == server_group_uuid
- name: Delete server group
cloudscale_server_group:
name: '{{ cloudscale_resource_prefix }}-grp2'
state: absent
register: grp
- name: 'VERIFY: Delete server group'
assert:
that:
- grp is changed
- grp.name == '{{ cloudscale_resource_prefix }}-grp2'
- grp.uuid == server_group_uuid
- name: Delete server group idempotence
cloudscale_server_group:
name: '{{ cloudscale_resource_prefix }}-grp2'
state: absent
register: grp
- name: 'VERIFY: Delete server group idempotence'
assert:
that:
- grp is not changed
- grp.name == '{{ cloudscale_resource_prefix }}-grp2'
- not grp.uuid

View file

@ -1,2 +0,0 @@
cloud/cloudscale
unsupported

View file

@ -1,3 +0,0 @@
---
dependencies:
- cloudscale_common

View file

@ -1,5 +0,0 @@
---
- name: Remove test server
cloudscale_server:
uuid: '{{ server.uuid }}'
state: 'absent'

View file

@ -1,38 +0,0 @@
---
- name: Create two volumes with the same name
uri:
url: 'https://api.cloudscale.ch/v1/volumes'
method: POST
headers:
Authorization: 'Bearer {{ cloudscale_api_token }}'
body:
name: '{{ cloudscale_resource_prefix }}-duplicate'
size_gb: 50
body_format: json
status_code: 201
register: duplicate
with_sequence: count=2
- name: Try access to duplicate name
cloudscale_volume:
name: '{{ cloudscale_resource_prefix }}-duplicate'
size_gb: 10
register: vol
ignore_errors: True
- name: 'VERIFY: Try access to duplicate name'
assert:
that:
- vol is failed
- name: Fail volume creation with UUID
cloudscale_volume:
uuid: ea3b39a3-77a8-4d0b-881d-0bb00a1e7f48
name: '{{ cloudscale_resource_prefix }}-inexistent'
size_gb: 10
register: vol
ignore_errors: True
- name: 'VERIFY: Fail volume creation with UUID'
assert:
that:
- vol is failed
- vol.msg.startswith('The volume with UUID \'ea3b39a3-77a8-4d0b-881d-0bb00a1e7f48\' was not found')

View file

@ -1,11 +0,0 @@
---
- block:
- import_tasks: setup.yml
- import_tasks: tests.yml
always:
- import_role:
name: cloudscale_common
tasks_from: cleanup_servers
- import_role:
name: cloudscale_common
tasks_from: cleanup_volumes

View file

@ -1,9 +0,0 @@
---
- name: Create test instance
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-server'
flavor: '{{ cloudscale_test_flavor }}'
zone: '{{ cloudscale_test_zone }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
register: server

View file

@ -1,262 +0,0 @@
---
- name: Create volume in check mode
cloudscale_volume:
name: '{{ cloudscale_resource_prefix }}-vol'
zone: '{{ cloudscale_test_zone }}'
size_gb: 50
tags:
project: ansible-test
stage: production
sla: 24-7
check_mode: yes
register: vol
- name: 'VERIFY: Create volume in check mode'
assert:
that:
- vol is successful
- vol is changed
- vol.state == 'absent'
- name: Create volume
cloudscale_volume:
name: '{{ cloudscale_resource_prefix }}-vol'
zone: '{{ cloudscale_test_zone }}'
size_gb: 50
tags:
project: ansible-test
stage: production
sla: 24-7
register: vol
- name: 'VERIFY: Create volume'
assert:
that:
- vol is successful
- vol is changed
- vol.size_gb == 50
- vol.name == '{{ cloudscale_resource_prefix }}-vol'
- vol.zone.slug == '{{ cloudscale_test_zone }}'
- vol.tags.project == 'ansible-test'
- vol.tags.stage == 'production'
- vol.tags.sla == '24-7'
- name: Create volume idempotence
cloudscale_volume:
name: '{{ cloudscale_resource_prefix }}-vol'
zone: '{{ cloudscale_test_zone }}'
size_gb: 50
tags:
project: ansible-test
stage: production
sla: 24-7
register: vol
- name: 'VERIFY: Create volume idempotence'
assert:
that:
- vol is successful
- vol is not changed
- vol.size_gb == 50
- vol.name == '{{ cloudscale_resource_prefix }}-vol'
- vol.zone.slug == '{{ cloudscale_test_zone }}'
- vol.tags.project == 'ansible-test'
- vol.tags.stage == 'production'
- vol.tags.sla == '24-7'
- name: Attach existing volume by name to server in check mode
cloudscale_volume:
name: '{{ cloudscale_resource_prefix }}-vol'
server_uuids:
- '{{ server.uuid }}'
check_mode: yes
register: vol
- name: 'VERIFY: Attach existing volume by name to server in check mode'
assert:
that:
- vol is successful
- vol is changed
- server.uuid not in vol.server_uuids
- name: Attach existing volume by name to server
cloudscale_volume:
name: '{{ cloudscale_resource_prefix }}-vol'
server_uuids:
- '{{ server.uuid }}'
register: vol
- name: 'VERIFY: Attach existing volume by name to server'
assert:
that:
- vol is successful
- vol is changed
- server.uuid in vol.server_uuids
- name: Attach existing volume by name to server idempotence
cloudscale_volume:
name: '{{ cloudscale_resource_prefix }}-vol'
server_uuids:
- '{{ server.uuid }}'
register: vol
- name: 'VERIFY: Attach existing volume by name to server idempotence'
assert:
that:
- vol is successful
- vol is not changed
- server.uuid in vol.server_uuids
- name: Resize attached volume by UUID in check mode
cloudscale_volume:
uuid: '{{ vol.uuid }}'
size_gb: 100
check_mode: yes
register: vol
- name: 'VERIFY: Resize attached volume by UUID in check mode'
assert:
that:
- vol is successful
- vol is changed
- vol.size_gb == 50
- name: Resize attached volume by UUID
cloudscale_volume:
uuid: '{{ vol.uuid }}'
size_gb: 100
register: vol
- name: 'VERIFY: Resize attached volume by UUID'
assert:
that:
- vol is successful
- vol is changed
- vol.size_gb == 100
- name: Resize attached volume by UUID idempotence
cloudscale_volume:
uuid: '{{ vol.uuid }}'
size_gb: 100
register: vol
- name: 'VERIFY: Resize attached volume by UUID idempotence'
assert:
that:
- vol is successful
- vol is not changed
- vol.size_gb == 100
- name: Delete attached volume by UUID in check mode
cloudscale_volume:
uuid: '{{ vol.uuid }}'
state: 'absent'
check_mode: yes
register: deleted
- name: 'VERIFY: Delete attached volume by UUID in check mode'
assert:
that:
- deleted is successful
- deleted is changed
- deleted.state == 'present'
- deleted.uuid == vol.uuid
- deleted.name == '{{ cloudscale_resource_prefix }}-vol'
- name: Delete attached volume by UUID
cloudscale_volume:
uuid: '{{ vol.uuid }}'
state: 'absent'
register: deleted
- name: 'VERIFY: Delete attached volume by UUID'
assert:
that:
- deleted is successful
- deleted is changed
- deleted.state == 'absent'
- deleted.uuid == vol.uuid
- deleted.name == '{{ cloudscale_resource_prefix }}-vol'
- name: Delete attached volume by UUID idempotence
cloudscale_volume:
uuid: '{{ vol.uuid }}'
state: 'absent'
register: deleted
- name: 'VERIFY: Delete attached volume by UUID idempotence'
assert:
that:
- deleted is successful
- deleted is not changed
- deleted.state == 'absent'
- deleted.uuid == vol.uuid
- not deleted.name
- name: Create bulk volume and attach
cloudscale_volume:
name: '{{ cloudscale_resource_prefix }}-bulk'
type: bulk
zone: '{{ cloudscale_test_zone }}'
size_gb: 100
server_uuids:
- '{{ server.uuid }}'
register: bulk
- name: 'VERIFY: Create bulk volume and attach'
assert:
that:
- bulk is successful
- bulk is changed
- bulk.size_gb == 100
- server.uuid in bulk.server_uuids
- name: Detach volume by UUID
cloudscale_volume:
uuid: '{{ bulk.uuid }}'
server_uuids: []
register: bulk
- name: 'VERIFY: Detach volume by UUID'
assert:
that:
- bulk is successful
- bulk is changed
- bulk.server_uuids == []
- name: Resize detached volume by name
cloudscale_volume:
name: '{{ bulk.name }}'
size_gb: 200
register: bulk
- name: 'VERIFY: Resize detached volume by name'
assert:
that:
- bulk is successful
- bulk is changed
- bulk.size_gb == 200
- name: Delete volume by name in check mode
cloudscale_volume:
name: '{{ bulk.name }}'
state: 'absent'
check_mode: yes
register: bulk
- name: 'VERIFY: Delete volume by name'
assert:
that:
- bulk is successful
- bulk is changed
- bulk.state == 'present'
- name: Delete volume by name
cloudscale_volume:
name: '{{ bulk.name }}'
state: 'absent'
register: bulk
- name: 'VERIFY: Delete volume by name'
assert:
that:
- bulk is successful
- bulk is changed
- bulk.state == 'absent'
- name: Delete volume by name idempotence
cloudscale_volume:
name: '{{ bulk.name }}'
state: 'absent'
register: bulk
- name: 'VERIFY: Delete volume by name idempotence'
assert:
that:
- bulk is successful
- bulk is not changed
- bulk.state == 'absent'
- import_tasks: failures.yml

View file

@ -1,3 +0,0 @@
cloud/cloudscale
unsupported
needs/target/cloudscale_common

View file

@ -1,17 +0,0 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.inventory.group import to_safe_group_name
def safe_group_name(name):
return to_safe_group_name(name)
class FilterModule(object):
filter_map = {
'safe_group_name': safe_group_name
}
def filters(self):
return self.filter_map

View file

@ -1,14 +0,0 @@
plugin: community.general.cloudscale
ansible_host: private
inventory_hostname: name
groups:
ansible: inventory_hostname.startswith('ansible')
private_net: (cloudscale.interfaces | selectattr('type', 'equalto', 'private') | list | length) > 0
keyed_groups:
- prefix: net
key: (cloudscale.interfaces.0.addresses.0.address + '/' + cloudscale.interfaces.0.addresses.0.prefix_length | string) | ansible.netcommon.ipaddr('network')
- prefix: distro
key: cloudscale.image.operating_system
compose:
flavor_image: cloudscale.flavor.slug + '_' + cloudscale.image.slug
strict: false

View file

@ -1,14 +0,0 @@
plugin: community.general.cloudscale
ansible_host: public_v4
inventory_hostname: name
groups:
ansible: inventory_hostname.startswith('ansible')
private_net: (cloudscale.interfaces | selectattr('type', 'equalto', 'private') | list | length) > 0
keyed_groups:
- prefix: net
key: (cloudscale.interfaces.0.addresses.0.address + '/' + cloudscale.interfaces.0.addresses.0.prefix_length | string) | ansible.netcommon.ipaddr('network')
- prefix: distro
key: cloudscale.image.operating_system
compose:
flavor_image: cloudscale.flavor.slug + '_' + cloudscale.image.slug
strict: false

View file

@ -1,14 +0,0 @@
plugin: community.general.cloudscale
ansible_host: public_v4
inventory_hostname: uuid
groups:
ansible: cloudscale.name.startswith('ansible')
private_net: (cloudscale.interfaces | selectattr('type', 'equalto', 'private') | list | length) > 0
keyed_groups:
- prefix: net
key: (cloudscale.interfaces.0.addresses.0.address + '/' + cloudscale.interfaces.0.addresses.0.prefix_length | string) | ansible.netcommon.ipaddr('network')
- prefix: distro
key: cloudscale.image.operating_system
compose:
flavor_image: cloudscale.flavor.slug + '_' + cloudscale.image.slug
strict: false

View file

@ -1,8 +0,0 @@
- name: Change inventory configuration to {{ inventory_config }}
file:
src: '{{ inventory_config }}'
dest: ../inventory_cloudscale.yml
state: link
- name: Refresh inventory
meta: refresh_inventory

View file

@ -1,17 +0,0 @@
---
- name: List all servers
uri:
url: 'https://api.cloudscale.ch/v1/servers'
headers:
Authorization: 'Bearer {{ lookup("env", "CLOUDSCALE_API_TOKEN") }}'
status_code: 200
register: server_list
- name: Remove all servers created by this test run
community.general.cloudscale_server:
uuid: '{{ item.uuid }}'
state: 'absent'
when: cloudscale_resource_prefix in item.name
with_items: '{{ server_list.json }}'
loop_control:
label: '{{ item.name }} ({{ item.uuid }})'

View file

@ -1,50 +0,0 @@
---
- name: '{{ inventory }}: Verify basic inventory'
assert:
that:
- server_public[identifier] in hostvars
- server_private[identifier] in hostvars
- server_public_private[identifier] in hostvars
- server_unsafe_chars[identifier] in hostvars
- name: '{{ inventory }}: Verify duplicate host names in inventory'
assert:
that:
- cloudscale_resource_prefix + '-duplicate' not in hostvars
- (cloudscale_resource_prefix + '-duplicate') | safe_group_name in groups
- name: '{{ inventory }}: Verify constructed groups in inventory'
assert:
that:
# Test for the "ansible" group
- '"ansible" in groups'
- server_public[identifier] in groups.ansible
- server_private[identifier] in groups.ansible
- server_public_private[identifier] in groups.ansible
- server_unsafe_chars[identifier] in groups.ansible
- server_other_prefix[identifier] not in groups.ansible
# Tests for the "private_net" group
- '"private_net" in groups'
- server_public[identifier] not in groups["private_net"]
- server_private[identifier] in groups["private_net"]
- server_public_private[identifier] in groups["private_net"]
# Tests for "distro" keyed group
- '"distro_Debian" in groups'
- '"distro_Ubuntu" in groups'
- server_public[identifier] in groups.distro_Debian
- server_private[identifier] not in groups.distro_Debian
- server_public[identifier] not in groups.distro_Ubuntu
- server_private[identifier] in groups.distro_Ubuntu
# Test for flavor_image composed variable
- hostvars[server_public[identifier]].flavor_image == 'flex-2_debian-9'
- hostvars[server_private[identifier]].flavor_image == 'flex-2_ubuntu-18.04'
- name: '{{ inventory }}: Verify cloudscale specific host variables'
assert:
that:
- hostvars[item.0[identifier]].cloudscale[item.1] == item.0[item.1]
with_nested:
- [ '{{ server_public }}', '{{ server_private }}', '{{ server_public_private }}' ]
- [ 'anti_affinity_with', 'flavor', 'href', 'image', 'interfaces', 'name', 'uuid', 'volumes' ]
loop_control:
label: '{{ item.0.name }} ({{ item.0.uuid }}): {{ item.1 }}'

View file

@ -1,74 +0,0 @@
---
- name: Create server with public network only
community.general.cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-inventory-public'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
use_public_network: True
use_private_network: False
register: server_public
- name: Create server with private network only
community.general.cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-inventory-private'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_alt_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
use_public_network: False
use_private_network: True
register: server_private
- name: Create server with public and private network
community.general.cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-inventory-public-private'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
use_public_network: True
use_private_network: True
register: server_public_private
- name: Create servers with duplicate names
# The cloudscale_server module does not allow creating two servers with the same
# name. To do this the uri module has to be used.
uri:
url: 'https://api.cloudscale.ch/v1/servers'
method: POST
headers:
Authorization: 'Bearer {{ lookup("env", "CLOUDSCALE_API_TOKEN") }}'
body:
name: '{{ cloudscale_resource_prefix }}-duplicate'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys:
- '{{ cloudscale_test_ssh_key }}'
body_format: json
status_code: 201
register: duplicate
with_sequence: count=2
- name: Create server with different prefix
community.general.cloudscale_server:
name: 'other-prefix-{{ cloudscale_resource_prefix }}-inventory'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
register: server_other_prefix
# The API does not allow creation of a server with a name containing
# characters not allowed in DNS names. So create a server and rename
# it afterwards (which is possible). The resaon for this restriction is
# that on creation a PTR entry for the server is created.
- name: Create server to be renamed with unsafe characters
community.general.cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-unsafe-chars'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
register: server_unsafe_chars
- name: Rename server to contain unsafe characters
community.general.cloudscale_server:
uuid: '{{ server_unsafe_chars.uuid }}'
name: '{{ cloudscale_resource_prefix }}-snowmans-are-cool-☃!'
register: server_unsafe_chars

View file

@ -1,68 +0,0 @@
---
- name: Create servers and test cloudscale inventory plugin
hosts: localhost
gather_facts: False
roles:
- cloudscale_common
tasks:
- block:
- import_tasks: setup.yml
- import_tasks: change-inventory-config.yml
vars:
inventory_config: inventory-public.yml
- import_tasks: common-asserts.yml
vars:
identifier: 'name'
inventory: 'Public v4'
- name: Verify inventory with public IP
assert:
that:
# Test ansible_host setting
- server_public.interfaces.0.addresses.0.address
== hostvars[server_public.name].ansible_host
- server_public_private.interfaces.0.addresses.0.address
== hostvars[server_public_private.name].ansible_host
- '"ansible_host" not in hostvars[server_private.name]'
- import_tasks: change-inventory-config.yml
vars:
inventory_config: inventory-private.yml
- import_tasks: common-asserts.yml
vars:
identifier: 'name'
inventory: 'Private v4'
- name: Verify inventory with private IP
assert:
that:
# Test ansible_host setting
- '"ansible_host" not in hostvars[server_public.name]'
- server_private.interfaces.0.addresses.0.address
== hostvars[server_private.name].ansible_host
- server_public_private.interfaces.1.addresses.0.address
== hostvars[server_public_private.name].ansible_host
- import_tasks: change-inventory-config.yml
vars:
inventory_config: inventory-uuid.yml
- import_tasks: common-asserts.yml
vars:
identifier: 'uuid'
inventory: 'UUID'
- name: Verify inventory with UUID
assert:
that:
# Test server name groups
- groups[server_public.name | safe_group_name] == [server_public.uuid]
- groups[server_private.name | safe_group_name] == [server_private.uuid]
- groups[server_public_private.name | safe_group_name] == [server_public_private.uuid]
- groups[server_unsafe_chars.name | safe_group_name] == [server_unsafe_chars.uuid]
always:
- import_tasks: cleanup.yml

View file

@ -1,21 +0,0 @@
#!/bin/sh
# Exit on errors, exit when accessing unset variables and print all commands
set -eux
# Set the role path so that the cloudscale_common role is available
export ANSIBLE_ROLES_PATH="../"
# Set the filter plugin search path so that the safe_group_name filter is available
export ANSIBLE_FILTER_PLUGINS="./filter_plugins"
rm -f inventory.yml
export ANSIBLE_INVENTORY="./inventory_cloudscale.yml"
# Run without converting invalid characters in group names
export ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS=never
ansible-playbook playbooks/test-inventory.yml "$@"
# Run with converting invalid characters in group names
export ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS=always
ansible-playbook playbooks/test-inventory.yml "$@"

View file

@ -194,12 +194,6 @@ plugins/modules/cloud/centurylink/clc_server_snapshot.py validate-modules:doc-mi
plugins/modules/cloud/centurylink/clc_server_snapshot.py validate-modules:implied-parameter-type-mismatch
plugins/modules/cloud/centurylink/clc_server_snapshot.py validate-modules:parameter-list-no-elements
plugins/modules/cloud/centurylink/clc_server_snapshot.py validate-modules:parameter-type-not-in-doc
plugins/modules/cloud/cloudscale/cloudscale_floating_ip.py validate-modules:doc-required-mismatch
plugins/modules/cloud/cloudscale/cloudscale_server.py validate-modules:doc-required-mismatch
plugins/modules/cloud/cloudscale/cloudscale_server.py validate-modules:parameter-list-no-elements
plugins/modules/cloud/cloudscale/cloudscale_server_group.py validate-modules:doc-required-mismatch
plugins/modules/cloud/cloudscale/cloudscale_volume.py validate-modules:doc-required-mismatch
plugins/modules/cloud/cloudscale/cloudscale_volume.py validate-modules:parameter-list-no-elements
plugins/modules/cloud/digital_ocean/digital_ocean.py validate-modules:doc-missing-type
plugins/modules/cloud/digital_ocean/digital_ocean.py validate-modules:parameter-list-no-elements
plugins/modules/cloud/digital_ocean/digital_ocean.py validate-modules:parameter-type-not-in-doc