mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-18 19:00:22 -07:00
Unflatmap community.general (#5461)
* Move files. * Update imports and references. * Move wrongly placed files. * Reverse redirects, deprecate long → short name redirects. * Simplify contribution guidelines for new modules. * Rewrite BOTMETA. * Add changelog fragment. * Fix ignore.txt files.
This commit is contained in:
parent
2b0bebc8fc
commit
b531ecdc9b
1033 changed files with 4802 additions and 1989 deletions
426
plugins/modules/one_image.py
Normal file
426
plugins/modules/one_image.py
Normal file
|
@ -0,0 +1,426 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# 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/>.
|
||||
"""
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: one_image
|
||||
short_description: Manages OpenNebula images
|
||||
description:
|
||||
- Manages OpenNebula images
|
||||
requirements:
|
||||
- pyone
|
||||
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.
|
||||
type: str
|
||||
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.
|
||||
type: str
|
||||
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.
|
||||
type: str
|
||||
id:
|
||||
description:
|
||||
- A C(id) of the image you would like to manage.
|
||||
type: int
|
||||
name:
|
||||
description:
|
||||
- A C(name) of the image you would like to manage.
|
||||
type: str
|
||||
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
|
||||
type: str
|
||||
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'.
|
||||
type: str
|
||||
author:
|
||||
- "Milan Ilic (@ilicmilan)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Fetch the IMAGE by id
|
||||
community.general.one_image:
|
||||
id: 45
|
||||
register: result
|
||||
|
||||
- name: Print the IMAGE properties
|
||||
ansible.builtin.debug:
|
||||
var: result
|
||||
|
||||
- name: Rename existing IMAGE
|
||||
community.general.one_image:
|
||||
id: 34
|
||||
state: renamed
|
||||
new_name: bar-image
|
||||
|
||||
- name: Disable the IMAGE by id
|
||||
community.general.one_image:
|
||||
id: 37
|
||||
enabled: false
|
||||
|
||||
- name: Enable the IMAGE by name
|
||||
community.general.one_image:
|
||||
name: bar-image
|
||||
enabled: true
|
||||
|
||||
- name: Clone the IMAGE by name
|
||||
community.general.one_image:
|
||||
name: bar-image
|
||||
state: cloned
|
||||
new_name: bar-image-clone
|
||||
register: result
|
||||
|
||||
- name: Delete the IMAGE by id
|
||||
community.general.one_image:
|
||||
id: '{{ result.id }}'
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
id:
|
||||
description: image id
|
||||
type: int
|
||||
returned: success
|
||||
sample: 153
|
||||
name:
|
||||
description: image name
|
||||
type: str
|
||||
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: str
|
||||
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: str
|
||||
returned: success
|
||||
sample: ansible-test
|
||||
state:
|
||||
description: state of image instance
|
||||
type: str
|
||||
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 pyone
|
||||
HAS_PYONE = True
|
||||
except ImportError:
|
||||
HAS_PYONE = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
import os
|
||||
|
||||
|
||||
def get_image(module, client, predicate):
|
||||
# Filter -2 means fetch all images user can Use
|
||||
pool = client.imagepool.info(-2, -1, -1, -1)
|
||||
|
||||
for image in pool.IMAGE:
|
||||
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):
|
||||
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, client, image_id, wait_timeout, state_predicate):
|
||||
import time
|
||||
start_time = time.time()
|
||||
|
||||
while (time.time() - start_time) < wait_timeout:
|
||||
image = client.image.info(image_id)
|
||||
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, client, image_id, wait_timeout=60):
|
||||
return wait_for_state(module, client, image_id, wait_timeout, lambda state: (state in [IMAGE_STATES.index('READY')]))
|
||||
|
||||
|
||||
def wait_for_delete(module, client, image_id, wait_timeout=60):
|
||||
return wait_for_state(module, client, image_id, wait_timeout, lambda state: (state in [IMAGE_STATES.index('DELETE')]))
|
||||
|
||||
|
||||
def enable_image(module, client, image, enable):
|
||||
image = client.image.info(image.ID)
|
||||
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.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.image.clone(image.ID, new_name)
|
||||
wait_for_ready(module, client, new_id)
|
||||
image = client.image.info(new_id)
|
||||
|
||||
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.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.image.delete(image.ID)
|
||||
wait_for_delete(module, client, image.ID)
|
||||
|
||||
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_PYONE:
|
||||
module.fail_json(msg='This module requires pyone 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 = pyone.OneServer(auth.url, session=auth.username + ':' + auth.password)
|
||||
|
||||
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()
|
Loading…
Add table
Add a link
Reference in a new issue