mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-22 12:50:22 -07:00
Initial commit
This commit is contained in:
commit
aebc1b03fd
4861 changed files with 812621 additions and 0 deletions
308
plugins/modules/cloud/smartos/imgadm.py
Normal file
308
plugins/modules/cloud/smartos/imgadm.py
Normal file
|
@ -0,0 +1,308 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2016, 2017 Jasper Lievisse Adriaanse <j@jasper.la>
|
||||
# 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
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: imgadm
|
||||
short_description: Manage SmartOS images
|
||||
description:
|
||||
- Manage SmartOS virtual machine images through imgadm(1M)
|
||||
author: Jasper Lievisse Adriaanse (@jasperla)
|
||||
options:
|
||||
force:
|
||||
required: false
|
||||
type: bool
|
||||
description:
|
||||
- Force a given operation (where supported by imgadm(1M)).
|
||||
pool:
|
||||
required: false
|
||||
default: zones
|
||||
description:
|
||||
- zpool to import to or delete images from.
|
||||
source:
|
||||
required: false
|
||||
description:
|
||||
- URI for the image source.
|
||||
state:
|
||||
required: true
|
||||
choices: [ present, absent, deleted, imported, updated, vacuumed ]
|
||||
description:
|
||||
- State the object operated on should be in. C(imported) is an alias for
|
||||
for C(present) and C(deleted) for C(absent). When set to C(vacuumed)
|
||||
and C(uuid) to C(*), it will remove all unused images.
|
||||
type:
|
||||
required: false
|
||||
choices: [ imgapi, docker, dsapi ]
|
||||
default: imgapi
|
||||
description:
|
||||
- Type for image sources.
|
||||
uuid:
|
||||
required: false
|
||||
description:
|
||||
- Image UUID. Can either be a full UUID or C(*) for all images.
|
||||
requirements:
|
||||
- python >= 2.6
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Import an image
|
||||
imgadm:
|
||||
uuid: '70e3ae72-96b6-11e6-9056-9737fd4d0764'
|
||||
state: imported
|
||||
|
||||
- name: Delete an image
|
||||
imgadm:
|
||||
uuid: '70e3ae72-96b6-11e6-9056-9737fd4d0764'
|
||||
state: deleted
|
||||
|
||||
- name: Update all images
|
||||
imgadm:
|
||||
uuid: '*'
|
||||
state: updated
|
||||
|
||||
- name: Update a single image
|
||||
imgadm:
|
||||
uuid: '70e3ae72-96b6-11e6-9056-9737fd4d0764'
|
||||
state: updated
|
||||
|
||||
- name: Add a source
|
||||
imgadm:
|
||||
source: 'https://datasets.project-fifo.net'
|
||||
state: present
|
||||
|
||||
- name: Add a Docker source
|
||||
imgadm:
|
||||
source: 'https://docker.io'
|
||||
type: docker
|
||||
state: present
|
||||
|
||||
- name: Remove a source
|
||||
imgadm:
|
||||
source: 'https://docker.io'
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
source:
|
||||
description: Source that is managed.
|
||||
returned: When not managing an image.
|
||||
type: str
|
||||
sample: https://datasets.project-fifo.net
|
||||
uuid:
|
||||
description: UUID for an image operated on.
|
||||
returned: When not managing an image source.
|
||||
type: str
|
||||
sample: 70e3ae72-96b6-11e6-9056-9737fd4d0764
|
||||
state:
|
||||
description: State of the target, after execution.
|
||||
returned: success
|
||||
type: str
|
||||
sample: 'present'
|
||||
'''
|
||||
|
||||
import re
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
# Shortcut for the imgadm(1M) command. While imgadm(1M) supports a
|
||||
# -E option to return any errors in JSON, the generated JSON does not play well
|
||||
# with the JSON parsers of Python. The returned message contains '\n' as part of
|
||||
# the stacktrace, which breaks the parsers.
|
||||
|
||||
|
||||
class Imgadm(object):
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.params = module.params
|
||||
self.cmd = module.get_bin_path('imgadm', required=True)
|
||||
self.changed = False
|
||||
self.uuid = module.params['uuid']
|
||||
|
||||
# Since there are a number of (natural) aliases, prevent having to look
|
||||
# them up everytime we operate on `state`.
|
||||
if self.params['state'] in ['present', 'imported', 'updated']:
|
||||
self.present = True
|
||||
else:
|
||||
self.present = False
|
||||
|
||||
# Perform basic UUID validation upfront.
|
||||
if self.uuid and self.uuid != '*':
|
||||
if not re.match('^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$', self.uuid, re.IGNORECASE):
|
||||
module.fail_json(msg='Provided value for uuid option is not a valid UUID.')
|
||||
|
||||
# Helper method to massage stderr
|
||||
def errmsg(self, stderr):
|
||||
match = re.match(r'^imgadm .*?: error \(\w+\): (.*): .*', stderr)
|
||||
if match:
|
||||
return match.groups()[0]
|
||||
else:
|
||||
return 'Unexpected failure'
|
||||
|
||||
def update_images(self):
|
||||
if self.uuid == '*':
|
||||
cmd = '{0} update'.format(self.cmd)
|
||||
else:
|
||||
cmd = '{0} update {1}'.format(self.cmd, self.uuid)
|
||||
|
||||
(rc, stdout, stderr) = self.module.run_command(cmd)
|
||||
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg='Failed to update images: {0}'.format(self.errmsg(stderr)))
|
||||
|
||||
# There is no feedback from imgadm(1M) to determine if anything
|
||||
# was actually changed. So treat this as an 'always-changes' operation.
|
||||
# Note that 'imgadm -v' produces unparseable JSON...
|
||||
self.changed = True
|
||||
|
||||
def manage_sources(self):
|
||||
force = self.params['force']
|
||||
source = self.params['source']
|
||||
imgtype = self.params['type']
|
||||
|
||||
cmd = '{0} sources'.format(self.cmd)
|
||||
|
||||
if force:
|
||||
cmd += ' -f'
|
||||
|
||||
if self.present:
|
||||
cmd = '{0} -a {1} -t {2}'.format(cmd, source, imgtype)
|
||||
(rc, stdout, stderr) = self.module.run_command(cmd)
|
||||
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg='Failed to add source: {0}'.format(self.errmsg(stderr)))
|
||||
|
||||
# Check the various responses.
|
||||
# Note that trying to add a source with the wrong type is handled
|
||||
# above as it results in a non-zero status.
|
||||
|
||||
regex = 'Already have "{0}" image source "{1}", no change'.format(imgtype, source)
|
||||
if re.match(regex, stdout):
|
||||
self.changed = False
|
||||
|
||||
regex = 'Added "%s" image source "%s"' % (imgtype, source)
|
||||
if re.match(regex, stdout):
|
||||
self.changed = True
|
||||
else:
|
||||
# Type is ignored by imgadm(1M) here
|
||||
cmd += ' -d %s' % source
|
||||
(rc, stdout, stderr) = self.module.run_command(cmd)
|
||||
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg='Failed to remove source: {0}'.format(self.errmsg(stderr)))
|
||||
|
||||
regex = 'Do not have image source "%s", no change' % source
|
||||
if re.match(regex, stdout):
|
||||
self.changed = False
|
||||
|
||||
regex = 'Deleted ".*" image source "%s"' % source
|
||||
if re.match(regex, stdout):
|
||||
self.changed = True
|
||||
|
||||
def manage_images(self):
|
||||
pool = self.params['pool']
|
||||
state = self.params['state']
|
||||
|
||||
if state == 'vacuumed':
|
||||
# Unconditionally pass '--force', otherwise we're prompted with 'y/N'
|
||||
cmd = '{0} vacuum -f'.format(self.cmd)
|
||||
|
||||
(rc, stdout, stderr) = self.module.run_command(cmd)
|
||||
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg='Failed to vacuum images: {0}'.format(self.errmsg(stderr)))
|
||||
else:
|
||||
if stdout == '':
|
||||
self.changed = False
|
||||
else:
|
||||
self.changed = True
|
||||
if self.present:
|
||||
cmd = '{0} import -P {1} -q {2}'.format(self.cmd, pool, self.uuid)
|
||||
|
||||
(rc, stdout, stderr) = self.module.run_command(cmd)
|
||||
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg='Failed to import image: {0}'.format(self.errmsg(stderr)))
|
||||
|
||||
regex = r'Image {0} \(.*\) is already installed, skipping'.format(self.uuid)
|
||||
if re.match(regex, stdout):
|
||||
self.changed = False
|
||||
|
||||
regex = '.*ActiveImageNotFound.*'
|
||||
if re.match(regex, stderr):
|
||||
self.changed = False
|
||||
|
||||
regex = 'Imported image {0}.*'.format(self.uuid)
|
||||
if re.match(regex, stdout.splitlines()[-1]):
|
||||
self.changed = True
|
||||
else:
|
||||
cmd = '{0} delete -P {1} {2}'.format(self.cmd, pool, self.uuid)
|
||||
|
||||
(rc, stdout, stderr) = self.module.run_command(cmd)
|
||||
|
||||
regex = '.*ImageNotInstalled.*'
|
||||
if re.match(regex, stderr):
|
||||
# Even if the 'rc' was non-zero (3), we handled the situation
|
||||
# in order to determine if there was a change.
|
||||
self.changed = False
|
||||
|
||||
regex = 'Deleted image {0}'.format(self.uuid)
|
||||
if re.match(regex, stdout):
|
||||
self.changed = True
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
force=dict(default=None, type='bool'),
|
||||
pool=dict(default='zones'),
|
||||
source=dict(default=None),
|
||||
state=dict(default=None, required=True, choices=['present', 'absent', 'deleted', 'imported', 'updated', 'vacuumed']),
|
||||
type=dict(default='imgapi', choices=['imgapi', 'docker', 'dsapi']),
|
||||
uuid=dict(default=None)
|
||||
),
|
||||
# This module relies largely on imgadm(1M) to enforce idempotency, which does not
|
||||
# provide a "noop" (or equivalent) mode to do a dry-run.
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
imgadm = Imgadm(module)
|
||||
|
||||
uuid = module.params['uuid']
|
||||
source = module.params['source']
|
||||
state = module.params['state']
|
||||
|
||||
result = {'state': state}
|
||||
|
||||
# Either manage sources or images.
|
||||
if source:
|
||||
result['source'] = source
|
||||
imgadm.manage_sources()
|
||||
else:
|
||||
result['uuid'] = uuid
|
||||
|
||||
if state == 'updated':
|
||||
imgadm.update_images()
|
||||
else:
|
||||
# Make sure operate on a single image for the following actions
|
||||
if (uuid == '*') and (state != 'vacuumed'):
|
||||
module.fail_json(msg='Can only specify uuid as "*" when updating image(s)')
|
||||
imgadm.manage_images()
|
||||
|
||||
result['changed'] = imgadm.changed
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
238
plugins/modules/cloud/smartos/nictagadm.py
Normal file
238
plugins/modules/cloud/smartos/nictagadm.py
Normal file
|
@ -0,0 +1,238 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Bruce Smith <Bruce.Smith.IT@gmail.com>
|
||||
# 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
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: nictagadm
|
||||
short_description: Manage nic tags on SmartOS systems
|
||||
description:
|
||||
- Create or delete nic tags on SmartOS systems.
|
||||
author:
|
||||
- Bruce Smith (@SmithX10)
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the nic tag.
|
||||
required: true
|
||||
type: str
|
||||
mac:
|
||||
description:
|
||||
- Specifies the I(mac) address to attach the nic tag to when not creating an I(etherstub).
|
||||
- Parameters I(mac) and I(etherstub) are mutually exclusive.
|
||||
type: str
|
||||
etherstub:
|
||||
description:
|
||||
- Specifies that the nic tag will be attached to a created I(etherstub).
|
||||
- Parameter I(etherstub) is mutually exclusive with both I(mtu), and I(mac).
|
||||
type: bool
|
||||
default: no
|
||||
mtu:
|
||||
description:
|
||||
- Specifies the size of the I(mtu) of the desired nic tag.
|
||||
- Parameters I(mtu) and I(etherstub) are mutually exclusive.
|
||||
type: int
|
||||
force:
|
||||
description:
|
||||
- When I(state) is absent set this switch will use the C(-f) parameter and delete the nic tag regardless of existing VMs.
|
||||
type: bool
|
||||
default: no
|
||||
state:
|
||||
description:
|
||||
- Create or delete a SmartOS nic tag.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create 'storage0' on '00:1b:21:a3:f5:4d'
|
||||
nictagadm:
|
||||
name: storage0
|
||||
mac: 00:1b:21:a3:f5:4d
|
||||
mtu: 9000
|
||||
state: present
|
||||
|
||||
- name: Remove 'storage0' nic tag
|
||||
nictagadm:
|
||||
name: storage0
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
name:
|
||||
description: nic tag name
|
||||
returned: always
|
||||
type: str
|
||||
sample: storage0
|
||||
mac:
|
||||
description: MAC Address that the nic tag was attached to.
|
||||
returned: always
|
||||
type: str
|
||||
sample: 00:1b:21:a3:f5:4d
|
||||
etherstub:
|
||||
description: specifies if the nic tag will create and attach to an etherstub.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: False
|
||||
mtu:
|
||||
description: specifies which MTU size was passed during the nictagadm add command. mtu and etherstub are mutually exclusive.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 1500
|
||||
force:
|
||||
description: Shows if -f was used during the deletion of a nic tag
|
||||
returned: always
|
||||
type: bool
|
||||
sample: False
|
||||
state:
|
||||
description: state of the target
|
||||
returned: always
|
||||
type: str
|
||||
sample: present
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.common.network import is_mac
|
||||
|
||||
|
||||
class NicTag(object):
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
self.name = module.params['name']
|
||||
self.mac = module.params['mac']
|
||||
self.etherstub = module.params['etherstub']
|
||||
self.mtu = module.params['mtu']
|
||||
self.force = module.params['force']
|
||||
self.state = module.params['state']
|
||||
|
||||
self.nictagadm_bin = self.module.get_bin_path('nictagadm', True)
|
||||
|
||||
def is_valid_mac(self):
|
||||
return is_mac(self.mac.lower())
|
||||
|
||||
def nictag_exists(self):
|
||||
cmd = [self.nictagadm_bin]
|
||||
|
||||
cmd.append('exists')
|
||||
cmd.append(self.name)
|
||||
|
||||
(rc, dummy, dummy) = self.module.run_command(cmd)
|
||||
|
||||
return rc == 0
|
||||
|
||||
def add_nictag(self):
|
||||
cmd = [self.nictagadm_bin]
|
||||
|
||||
cmd.append('-v')
|
||||
cmd.append('add')
|
||||
|
||||
if self.etherstub:
|
||||
cmd.append('-l')
|
||||
|
||||
if self.mtu:
|
||||
cmd.append('-p')
|
||||
cmd.append('mtu=' + str(self.mtu))
|
||||
|
||||
if self.mac:
|
||||
cmd.append('-p')
|
||||
cmd.append('mac=' + str(self.mac))
|
||||
|
||||
cmd.append(self.name)
|
||||
|
||||
return self.module.run_command(cmd)
|
||||
|
||||
def delete_nictag(self):
|
||||
cmd = [self.nictagadm_bin]
|
||||
|
||||
cmd.append('-v')
|
||||
cmd.append('delete')
|
||||
|
||||
if self.force:
|
||||
cmd.append('-f')
|
||||
|
||||
cmd.append(self.name)
|
||||
|
||||
return self.module.run_command(cmd)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(type='str', required=True),
|
||||
mac=dict(type='str'),
|
||||
etherstub=dict(type='bool', default=False),
|
||||
mtu=dict(type='int'),
|
||||
force=dict(type='bool', default=False),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||
),
|
||||
mutually_exclusive=[
|
||||
['etherstub', 'mac'],
|
||||
['etherstub', 'mtu'],
|
||||
],
|
||||
required_if=[
|
||||
['etherstub', False, ['name', 'mac']],
|
||||
['state', 'absent', ['name', 'force']],
|
||||
],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
nictag = NicTag(module)
|
||||
|
||||
rc = None
|
||||
out = ''
|
||||
err = ''
|
||||
result = dict(
|
||||
changed=False,
|
||||
etherstub=nictag.etherstub,
|
||||
force=nictag.force,
|
||||
name=nictag.name,
|
||||
mac=nictag.mac,
|
||||
mtu=nictag.mtu,
|
||||
state=nictag.state,
|
||||
)
|
||||
|
||||
if not nictag.is_valid_mac():
|
||||
module.fail_json(msg='Invalid MAC Address Value',
|
||||
name=nictag.name,
|
||||
mac=nictag.mac,
|
||||
etherstub=nictag.etherstub)
|
||||
|
||||
if nictag.state == 'absent':
|
||||
if nictag.nictag_exists():
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
(rc, out, err) = nictag.delete_nictag()
|
||||
if rc != 0:
|
||||
module.fail_json(name=nictag.name, msg=err, rc=rc)
|
||||
elif nictag.state == 'present':
|
||||
if not nictag.nictag_exists():
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
(rc, out, err) = nictag.add_nictag()
|
||||
if rc is not None and rc != 0:
|
||||
module.fail_json(name=nictag.name, msg=err, rc=rc)
|
||||
|
||||
if rc is not None:
|
||||
result['changed'] = True
|
||||
if out:
|
||||
result['stdout'] = out
|
||||
if err:
|
||||
result['stderr'] = err
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
1
plugins/modules/cloud/smartos/smartos_image_facts.py
Symbolic link
1
plugins/modules/cloud/smartos/smartos_image_facts.py
Symbolic link
|
@ -0,0 +1 @@
|
|||
smartos_image_info.py
|
123
plugins/modules/cloud/smartos/smartos_image_info.py
Normal file
123
plugins/modules/cloud/smartos/smartos_image_info.py
Normal file
|
@ -0,0 +1,123 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2015, Adam Števko <adam.stevko@gmail.com>
|
||||
# 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
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: smartos_image_info
|
||||
short_description: Get SmartOS image details.
|
||||
description:
|
||||
- Retrieve information about all installed images on SmartOS.
|
||||
- This module was called C(smartos_image_facts) before Ansible 2.9, returning C(ansible_facts).
|
||||
Note that the M(smartos_image_info) module no longer returns C(ansible_facts)!
|
||||
author: Adam Števko (@xen0l)
|
||||
options:
|
||||
filters:
|
||||
description:
|
||||
- Criteria for selecting image. Can be any value from image
|
||||
manifest and 'published_date', 'published', 'source', 'clones',
|
||||
and 'size'. More information can be found at U(https://smartos.org/man/1m/imgadm)
|
||||
under 'imgadm list'.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Return information about all installed images.
|
||||
- smartos_image_info:
|
||||
register: result
|
||||
|
||||
# Return all private active Linux images.
|
||||
- smartos_image_info: filters="os=linux state=active public=false"
|
||||
register: result
|
||||
|
||||
# Show, how many clones does every image have.
|
||||
- smartos_image_info:
|
||||
register: result
|
||||
|
||||
- debug: msg="{{ result.smartos_images[item]['name'] }}-{{ result.smartos_images[item]['version'] }}
|
||||
has {{ result.smartos_images[item]['clones'] }} VM(s)"
|
||||
with_items: "{{ result.smartos_images.keys() | list }}"
|
||||
|
||||
# When the module is called as smartos_image_facts, return values are published
|
||||
# in ansible_facts['smartos_images'] and can be used as follows.
|
||||
# Note that this is deprecated and will stop working in Ansible 2.13.
|
||||
- debug: msg="{{ smartos_images[item]['name'] }}-{{ smartos_images[item]['version'] }}
|
||||
has {{ smartos_images[item]['clones'] }} VM(s)"
|
||||
with_items: "{{ smartos_images.keys() | list }}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
import json
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
class ImageFacts(object):
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
self.filters = module.params['filters']
|
||||
|
||||
def return_all_installed_images(self):
|
||||
cmd = [self.module.get_bin_path('imgadm')]
|
||||
|
||||
cmd.append('list')
|
||||
cmd.append('-j')
|
||||
|
||||
if self.filters:
|
||||
cmd.append(self.filters)
|
||||
|
||||
(rc, out, err) = self.module.run_command(cmd)
|
||||
|
||||
if rc != 0:
|
||||
self.module.exit_json(
|
||||
msg='Failed to get all installed images', stderr=err)
|
||||
|
||||
images = json.loads(out)
|
||||
|
||||
result = {}
|
||||
for image in images:
|
||||
result[image['manifest']['uuid']] = image['manifest']
|
||||
# Merge additional attributes with the image manifest.
|
||||
for attrib in ['clones', 'source', 'zpool']:
|
||||
result[image['manifest']['uuid']][attrib] = image[attrib]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
filters=dict(default=None),
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
is_old_facts = module._name == 'smartos_image_facts'
|
||||
if is_old_facts:
|
||||
module.deprecate("The 'smartos_image_facts' module has been renamed to 'smartos_image_info', "
|
||||
"and the renamed one no longer returns ansible_facts", version='2.13')
|
||||
|
||||
image_facts = ImageFacts(module)
|
||||
|
||||
data = dict(smartos_images=image_facts.return_all_installed_images())
|
||||
|
||||
if is_old_facts:
|
||||
module.exit_json(ansible_facts=data)
|
||||
else:
|
||||
module.exit_json(**data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
748
plugins/modules/cloud/smartos/vmadm.py
Normal file
748
plugins/modules/cloud/smartos/vmadm.py
Normal file
|
@ -0,0 +1,748 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2017, Jasper Lievisse Adriaanse <j@jasper.la>
|
||||
# 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
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: vmadm
|
||||
short_description: Manage SmartOS virtual machines and zones.
|
||||
description:
|
||||
- Manage SmartOS virtual machines through vmadm(1M).
|
||||
author: Jasper Lievisse Adriaanse (@jasperla)
|
||||
options:
|
||||
archive_on_delete:
|
||||
required: false
|
||||
description:
|
||||
- When enabled, the zone dataset will be mounted on C(/zones/archive)
|
||||
upon removal.
|
||||
autoboot:
|
||||
required: false
|
||||
description:
|
||||
- Whether or not a VM is booted when the system is rebooted.
|
||||
brand:
|
||||
required: true
|
||||
choices: [ joyent, joyent-minimal, lx, kvm, bhyve ]
|
||||
default: joyent
|
||||
description:
|
||||
- Type of virtual machine. The C(bhyve) option was added in Ansible 2.10.
|
||||
boot:
|
||||
required: false
|
||||
description:
|
||||
- Set the boot order for KVM VMs.
|
||||
cpu_cap:
|
||||
required: false
|
||||
description:
|
||||
- Sets a limit on the amount of CPU time that can be used by a VM.
|
||||
Use C(0) for no cap.
|
||||
cpu_shares:
|
||||
required: false
|
||||
description:
|
||||
- Sets a limit on the number of fair share scheduler (FSS) CPU shares for
|
||||
a VM. This limit is relative to all other VMs on the system.
|
||||
cpu_type:
|
||||
required: false
|
||||
choices: [ qemu64, host ]
|
||||
default: qemu64
|
||||
description:
|
||||
- Control the type of virtual CPU exposed to KVM VMs.
|
||||
customer_metadata:
|
||||
required: false
|
||||
description:
|
||||
- Metadata to be set and associated with this VM, this contain customer
|
||||
modifiable keys.
|
||||
delegate_dataset:
|
||||
required: false
|
||||
description:
|
||||
- Whether to delegate a ZFS dataset to an OS VM.
|
||||
disk_driver:
|
||||
required: false
|
||||
description:
|
||||
- Default value for a virtual disk model for KVM guests.
|
||||
disks:
|
||||
required: false
|
||||
description:
|
||||
- A list of disks to add, valid properties are documented in vmadm(1M).
|
||||
dns_domain:
|
||||
required: false
|
||||
description:
|
||||
- Domain value for C(/etc/hosts).
|
||||
docker:
|
||||
required: false
|
||||
description:
|
||||
- Docker images need this flag enabled along with the I(brand) set to C(lx).
|
||||
filesystems:
|
||||
required: false
|
||||
description:
|
||||
- Mount additional filesystems into an OS VM.
|
||||
firewall_enabled:
|
||||
required: false
|
||||
description:
|
||||
- Enables the firewall, allowing fwadm(1M) rules to be applied.
|
||||
force:
|
||||
required: false
|
||||
description:
|
||||
- Force a particular action (i.e. stop or delete a VM).
|
||||
fs_allowed:
|
||||
required: false
|
||||
description:
|
||||
- Comma separated list of filesystem types this zone is allowed to mount.
|
||||
hostname:
|
||||
required: false
|
||||
description:
|
||||
- Zone/VM hostname.
|
||||
image_uuid:
|
||||
required: false
|
||||
description:
|
||||
- Image UUID.
|
||||
indestructible_delegated:
|
||||
required: false
|
||||
description:
|
||||
- Adds an C(@indestructible) snapshot to delegated datasets.
|
||||
indestructible_zoneroot:
|
||||
required: false
|
||||
description:
|
||||
- Adds an C(@indestructible) snapshot to zoneroot.
|
||||
internal_metadata:
|
||||
required: false
|
||||
description:
|
||||
- Metadata to be set and associated with this VM, this contains operator
|
||||
generated keys.
|
||||
internal_metadata_namespace:
|
||||
required: false
|
||||
description:
|
||||
- List of namespaces to be set as I(internal_metadata-only); these namespaces
|
||||
will come from I(internal_metadata) rather than I(customer_metadata).
|
||||
kernel_version:
|
||||
required: false
|
||||
description:
|
||||
- Kernel version to emulate for LX VMs.
|
||||
limit_priv:
|
||||
required: false
|
||||
description:
|
||||
- Set (comma separated) list of privileges the zone is allowed to use.
|
||||
maintain_resolvers:
|
||||
required: false
|
||||
description:
|
||||
- Resolvers in C(/etc/resolv.conf) will be updated when updating
|
||||
the I(resolvers) property.
|
||||
max_locked_memory:
|
||||
required: false
|
||||
description:
|
||||
- Total amount of memory (in MiBs) on the host that can be locked by this VM.
|
||||
max_lwps:
|
||||
required: false
|
||||
description:
|
||||
- Maximum number of lightweight processes this VM is allowed to have running.
|
||||
max_physical_memory:
|
||||
required: false
|
||||
description:
|
||||
- Maximum amount of memory (in MiBs) on the host that the VM is allowed to use.
|
||||
max_swap:
|
||||
required: false
|
||||
description:
|
||||
- Maximum amount of virtual memory (in MiBs) the VM is allowed to use.
|
||||
mdata_exec_timeout:
|
||||
required: false
|
||||
description:
|
||||
- Timeout in seconds (or 0 to disable) for the C(svc:/smartdc/mdata:execute) service
|
||||
that runs user-scripts in the zone.
|
||||
name:
|
||||
required: false
|
||||
aliases: [ alias ]
|
||||
description:
|
||||
- Name of the VM. vmadm(1M) uses this as an optional name.
|
||||
nic_driver:
|
||||
required: false
|
||||
description:
|
||||
- Default value for a virtual NIC model for KVM guests.
|
||||
nics:
|
||||
required: false
|
||||
description:
|
||||
- A list of nics to add, valid properties are documented in vmadm(1M).
|
||||
nowait:
|
||||
required: false
|
||||
description:
|
||||
- Consider the provisioning complete when the VM first starts, rather than
|
||||
when the VM has rebooted.
|
||||
qemu_opts:
|
||||
required: false
|
||||
description:
|
||||
- Additional qemu arguments for KVM guests. This overwrites the default arguments
|
||||
provided by vmadm(1M) and should only be used for debugging.
|
||||
qemu_extra_opts:
|
||||
required: false
|
||||
description:
|
||||
- Additional qemu cmdline arguments for KVM guests.
|
||||
quota:
|
||||
required: false
|
||||
description:
|
||||
- Quota on zone filesystems (in MiBs).
|
||||
ram:
|
||||
required: false
|
||||
description:
|
||||
- Amount of virtual RAM for a KVM guest (in MiBs).
|
||||
resolvers:
|
||||
required: false
|
||||
description:
|
||||
- List of resolvers to be put into C(/etc/resolv.conf).
|
||||
routes:
|
||||
required: false
|
||||
description:
|
||||
- Dictionary that maps destinations to gateways, these will be set as static
|
||||
routes in the VM.
|
||||
spice_opts:
|
||||
required: false
|
||||
description:
|
||||
- Addition options for SPICE-enabled KVM VMs.
|
||||
spice_password:
|
||||
required: false
|
||||
description:
|
||||
- Password required to connect to SPICE. By default no password is set.
|
||||
Please note this can be read from the Global Zone.
|
||||
state:
|
||||
required: true
|
||||
choices: [ present, absent, stopped, restarted ]
|
||||
description:
|
||||
- States for the VM to be in. Please note that C(present), C(stopped) and C(restarted)
|
||||
operate on a VM that is currently provisioned. C(present) means that the VM will be
|
||||
created if it was absent, and that it will be in a running state. C(absent) will
|
||||
shutdown the zone before removing it.
|
||||
C(stopped) means the zone will be created if it doesn't exist already, before shutting
|
||||
it down.
|
||||
tmpfs:
|
||||
required: false
|
||||
description:
|
||||
- Amount of memory (in MiBs) that will be available in the VM for the C(/tmp) filesystem.
|
||||
uuid:
|
||||
required: false
|
||||
description:
|
||||
- UUID of the VM. Can either be a full UUID or C(*) for all VMs.
|
||||
vcpus:
|
||||
required: false
|
||||
description:
|
||||
- Number of virtual CPUs for a KVM guest.
|
||||
vga:
|
||||
required: false
|
||||
description:
|
||||
- Specify VGA emulation used by KVM VMs.
|
||||
virtio_txburst:
|
||||
required: false
|
||||
description:
|
||||
- Number of packets that can be sent in a single flush of the tx queue of virtio NICs.
|
||||
virtio_txtimer:
|
||||
required: false
|
||||
description:
|
||||
- Timeout (in nanoseconds) for the TX timer of virtio NICs.
|
||||
vnc_password:
|
||||
required: false
|
||||
description:
|
||||
- Password required to connect to VNC. By default no password is set.
|
||||
Please note this can be read from the Global Zone.
|
||||
vnc_port:
|
||||
required: false
|
||||
description:
|
||||
- TCP port to listen of the VNC server. Or set C(0) for random,
|
||||
or C(-1) to disable.
|
||||
zfs_data_compression:
|
||||
required: false
|
||||
description:
|
||||
- Specifies compression algorithm used for this VMs data dataset. This option
|
||||
only has effect on delegated datasets.
|
||||
zfs_data_recsize:
|
||||
required: false
|
||||
description:
|
||||
- Suggested block size (power of 2) for files in the delegated dataset's filesystem.
|
||||
zfs_filesystem_limit:
|
||||
required: false
|
||||
description:
|
||||
- Maximum number of filesystems the VM can have.
|
||||
zfs_io_priority:
|
||||
required: false
|
||||
description:
|
||||
- IO throttle priority value relative to other VMs.
|
||||
zfs_root_compression:
|
||||
required: false
|
||||
description:
|
||||
- Specifies compression algorithm used for this VMs root dataset. This option
|
||||
only has effect on the zoneroot dataset.
|
||||
zfs_root_recsize:
|
||||
required: false
|
||||
description:
|
||||
- Suggested block size (power of 2) for files in the zoneroot dataset's filesystem.
|
||||
zfs_snapshot_limit:
|
||||
required: false
|
||||
description:
|
||||
- Number of snapshots the VM can have.
|
||||
zpool:
|
||||
required: false
|
||||
description:
|
||||
- ZFS pool the VM's zone dataset will be created in.
|
||||
requirements:
|
||||
- python >= 2.6
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: create SmartOS zone
|
||||
vmadm:
|
||||
brand: joyent
|
||||
state: present
|
||||
alias: fw_zone
|
||||
image_uuid: 95f265b8-96b2-11e6-9597-972f3af4b6d5
|
||||
firewall_enabled: yes
|
||||
indestructible_zoneroot: yes
|
||||
nics:
|
||||
- nic_tag: admin
|
||||
ip: dhcp
|
||||
primary: true
|
||||
internal_metadata:
|
||||
root_pw: 'secret'
|
||||
quota: 1
|
||||
|
||||
- name: Delete a zone
|
||||
vmadm:
|
||||
alias: test_zone
|
||||
state: deleted
|
||||
|
||||
- name: Stop all zones
|
||||
vmadm:
|
||||
uuid: '*'
|
||||
state: stopped
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
uuid:
|
||||
description: UUID of the managed VM.
|
||||
returned: always
|
||||
type: str
|
||||
sample: 'b217ab0b-cf57-efd8-cd85-958d0b80be33'
|
||||
alias:
|
||||
description: Alias of the managed VM.
|
||||
returned: When addressing a VM by alias.
|
||||
type: str
|
||||
sample: 'dns-zone'
|
||||
state:
|
||||
description: State of the target, after execution.
|
||||
returned: success
|
||||
type: str
|
||||
sample: 'running'
|
||||
'''
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import traceback
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
# While vmadm(1M) supports a -E option to return any errors in JSON, the
|
||||
# generated JSON does not play well with the JSON parsers of Python.
|
||||
# The returned message contains '\n' as part of the stacktrace,
|
||||
# which breaks the parsers.
|
||||
|
||||
|
||||
def get_vm_prop(module, uuid, prop):
|
||||
# Lookup a property for the given VM.
|
||||
# Returns the property, or None if not found.
|
||||
cmd = '{0} lookup -j -o {1} uuid={2}'.format(module.vmadm, prop, uuid)
|
||||
|
||||
(rc, stdout, stderr) = module.run_command(cmd)
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(
|
||||
msg='Could not perform lookup of {0} on {1}'.format(prop, uuid), exception=stderr)
|
||||
|
||||
try:
|
||||
stdout_json = json.loads(stdout)
|
||||
except Exception as e:
|
||||
module.fail_json(
|
||||
msg='Invalid JSON returned by vmadm for uuid lookup of {0}'.format(prop),
|
||||
details=to_native(e), exception=traceback.format_exc())
|
||||
|
||||
if len(stdout_json) > 0 and prop in stdout_json[0]:
|
||||
return stdout_json[0][prop]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_vm_uuid(module, alias):
|
||||
# Lookup the uuid that goes with the given alias.
|
||||
# Returns the uuid or '' if not found.
|
||||
cmd = '{0} lookup -j -o uuid alias={1}'.format(module.vmadm, alias)
|
||||
|
||||
(rc, stdout, stderr) = module.run_command(cmd)
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(
|
||||
msg='Could not retrieve UUID of {0}'.format(alias), exception=stderr)
|
||||
|
||||
# If no VM was found matching the given alias, we get back an empty array.
|
||||
# That is not an error condition as we might be explicitly checking it's
|
||||
# absence.
|
||||
if stdout.strip() == '[]':
|
||||
return None
|
||||
else:
|
||||
try:
|
||||
stdout_json = json.loads(stdout)
|
||||
except Exception as e:
|
||||
module.fail_json(
|
||||
msg='Invalid JSON returned by vmadm for uuid lookup of {0}'.format(alias),
|
||||
details=to_native(e), exception=traceback.format_exc())
|
||||
|
||||
if len(stdout_json) > 0 and 'uuid' in stdout_json[0]:
|
||||
return stdout_json[0]['uuid']
|
||||
|
||||
|
||||
def get_all_vm_uuids(module):
|
||||
# Retrieve the UUIDs for all VMs.
|
||||
cmd = '{0} lookup -j -o uuid'.format(module.vmadm)
|
||||
|
||||
(rc, stdout, stderr) = module.run_command(cmd)
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(msg='Failed to get VMs list', exception=stderr)
|
||||
|
||||
try:
|
||||
stdout_json = json.loads(stdout)
|
||||
return [v['uuid'] for v in stdout_json]
|
||||
except Exception as e:
|
||||
module.fail_json(msg='Could not retrieve VM UUIDs', details=to_native(e),
|
||||
exception=traceback.format_exc())
|
||||
|
||||
|
||||
def new_vm(module, uuid, vm_state):
|
||||
payload_file = create_payload(module, uuid)
|
||||
|
||||
(rc, stdout, stderr) = vmadm_create_vm(module, payload_file)
|
||||
|
||||
if rc != 0:
|
||||
changed = False
|
||||
module.fail_json(msg='Could not create VM', exception=stderr)
|
||||
else:
|
||||
changed = True
|
||||
# 'vmadm create' returns all output to stderr...
|
||||
match = re.match('Successfully created VM (.*)', stderr)
|
||||
if match:
|
||||
vm_uuid = match.groups()[0]
|
||||
if not is_valid_uuid(vm_uuid):
|
||||
module.fail_json(msg='Invalid UUID for VM {0}?'.format(vm_uuid))
|
||||
else:
|
||||
module.fail_json(msg='Could not retrieve UUID of newly created(?) VM')
|
||||
|
||||
# Now that the VM is created, ensure it is in the desired state (if not 'running')
|
||||
if vm_state != 'running':
|
||||
ret = set_vm_state(module, vm_uuid, vm_state)
|
||||
if not ret:
|
||||
module.fail_json(msg='Could not set VM {0} to state {1}'.format(vm_uuid, vm_state))
|
||||
|
||||
try:
|
||||
os.unlink(payload_file)
|
||||
except Exception as e:
|
||||
# Since the payload may contain sensitive information, fail hard
|
||||
# if we cannot remove the file so the operator knows about it.
|
||||
module.fail_json(msg='Could not remove temporary JSON payload file {0}: {1}'.format(payload_file, to_native(e)),
|
||||
exception=traceback.format_exc())
|
||||
|
||||
return changed, vm_uuid
|
||||
|
||||
|
||||
def vmadm_create_vm(module, payload_file):
|
||||
# Create a new VM using the provided payload.
|
||||
cmd = '{0} create -f {1}'.format(module.vmadm, payload_file)
|
||||
|
||||
return module.run_command(cmd)
|
||||
|
||||
|
||||
def set_vm_state(module, vm_uuid, vm_state):
|
||||
p = module.params
|
||||
|
||||
# Check if the VM is already in the desired state.
|
||||
state = get_vm_prop(module, vm_uuid, 'state')
|
||||
if state and (state == vm_state):
|
||||
return None
|
||||
|
||||
# Lookup table for the state to be in, and which command to use for that.
|
||||
# vm_state: [vmadm commandm, forceable?]
|
||||
cmds = {
|
||||
'stopped': ['stop', True],
|
||||
'running': ['start', False],
|
||||
'deleted': ['delete', True],
|
||||
'rebooted': ['reboot', False]
|
||||
}
|
||||
|
||||
if p['force'] and cmds[vm_state][1]:
|
||||
force = '-F'
|
||||
else:
|
||||
force = ''
|
||||
|
||||
cmd = 'vmadm {0} {1} {2}'.format(cmds[vm_state][0], force, vm_uuid)
|
||||
|
||||
(rc, stdout, stderr) = module.run_command(cmd)
|
||||
|
||||
match = re.match('^Successfully.*', stderr)
|
||||
if match:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def create_payload(module, uuid):
|
||||
# Create the JSON payload (vmdef) and return the filename.
|
||||
|
||||
p = module.params
|
||||
|
||||
# Filter out the few options that are not valid VM properties.
|
||||
module_options = ['debug', 'force', 'state']
|
||||
vmattrs = filter(lambda prop: prop not in module_options, p)
|
||||
|
||||
vmdef = {}
|
||||
|
||||
for attr in vmattrs:
|
||||
if p[attr]:
|
||||
vmdef[attr] = p[attr]
|
||||
|
||||
try:
|
||||
vmdef_json = json.dumps(vmdef)
|
||||
except Exception as e:
|
||||
module.fail_json(
|
||||
msg='Could not create valid JSON payload', exception=traceback.format_exc())
|
||||
|
||||
# Create the temporary file that contains our payload, and set tight
|
||||
# permissions for it may container sensitive information.
|
||||
try:
|
||||
# XXX: When there's a way to get the current ansible temporary directory
|
||||
# drop the mkstemp call and rely on ANSIBLE_KEEP_REMOTE_FILES to retain
|
||||
# the payload (thus removing the `save_payload` option).
|
||||
fname = tempfile.mkstemp()[1]
|
||||
os.chmod(fname, 0o400)
|
||||
with open(fname, 'w') as fh:
|
||||
fh.write(vmdef_json)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='Could not save JSON payload: %s' % to_native(e), exception=traceback.format_exc())
|
||||
|
||||
return fname
|
||||
|
||||
|
||||
def vm_state_transition(module, uuid, vm_state):
|
||||
ret = set_vm_state(module, uuid, vm_state)
|
||||
|
||||
# Whether the VM changed state.
|
||||
if ret is None:
|
||||
return False
|
||||
elif ret:
|
||||
return True
|
||||
else:
|
||||
module.fail_json(msg='Failed to set VM {0} to state {1}'.format(uuid, vm_state))
|
||||
|
||||
|
||||
def is_valid_uuid(uuid):
|
||||
if re.match('^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$', uuid, re.IGNORECASE):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def validate_uuids(module):
|
||||
# Perform basic UUID validation.
|
||||
failed = []
|
||||
|
||||
for u in [['uuid', module.params['uuid']],
|
||||
['image_uuid', module.params['image_uuid']]]:
|
||||
if u[1] and u[1] != '*':
|
||||
if not is_valid_uuid(u[1]):
|
||||
failed.append(u[0])
|
||||
|
||||
if len(failed) > 0:
|
||||
module.fail_json(msg='No valid UUID(s) found for: {0}'.format(", ".join(failed)))
|
||||
|
||||
|
||||
def manage_all_vms(module, vm_state):
|
||||
# Handle operations for all VMs, which can by definition only
|
||||
# be state transitions.
|
||||
state = module.params['state']
|
||||
|
||||
if state == 'created':
|
||||
module.fail_json(msg='State "created" is only valid for tasks with a single VM')
|
||||
|
||||
# If any of the VMs has a change, the task as a whole has a change.
|
||||
any_changed = False
|
||||
|
||||
# First get all VM uuids and for each check their state, and adjust it if needed.
|
||||
for uuid in get_all_vm_uuids(module):
|
||||
current_vm_state = get_vm_prop(module, uuid, 'state')
|
||||
if not current_vm_state and vm_state == 'deleted':
|
||||
any_changed = False
|
||||
else:
|
||||
if module.check_mode:
|
||||
if (not current_vm_state) or (get_vm_prop(module, uuid, 'state') != state):
|
||||
any_changed = True
|
||||
else:
|
||||
any_changed = (vm_state_transition(module, uuid, vm_state) | any_changed)
|
||||
|
||||
return any_changed
|
||||
|
||||
|
||||
def main():
|
||||
# In order to reduce the clutter and boilerplate for trivial options,
|
||||
# abstract the vmadm properties and build the dict of arguments later.
|
||||
# Dict of all options that are simple to define based on their type.
|
||||
# They're not required and have a default of None.
|
||||
properties = {
|
||||
'str': [
|
||||
'boot', 'disk_driver', 'dns_domain', 'fs_allowed', 'hostname',
|
||||
'image_uuid', 'internal_metadata_namespace', 'kernel_version',
|
||||
'limit_priv', 'nic_driver', 'qemu_opts', 'qemu_extra_opts',
|
||||
'spice_opts', 'uuid', 'vga', 'zfs_data_compression',
|
||||
'zfs_root_compression', 'zpool'
|
||||
],
|
||||
'bool': [
|
||||
'archive_on_delete', 'autoboot', 'debug', 'delegate_dataset',
|
||||
'docker', 'firewall_enabled', 'force', 'indestructible_delegated',
|
||||
'indestructible_zoneroot', 'maintain_resolvers', 'nowait'
|
||||
],
|
||||
'int': [
|
||||
'cpu_cap', 'cpu_shares', 'max_locked_memory', 'max_lwps',
|
||||
'max_physical_memory', 'max_swap', 'mdata_exec_timeout',
|
||||
'quota', 'ram', 'tmpfs', 'vcpus', 'virtio_txburst',
|
||||
'virtio_txtimer', 'vnc_port', 'zfs_data_recsize',
|
||||
'zfs_filesystem_limit', 'zfs_io_priority', 'zfs_root_recsize',
|
||||
'zfs_snapshot_limit'
|
||||
],
|
||||
'dict': ['customer_metadata', 'internal_metadata', 'routes'],
|
||||
'list': ['disks', 'nics', 'resolvers', 'filesystems']
|
||||
}
|
||||
|
||||
# Start with the options that are not as trivial as those above.
|
||||
options = dict(
|
||||
state=dict(
|
||||
default='running',
|
||||
type='str',
|
||||
choices=['present', 'running', 'absent', 'deleted', 'stopped', 'created', 'restarted', 'rebooted']
|
||||
),
|
||||
name=dict(
|
||||
default=None, type='str',
|
||||
aliases=['alias']
|
||||
),
|
||||
brand=dict(
|
||||
default='joyent',
|
||||
type='str',
|
||||
choices=['joyent', 'joyent-minimal', 'lx', 'kvm', 'bhyve']
|
||||
),
|
||||
cpu_type=dict(
|
||||
default='qemu64',
|
||||
type='str',
|
||||
choices=['host', 'qemu64']
|
||||
),
|
||||
# Regular strings, however these require additional options.
|
||||
spice_password=dict(type='str', no_log=True),
|
||||
vnc_password=dict(type='str', no_log=True),
|
||||
)
|
||||
|
||||
# Add our 'simple' options to options dict.
|
||||
for type in properties:
|
||||
for p in properties[type]:
|
||||
option = dict(default=None, type=type)
|
||||
options[p] = option
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=options,
|
||||
supports_check_mode=True,
|
||||
required_one_of=[['name', 'uuid']]
|
||||
)
|
||||
|
||||
module.vmadm = module.get_bin_path('vmadm', required=True)
|
||||
|
||||
p = module.params
|
||||
uuid = p['uuid']
|
||||
state = p['state']
|
||||
|
||||
# Translate the state parameter into something we can use later on.
|
||||
if state in ['present', 'running']:
|
||||
vm_state = 'running'
|
||||
elif state in ['stopped', 'created']:
|
||||
vm_state = 'stopped'
|
||||
elif state in ['absent', 'deleted']:
|
||||
vm_state = 'deleted'
|
||||
elif state in ['restarted', 'rebooted']:
|
||||
vm_state = 'rebooted'
|
||||
|
||||
result = {'state': state}
|
||||
|
||||
# While it's possible to refer to a given VM by it's `alias`, it's easier
|
||||
# to operate on VMs by their UUID. So if we're not given a `uuid`, look
|
||||
# it up.
|
||||
if not uuid:
|
||||
uuid = get_vm_uuid(module, p['name'])
|
||||
# Bit of a chicken and egg problem here for VMs with state == deleted.
|
||||
# If they're going to be removed in this play, we have to lookup the
|
||||
# uuid. If they're already deleted there's nothing to lookup.
|
||||
# So if state == deleted and get_vm_uuid() returned '', the VM is already
|
||||
# deleted and there's nothing else to do.
|
||||
if uuid is None and vm_state == 'deleted':
|
||||
result['name'] = p['name']
|
||||
module.exit_json(**result)
|
||||
|
||||
validate_uuids(module)
|
||||
|
||||
if p['name']:
|
||||
result['name'] = p['name']
|
||||
result['uuid'] = uuid
|
||||
|
||||
if uuid == '*':
|
||||
result['changed'] = manage_all_vms(module, vm_state)
|
||||
module.exit_json(**result)
|
||||
|
||||
# The general flow is as follows:
|
||||
# - first the current state of the VM is obtained by it's UUID.
|
||||
# - If the state was not found and the desired state is 'deleted', return.
|
||||
# - If the state was not found, it means the VM has to be created.
|
||||
# Subsequently the VM will be set to the desired state (i.e. stopped)
|
||||
# - Otherwise, it means the VM exists already and we operate on it's
|
||||
# state (i.e. reboot it.)
|
||||
#
|
||||
# In the future it should be possible to query the VM for a particular
|
||||
# property as a valid state (i.e. queried) so the result can be
|
||||
# registered.
|
||||
# Also, VMs should be able to get their properties updated.
|
||||
# Managing VM snapshots should be part of a standalone module.
|
||||
|
||||
# First obtain the VM state to determine what needs to be done with it.
|
||||
current_vm_state = get_vm_prop(module, uuid, 'state')
|
||||
|
||||
# First handle the case where the VM should be deleted and is not present.
|
||||
if not current_vm_state and vm_state == 'deleted':
|
||||
result['changed'] = False
|
||||
elif module.check_mode:
|
||||
# Shortcut for check mode, if there is no VM yet, it will need to be created.
|
||||
# Or, if the VM is not in the desired state yet, it needs to transition.
|
||||
if (not current_vm_state) or (get_vm_prop(module, uuid, 'state') != state):
|
||||
result['changed'] = True
|
||||
else:
|
||||
result['changed'] = False
|
||||
|
||||
module.exit_json(**result)
|
||||
# No VM was found that matched the given ID (alias or uuid), so we create it.
|
||||
elif not current_vm_state:
|
||||
result['changed'], result['uuid'] = new_vm(module, uuid, vm_state)
|
||||
else:
|
||||
# VM was found, operate on its state directly.
|
||||
result['changed'] = vm_state_transition(module, uuid, vm_state)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Add table
Add a link
Reference in a new issue