diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/general/plugins/modules/imgadm.py | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/general/plugins/modules/imgadm.py')
-rw-r--r-- | ansible_collections/community/general/plugins/modules/imgadm.py | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/ansible_collections/community/general/plugins/modules/imgadm.py b/ansible_collections/community/general/plugins/modules/imgadm.py new file mode 100644 index 000000000..6e4b81098 --- /dev/null +++ b/ansible_collections/community/general/plugins/modules/imgadm.py @@ -0,0 +1,319 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2016, 2017 Jasper Lievisse Adriaanse <j@jasper.la> +# 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 + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: imgadm +short_description: Manage SmartOS images +description: + - Manage SmartOS virtual machine images through imgadm(1M) +author: Jasper Lievisse Adriaanse (@jasperla) +extends_documentation_fragment: + - community.general.attributes +attributes: + check_mode: + support: none + diff_mode: + support: none +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. + type: str + source: + required: false + description: + - URI for the image source. + type: str + 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: str + + type: + required: false + choices: [ imgapi, docker, dsapi ] + default: imgapi + description: + - Type for image sources. + type: str + + uuid: + required: false + description: + - Image UUID. Can either be a full UUID or C(*) for all images. + type: str + +requirements: + - python >= 2.6 +''' + +EXAMPLES = ''' +- name: Import an image + community.general.imgadm: + uuid: '70e3ae72-96b6-11e6-9056-9737fd4d0764' + state: imported + +- name: Delete an image + community.general.imgadm: + uuid: '70e3ae72-96b6-11e6-9056-9737fd4d0764' + state: deleted + +- name: Update all images + community.general.imgadm: + uuid: '*' + state: updated + +- name: Update a single image + community.general.imgadm: + uuid: '70e3ae72-96b6-11e6-9056-9737fd4d0764' + state: updated + +- name: Add a source + community.general.imgadm: + source: 'https://datasets.project-fifo.net' + state: present + +- name: Add a Docker source + community.general.imgadm: + source: 'https://docker.io' + type: docker + state: present + +- name: Remove a source + community.general.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(type='bool'), + pool=dict(default='zones'), + source=dict(), + state=dict(required=True, choices=['present', 'absent', 'deleted', 'imported', 'updated', 'vacuumed']), + type=dict(default='imgapi', choices=['imgapi', 'docker', 'dsapi']), + uuid=dict() + ), + # 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() |