mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Add OpenNebula one_image module (#37831)
This commit is contained in:
parent
697c301f04
commit
a73e2a924b
4 changed files with 721 additions and 0 deletions
424
lib/ansible/modules/cloud/opennebula/one_image.py
Normal file
424
lib/ansible/modules/cloud/opennebula/one_image.py
Normal file
|
@ -0,0 +1,424 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
"""
|
||||||
|
(c) 2018, Milan Ilic <milani@nordeus.com>
|
||||||
|
|
||||||
|
This file is part of Ansible
|
||||||
|
|
||||||
|
Ansible is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Ansible is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a clone of the GNU General Public License
|
||||||
|
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {'status': ['preview'],
|
||||||
|
'supported_by': 'community',
|
||||||
|
'metadata_version': '1.1'}
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: one_image
|
||||||
|
short_description: Manages OpenNebula images
|
||||||
|
description:
|
||||||
|
- Manages OpenNebula images
|
||||||
|
version_added: "2.6"
|
||||||
|
requirements:
|
||||||
|
- python-oca
|
||||||
|
options:
|
||||||
|
api_url:
|
||||||
|
description:
|
||||||
|
- URL of the OpenNebula RPC server.
|
||||||
|
- It is recommended to use HTTPS so that the username/password are not
|
||||||
|
- transferred over the network unencrypted.
|
||||||
|
- If not set then the value of the C(ONE_URL) environment variable is used.
|
||||||
|
api_username:
|
||||||
|
description:
|
||||||
|
- Name of the user to login into the OpenNebula RPC server. If not set
|
||||||
|
- then the value of the C(ONE_USERNAME) environment variable is used.
|
||||||
|
api_password:
|
||||||
|
description:
|
||||||
|
- Password of the user to login into OpenNebula RPC server. If not set
|
||||||
|
- then the value of the C(ONE_PASSWORD) environment variable is used.
|
||||||
|
id:
|
||||||
|
description:
|
||||||
|
- A C(id) of the image you would like to manage.
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- A C(name) of the image you would like to manage.
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- C(present) - state that is used to manage the image
|
||||||
|
- C(absent) - delete the image
|
||||||
|
- C(cloned) - clone the image
|
||||||
|
- C(renamed) - rename the image to the C(new_name)
|
||||||
|
choices: ["present", "absent", "cloned", "renamed"]
|
||||||
|
default: present
|
||||||
|
enabled:
|
||||||
|
description:
|
||||||
|
- Whether the image should be enabled or disabled.
|
||||||
|
type: bool
|
||||||
|
new_name:
|
||||||
|
description:
|
||||||
|
- A name that will be assigned to the existing or new image.
|
||||||
|
- In the case of cloning, by default C(new_name) will take the name of the origin image with the prefix 'Copy of'.
|
||||||
|
author:
|
||||||
|
- "Milan Ilic (@ilicmilan)"
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
# Fetch the IMAGE by id
|
||||||
|
- one_image:
|
||||||
|
id: 45
|
||||||
|
register: result
|
||||||
|
|
||||||
|
# Print the IMAGE properties
|
||||||
|
- debug:
|
||||||
|
msg: result
|
||||||
|
|
||||||
|
# Rename existing IMAGE
|
||||||
|
- one_image:
|
||||||
|
id: 34
|
||||||
|
state: renamed
|
||||||
|
new_name: bar-image
|
||||||
|
|
||||||
|
# Disable the IMAGE by id
|
||||||
|
- one_image:
|
||||||
|
id: 37
|
||||||
|
enabled: no
|
||||||
|
|
||||||
|
# Enable the IMAGE by name
|
||||||
|
- one_image:
|
||||||
|
name: bar-image
|
||||||
|
enabled: yes
|
||||||
|
|
||||||
|
# Clone the IMAGE by name
|
||||||
|
- one_image:
|
||||||
|
name: bar-image
|
||||||
|
state: cloned
|
||||||
|
new_name: bar-image-clone
|
||||||
|
register: result
|
||||||
|
|
||||||
|
# Delete the IMAGE by id
|
||||||
|
- one_image:
|
||||||
|
id: '{{ result.id }}'
|
||||||
|
state: absent
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
id:
|
||||||
|
description: image id
|
||||||
|
type: int
|
||||||
|
returned: success
|
||||||
|
sample: 153
|
||||||
|
name:
|
||||||
|
description: image name
|
||||||
|
type: string
|
||||||
|
returned: success
|
||||||
|
sample: app1
|
||||||
|
group_id:
|
||||||
|
description: image's group id
|
||||||
|
type: int
|
||||||
|
returned: success
|
||||||
|
sample: 1
|
||||||
|
group_name:
|
||||||
|
description: image's group name
|
||||||
|
type: string
|
||||||
|
returned: success
|
||||||
|
sample: one-users
|
||||||
|
owner_id:
|
||||||
|
description: image's owner id
|
||||||
|
type: int
|
||||||
|
returned: success
|
||||||
|
sample: 143
|
||||||
|
owner_name:
|
||||||
|
description: image's owner name
|
||||||
|
type: string
|
||||||
|
returned: success
|
||||||
|
sample: ansible-test
|
||||||
|
state:
|
||||||
|
description: state of image instance
|
||||||
|
type: string
|
||||||
|
returned: success
|
||||||
|
sample: READY
|
||||||
|
used:
|
||||||
|
description: is image in use
|
||||||
|
type: bool
|
||||||
|
returned: success
|
||||||
|
sample: true
|
||||||
|
running_vms:
|
||||||
|
description: count of running vms that use this image
|
||||||
|
type: int
|
||||||
|
returned: success
|
||||||
|
sample: 7
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
import oca
|
||||||
|
HAS_OCA = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_OCA = False
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def get_image(module, client, predicate):
|
||||||
|
pool = oca.ImagePool(client)
|
||||||
|
# Filter -2 means fetch all images user can Use
|
||||||
|
pool.info(filter=-2)
|
||||||
|
|
||||||
|
for image in pool:
|
||||||
|
if predicate(image):
|
||||||
|
return image
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_by_name(module, client, image_name):
|
||||||
|
return get_image(module, client, lambda image: (image.name == image_name))
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_by_id(module, client, image_id):
|
||||||
|
return get_image(module, client, lambda image: (image.id == image_id))
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_instance(module, client, requested_id, requested_name):
|
||||||
|
if requested_id:
|
||||||
|
return get_image_by_id(module, client, requested_id)
|
||||||
|
else:
|
||||||
|
return get_image_by_name(module, client, requested_name)
|
||||||
|
|
||||||
|
|
||||||
|
IMAGE_STATES = ['INIT', 'READY', 'USED', 'DISABLED', 'LOCKED', 'ERROR', 'CLONE', 'DELETE', 'USED_PERS', 'LOCKED_USED', 'LOCKED_USED_PERS']
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_info(image):
|
||||||
|
image.info()
|
||||||
|
|
||||||
|
info = {
|
||||||
|
'id': image.id,
|
||||||
|
'name': image.name,
|
||||||
|
'state': IMAGE_STATES[image.state],
|
||||||
|
'running_vms': image.running_vms,
|
||||||
|
'used': bool(image.running_vms),
|
||||||
|
'user_name': image.uname,
|
||||||
|
'user_id': image.uid,
|
||||||
|
'group_name': image.gname,
|
||||||
|
'group_id': image.gid,
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_state(module, image, wait_timeout, state_predicate):
|
||||||
|
import time
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
while (time.time() - start_time) < wait_timeout:
|
||||||
|
image.info()
|
||||||
|
state = image.state
|
||||||
|
|
||||||
|
if state_predicate(state):
|
||||||
|
return image
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
module.fail_json(msg="Wait timeout has expired!")
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_ready(module, image, wait_timeout=60):
|
||||||
|
return wait_for_state(module, image, wait_timeout, lambda state: (state in [IMAGE_STATES.index('READY')]))
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_delete(module, image, wait_timeout=60):
|
||||||
|
return wait_for_state(module, image, wait_timeout, lambda state: (state in [IMAGE_STATES.index('DELETE')]))
|
||||||
|
|
||||||
|
|
||||||
|
def enable_image(module, client, image, enable):
|
||||||
|
image.info()
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
state = image.state
|
||||||
|
|
||||||
|
if state not in [IMAGE_STATES.index('READY'), IMAGE_STATES.index('DISABLED'), IMAGE_STATES.index('ERROR')]:
|
||||||
|
if enable:
|
||||||
|
module.fail_json(msg="Cannot enable " + IMAGE_STATES[state] + " image!")
|
||||||
|
else:
|
||||||
|
module.fail_json(msg="Cannot disable " + IMAGE_STATES[state] + " image!")
|
||||||
|
|
||||||
|
if ((enable and state != IMAGE_STATES.index('READY')) or
|
||||||
|
(not enable and state != IMAGE_STATES.index('DISABLED'))):
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if changed and not module.check_mode:
|
||||||
|
client.call('image.enable', image.id, enable)
|
||||||
|
|
||||||
|
result = get_image_info(image)
|
||||||
|
result['changed'] = changed
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def clone_image(module, client, image, new_name):
|
||||||
|
if new_name is None:
|
||||||
|
new_name = "Copy of " + image.name
|
||||||
|
|
||||||
|
tmp_image = get_image_by_name(module, client, new_name)
|
||||||
|
if tmp_image:
|
||||||
|
result = get_image_info(tmp_image)
|
||||||
|
result['changed'] = False
|
||||||
|
return result
|
||||||
|
|
||||||
|
if image.state == IMAGE_STATES.index('DISABLED'):
|
||||||
|
module.fail_json(msg="Cannot clone DISABLED image")
|
||||||
|
|
||||||
|
if not module.check_mode:
|
||||||
|
new_id = client.call('image.clone', image.id, new_name)
|
||||||
|
image = get_image_by_id(module, client, new_id)
|
||||||
|
wait_for_ready(module, image)
|
||||||
|
|
||||||
|
result = get_image_info(image)
|
||||||
|
result['changed'] = True
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def rename_image(module, client, image, new_name):
|
||||||
|
if new_name is None:
|
||||||
|
module.fail_json(msg="'new_name' option has to be specified when the state is 'renamed'")
|
||||||
|
|
||||||
|
if new_name == image.name:
|
||||||
|
result = get_image_info(image)
|
||||||
|
result['changed'] = False
|
||||||
|
return result
|
||||||
|
|
||||||
|
tmp_image = get_image_by_name(module, client, new_name)
|
||||||
|
if tmp_image:
|
||||||
|
module.fail_json(msg="Name '" + new_name + "' is already taken by IMAGE with id=" + str(tmp_image.id))
|
||||||
|
|
||||||
|
if not module.check_mode:
|
||||||
|
client.call('image.rename', image.id, new_name)
|
||||||
|
|
||||||
|
result = get_image_info(image)
|
||||||
|
result['changed'] = True
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def delete_image(module, client, image):
|
||||||
|
|
||||||
|
if not image:
|
||||||
|
return {'changed': False}
|
||||||
|
|
||||||
|
if image.running_vms > 0:
|
||||||
|
module.fail_json(msg="Cannot delete image. There are " + str(image.running_vms) + " VMs using it.")
|
||||||
|
|
||||||
|
if not module.check_mode:
|
||||||
|
client.call('image.delete', image.id)
|
||||||
|
wait_for_delete(module, image)
|
||||||
|
|
||||||
|
return {'changed': True}
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection_info(module):
|
||||||
|
|
||||||
|
url = module.params.get('api_url')
|
||||||
|
username = module.params.get('api_username')
|
||||||
|
password = module.params.get('api_password')
|
||||||
|
|
||||||
|
if not url:
|
||||||
|
url = os.environ.get('ONE_URL')
|
||||||
|
|
||||||
|
if not username:
|
||||||
|
username = os.environ.get('ONE_USERNAME')
|
||||||
|
|
||||||
|
if not password:
|
||||||
|
password = os.environ.get('ONE_PASSWORD')
|
||||||
|
|
||||||
|
if not(url and username and password):
|
||||||
|
module.fail_json(msg="One or more connection parameters (api_url, api_username, api_password) were not specified")
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
auth_params = namedtuple('auth', ('url', 'username', 'password'))
|
||||||
|
|
||||||
|
return auth_params(url=url, username=username, password=password)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
fields = {
|
||||||
|
"api_url": {"required": False, "type": "str"},
|
||||||
|
"api_username": {"required": False, "type": "str"},
|
||||||
|
"api_password": {"required": False, "type": "str", "no_log": True},
|
||||||
|
"id": {"required": False, "type": "int"},
|
||||||
|
"name": {"required": False, "type": "str"},
|
||||||
|
"state": {
|
||||||
|
"default": "present",
|
||||||
|
"choices": ['present', 'absent', 'cloned', 'renamed'],
|
||||||
|
"type": "str"
|
||||||
|
},
|
||||||
|
"enabled": {"required": False, "type": "bool"},
|
||||||
|
"new_name": {"required": False, "type": "str"},
|
||||||
|
}
|
||||||
|
|
||||||
|
module = AnsibleModule(argument_spec=fields,
|
||||||
|
mutually_exclusive=[['id', 'name']],
|
||||||
|
supports_check_mode=True)
|
||||||
|
|
||||||
|
if not HAS_OCA:
|
||||||
|
module.fail_json(msg='This module requires python-oca to work!')
|
||||||
|
|
||||||
|
auth = get_connection_info(module)
|
||||||
|
params = module.params
|
||||||
|
id = params.get('id')
|
||||||
|
name = params.get('name')
|
||||||
|
state = params.get('state')
|
||||||
|
enabled = params.get('enabled')
|
||||||
|
new_name = params.get('new_name')
|
||||||
|
client = oca.Client(auth.username + ':' + auth.password, auth.url)
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
if not id and state == 'renamed':
|
||||||
|
module.fail_json(msg="Option 'id' is required when the state is 'renamed'")
|
||||||
|
|
||||||
|
image = get_image_instance(module, client, id, name)
|
||||||
|
if not image and state != 'absent':
|
||||||
|
if id:
|
||||||
|
module.fail_json(msg="There is no image with id=" + str(id))
|
||||||
|
else:
|
||||||
|
module.fail_json(msg="There is no image with name=" + name)
|
||||||
|
|
||||||
|
if state == 'absent':
|
||||||
|
result = delete_image(module, client, image)
|
||||||
|
else:
|
||||||
|
result = get_image_info(image)
|
||||||
|
changed = False
|
||||||
|
result['changed'] = False
|
||||||
|
|
||||||
|
if enabled is not None:
|
||||||
|
result = enable_image(module, client, image, enabled)
|
||||||
|
if state == "cloned":
|
||||||
|
result = clone_image(module, client, image, new_name)
|
||||||
|
elif state == "renamed":
|
||||||
|
result = rename_image(module, client, image, new_name)
|
||||||
|
|
||||||
|
changed = changed or result['changed']
|
||||||
|
result['changed'] = changed
|
||||||
|
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -2,3 +2,4 @@
|
||||||
- hosts: localhost
|
- hosts: localhost
|
||||||
roles:
|
roles:
|
||||||
- { role: one_vm, tags: test_one_vm }
|
- { role: one_vm, tags: test_one_vm }
|
||||||
|
- { role: one_image, tags: test_one_image }
|
||||||
|
|
9
test/legacy/roles/one_image/defaults/main.yml
Normal file
9
test/legacy/roles/one_image/defaults/main.yml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
# This is a role for running integration test of the one_image module.
|
||||||
|
# For this role to be used you need to meet the following prerequisites:
|
||||||
|
# 1. Environment variables ONE_URL, ONE_USERNAME and ONE_PASSWORD
|
||||||
|
# need to be set.
|
||||||
|
# 2. Image needs to exist.
|
||||||
|
# 3. Play vars need to be set bellow to reflect the image IDs, image names, etc.
|
||||||
|
|
||||||
|
one_image_name: 'one_image_test'
|
287
test/legacy/roles/one_image/tasks/main.yml
Normal file
287
test/legacy/roles/one_image/tasks/main.yml
Normal file
|
@ -0,0 +1,287 @@
|
||||||
|
---
|
||||||
|
- name: Check that '{{ one_image_name }}' exists
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}'
|
||||||
|
|
||||||
|
- name: Try to fetch non-existent image by name
|
||||||
|
one_image:
|
||||||
|
name: non-existent-vm-{{ ansible_date_time.iso8601_basic_short }}
|
||||||
|
register: image_missing
|
||||||
|
failed_when: not image_missing is failed
|
||||||
|
|
||||||
|
- name: Try to fetch non-existent image by id
|
||||||
|
one_image:
|
||||||
|
id: -999
|
||||||
|
register: image_missing
|
||||||
|
failed_when: not image_missing is failed
|
||||||
|
|
||||||
|
- name: Try to fetch image by id and name
|
||||||
|
one_image:
|
||||||
|
id: 35
|
||||||
|
name: '{{ one_image_name }}'
|
||||||
|
register: module_failed
|
||||||
|
failed_when: not module_failed is failed
|
||||||
|
|
||||||
|
- name: Fetch image info
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}'
|
||||||
|
register: unused_image
|
||||||
|
|
||||||
|
- name: Check is the image in USE
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not unused_image is changed
|
||||||
|
- unused_image.name == one_image_name
|
||||||
|
- unused_image.running_vms == 0
|
||||||
|
- unused_image.state == "READY"
|
||||||
|
- not unused_image.used|bool
|
||||||
|
msg: 'Image is USED'
|
||||||
|
|
||||||
|
- name: Enable image
|
||||||
|
one_image:
|
||||||
|
id: '{{ unused_image.id }}'
|
||||||
|
enabled: yes
|
||||||
|
|
||||||
|
- name: Disable the image in check-mode
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}'
|
||||||
|
enabled: no
|
||||||
|
check_mode: yes
|
||||||
|
register: disable_image
|
||||||
|
|
||||||
|
- name: Check if task in check-mode returns as 'changed'
|
||||||
|
assert:
|
||||||
|
that: disable_image is changed
|
||||||
|
msg: 'Disabling the enabled image in check-mode should return as changed.'
|
||||||
|
|
||||||
|
- name: Disable the image again in check-mode to check idempotence
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}'
|
||||||
|
enabled: no
|
||||||
|
check_mode: yes
|
||||||
|
register: disable_image2
|
||||||
|
|
||||||
|
- name: Check if task in check-mode returns as 'changed'
|
||||||
|
assert:
|
||||||
|
that: disable_image2 is changed
|
||||||
|
msg: 'Disabling the enabled image in check-mode should return as changed.'
|
||||||
|
|
||||||
|
- name: Disable the image
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}'
|
||||||
|
enabled: no
|
||||||
|
register: disable_image
|
||||||
|
|
||||||
|
- name: Check if image's state is 'DISABLED'
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- disable_image is changed
|
||||||
|
- disable_image.state == "DISABLED"
|
||||||
|
msg: 'Disabling the enabled image was unsuccessful.'
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: Try to clone disabled image
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}'
|
||||||
|
state: cloned
|
||||||
|
new_name: '{{ one_image_name }}-clone'
|
||||||
|
register: clone_image
|
||||||
|
failed_when: not clone_image is failed
|
||||||
|
rescue:
|
||||||
|
- name: Delete new image
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}-clone'
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Enable the image
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}'
|
||||||
|
enabled: yes
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: Check that clone image doesn't exist
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}-clone'
|
||||||
|
register: clone_image_result
|
||||||
|
failed_when: not clone_image_result is failed
|
||||||
|
|
||||||
|
- name: Clone the image in check-mode
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}'
|
||||||
|
state: cloned
|
||||||
|
new_name: '{{ one_image_name }}-clone'
|
||||||
|
register: new_image
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: Check if cloning in check-mode was returned as 'changed'
|
||||||
|
assert:
|
||||||
|
that: new_image is changed
|
||||||
|
msg: "Cloning image in check-mode should be returned as 'changed'"
|
||||||
|
|
||||||
|
- name: Check that new image doesn't exist
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}-clone'
|
||||||
|
register: new_image_result
|
||||||
|
failed_when: not new_image_result is failed
|
||||||
|
|
||||||
|
- name: Clone the image
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}'
|
||||||
|
state: cloned
|
||||||
|
new_name: '{{ one_image_name }}-clone'
|
||||||
|
register: new_image
|
||||||
|
|
||||||
|
- name: Verify cloning of the image
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- new_image is changed
|
||||||
|
- new_image.name == '{{ one_image_name }}-clone'
|
||||||
|
- new_image.state == "READY"
|
||||||
|
- not new_image.used|bool
|
||||||
|
|
||||||
|
- name: Clone the image again to check idempotence
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}'
|
||||||
|
state: cloned
|
||||||
|
new_name: '{{ one_image_name }}-clone'
|
||||||
|
register: new_image
|
||||||
|
|
||||||
|
- name: Verify cloning of the image
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not new_image is changed
|
||||||
|
- new_image.name == '{{ one_image_name }}-clone'
|
||||||
|
- new_image.state == "READY"
|
||||||
|
- not new_image.used|bool
|
||||||
|
|
||||||
|
- name: Try to rename an image without a passed new name
|
||||||
|
one_image:
|
||||||
|
id: '{{ new_image.id }}'
|
||||||
|
state: renamed
|
||||||
|
register: rename_fail
|
||||||
|
failed_when: not rename_fail is failed
|
||||||
|
|
||||||
|
- name: Verify a fail message
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- rename_fail.msg == "'new_name' option has to be specified when the state is 'renamed'"
|
||||||
|
|
||||||
|
- name: Set the image's new name
|
||||||
|
set_fact:
|
||||||
|
image_new_name: test-{{ ansible_date_time.iso8601_basic_short }}
|
||||||
|
|
||||||
|
- name: Try to rename an image without specified id
|
||||||
|
one_image:
|
||||||
|
name: '{{ new_image.name }}'
|
||||||
|
state: renamed
|
||||||
|
new_name: '{{ image_new_name }}'
|
||||||
|
register: rename_fail
|
||||||
|
failed_when: not rename_fail is failed
|
||||||
|
|
||||||
|
- name: Verify a fail message
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- rename_fail.msg == "Option 'id' is required when the state is 'renamed'"
|
||||||
|
|
||||||
|
- name: Rename cloned instance in check-mode
|
||||||
|
one_image:
|
||||||
|
id: '{{ new_image.id }}'
|
||||||
|
state: renamed
|
||||||
|
new_name: '{{ image_new_name }}'
|
||||||
|
register: new_name_check
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: Check if previous task is returned as 'changed'
|
||||||
|
assert:
|
||||||
|
that: new_name_check is changed
|
||||||
|
msg: "Renaming in check-mode should return as 'changed'."
|
||||||
|
|
||||||
|
- name: Check if that image wasn't renamed in check-mode
|
||||||
|
assert:
|
||||||
|
that: new_name_check.name == new_image.name
|
||||||
|
msg: "Renaming in check-mode shouldn't rename the image."
|
||||||
|
|
||||||
|
- name: Rename cloned instance
|
||||||
|
one_image:
|
||||||
|
id: '{{ new_image.id }}'
|
||||||
|
state: renamed
|
||||||
|
new_name: '{{ image_new_name }}'
|
||||||
|
register: new_name
|
||||||
|
|
||||||
|
- name: Check that name is correctly assigned
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- new_name is changed
|
||||||
|
- new_name.name == image_new_name
|
||||||
|
- new_name.id == new_image.id
|
||||||
|
msg: "The new name wasn't assigned correctly"
|
||||||
|
|
||||||
|
- name: Rename cloned instance again to check idempotence
|
||||||
|
one_image:
|
||||||
|
id: '{{ new_name.id }}'
|
||||||
|
state: renamed
|
||||||
|
new_name: '{{ image_new_name }}'
|
||||||
|
register: new_name
|
||||||
|
|
||||||
|
- name: Check if renaming is idempotent
|
||||||
|
assert:
|
||||||
|
that: not new_name is changed
|
||||||
|
msg: "Renaming should be idempotent."
|
||||||
|
|
||||||
|
- name: Try to assigned name of the existent image
|
||||||
|
one_image:
|
||||||
|
id: '{{ new_name.id }}'
|
||||||
|
state: renamed
|
||||||
|
new_name: '{{ one_image_name }}'
|
||||||
|
register: existent_name
|
||||||
|
failed_when: not existent_name is failed
|
||||||
|
|
||||||
|
- name: Verify the fail message
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- existent_name.msg is match("Name '{{ one_image_name }}' is already taken by IMAGE with id=\d+")
|
||||||
|
|
||||||
|
- name: Delete new image in check-mode
|
||||||
|
one_image:
|
||||||
|
name: '{{ image_new_name }}'
|
||||||
|
state: absent
|
||||||
|
register: delete_new_image_check
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: Check if deletion in check-mode was returned as 'changed'
|
||||||
|
assert:
|
||||||
|
that: delete_new_image_check is changed
|
||||||
|
msg: "Deletion of the image in check-mode should return as 'changed'."
|
||||||
|
|
||||||
|
- name: Delete new image
|
||||||
|
one_image:
|
||||||
|
name: '{{ image_new_name }}'
|
||||||
|
state: absent
|
||||||
|
register: delete_new_image
|
||||||
|
|
||||||
|
- name: Check if deletion was returned as 'changed'
|
||||||
|
assert:
|
||||||
|
that: delete_new_image is changed
|
||||||
|
msg: "Deletion of the existent image should return as 'changed'."
|
||||||
|
|
||||||
|
- name: Delete the image again to check idempotece
|
||||||
|
one_image:
|
||||||
|
name: '{{ image_new_name }}'
|
||||||
|
state: absent
|
||||||
|
register: delete_new_image
|
||||||
|
|
||||||
|
- name: Check if deletion was returned as 'changed'
|
||||||
|
assert:
|
||||||
|
that: not delete_new_image is changed
|
||||||
|
msg: "Deletion of the non-existent image shouldn't return as 'changed'."
|
||||||
|
|
||||||
|
always:
|
||||||
|
- name: Delete image
|
||||||
|
one_image:
|
||||||
|
name: '{{ one_image_name }}-clone'
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Delete image
|
||||||
|
one_image:
|
||||||
|
name: '{{ image_new_name }}'
|
||||||
|
state: absent
|
Loading…
Reference in a new issue