From 0eff87d0be6e7b01a8c8da229b167bb16764c01d Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Fri, 26 Mar 2021 07:47:38 +0100 Subject: [PATCH] opennebula: add one_template module (#2046) (#2111) * opennebula: add one_template module A basic module for maintaining VM templates which should be flexible enough for most needs ... * fixup! opennebula: add one_template module * fixup! fixup! opennebula: add one_template module (cherry picked from commit cdc415ea1fb3edbfeef57bbe00c55f859669758d) Co-authored-by: Georg Gadinger --- plugins/module_utils/opennebula.py | 8 +- .../modules/cloud/opennebula/one_template.py | 276 ++++++++++++++++++ plugins/modules/one_template.py | 1 + .../integration/targets/one_template/aliases | 2 + .../testhost/tmp/opennebula-fixtures.json.gz | Bin 0 -> 1069 bytes .../targets/one_template/tasks/main.yml | 243 +++++++++++++++ 6 files changed, 527 insertions(+), 3 deletions(-) create mode 100644 plugins/modules/cloud/opennebula/one_template.py create mode 120000 plugins/modules/one_template.py create mode 100644 tests/integration/targets/one_template/aliases create mode 100644 tests/integration/targets/one_template/files/testhost/tmp/opennebula-fixtures.json.gz create mode 100644 tests/integration/targets/one_template/tasks/main.yml diff --git a/plugins/module_utils/opennebula.py b/plugins/module_utils/opennebula.py index 0b95c6185b..a0a8d1305b 100644 --- a/plugins/module_utils/opennebula.py +++ b/plugins/module_utils/opennebula.py @@ -39,14 +39,16 @@ class OpenNebulaModule: wait_timeout=dict(type='int', default=300), ) - def __init__(self, argument_spec, supports_check_mode=False, mutually_exclusive=None): + def __init__(self, argument_spec, supports_check_mode=False, mutually_exclusive=None, required_one_of=None, required_if=None): - module_args = OpenNebulaModule.common_args + module_args = OpenNebulaModule.common_args.copy() module_args.update(argument_spec) self.module = AnsibleModule(argument_spec=module_args, supports_check_mode=supports_check_mode, - mutually_exclusive=mutually_exclusive) + mutually_exclusive=mutually_exclusive, + required_one_of=required_one_of, + required_if=required_if) self.result = dict(changed=False, original_message='', message='') diff --git a/plugins/modules/cloud/opennebula/one_template.py b/plugins/modules/cloud/opennebula/one_template.py new file mode 100644 index 0000000000..b4c8a2fa83 --- /dev/null +++ b/plugins/modules/cloud/opennebula/one_template.py @@ -0,0 +1,276 @@ +#!/usr/bin/python +# +# Copyright: (c) 2021, Georg Gadinger +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: one_template + +short_description: Manages OpenNebula templates + +version_added: 2.4.0 + +requirements: + - pyone + +description: + - "Manages OpenNebula templates." + +options: + id: + description: + - A I(id) of the template you would like to manage. If not set then a + - new template will be created with the given I(name). + type: int + name: + description: + - A I(name) of the template you would like to manage. If a template with + - the given name does not exist it will be created, otherwise it will be + - managed by this module. + type: str + template: + description: + - A string containing the template contents. + type: str + state: + description: + - C(present) - state that is used to manage the template. + - C(absent) - delete the template. + choices: ["present", "absent"] + default: present + type: str + +notes: + - Supports C(check_mode). Note that check mode always returns C(changed=true) for existing templates, even if the template would not actually change. + +extends_documentation_fragment: + - community.general.opennebula + +author: + - "Georg Gadinger (@nilsding)" +''' + +EXAMPLES = ''' +- name: Fetch the TEMPLATE by id + community.general.one_template: + id: 6459 + register: result + +- name: Print the TEMPLATE properties + ansible.builtin.debug: + var: result + +- name: Fetch the TEMPLATE by name + community.general.one_template: + name: tf-prd-users-workerredis-p6379a + register: result + +- name: Create a new or update an existing TEMPLATE + community.general.one_template: + name: generic-opensuse + template: | + CONTEXT = [ + HOSTNAME = "generic-opensuse" + ] + CPU = "1" + CUSTOM_ATTRIBUTE = "" + DISK = [ + CACHE = "writeback", + DEV_PREFIX = "sd", + DISCARD = "unmap", + IMAGE = "opensuse-leap-15.2", + IMAGE_UNAME = "oneadmin", + IO = "threads", + SIZE = "" ] + MEMORY = "2048" + NIC = [ + MODEL = "virtio", + NETWORK = "testnet", + NETWORK_UNAME = "oneadmin" ] + OS = [ + ARCH = "x86_64", + BOOT = "disk0" ] + SCHED_REQUIREMENTS = "CLUSTER_ID=\\"100\\"" + VCPU = "2" + +- name: Delete the TEMPLATE by id + community.general.one_template: + id: 6459 + state: absent +''' + +RETURN = ''' +id: + description: template id + type: int + returned: when I(state=present) + sample: 153 +name: + description: template name + type: str + returned: when I(state=present) + sample: app1 +template: + description: the parsed template + type: dict + returned: when I(state=present) +group_id: + description: template's group id + type: int + returned: when I(state=present) + sample: 1 +group_name: + description: template's group name + type: str + returned: when I(state=present) + sample: one-users +owner_id: + description: template's owner id + type: int + returned: when I(state=present) + sample: 143 +owner_name: + description: template's owner name + type: str + returned: when I(state=present) + sample: ansible-test +''' + + +from ansible_collections.community.general.plugins.module_utils.opennebula import OpenNebulaModule + + +class TemplateModule(OpenNebulaModule): + def __init__(self): + argument_spec = dict( + id=dict(type='int', required=False), + name=dict(type='str', required=False), + state=dict(type='str', choices=['present', 'absent'], default='present'), + template=dict(type='str', required=False), + ) + + mutually_exclusive = [ + ['id', 'name'] + ] + + required_one_of = [('id', 'name')] + + required_if = [ + ['state', 'present', ['template']] + ] + + OpenNebulaModule.__init__(self, + argument_spec, + supports_check_mode=True, + mutually_exclusive=mutually_exclusive, + required_one_of=required_one_of, + required_if=required_if) + + def run(self, one, module, result): + params = module.params + id = params.get('id') + name = params.get('name') + desired_state = params.get('state') + template_data = params.get('template') + + self.result = {} + + template = self.get_template_instance(id, name) + needs_creation = False + if not template and desired_state != 'absent': + if id: + module.fail_json(msg="There is no template with id=" + str(id)) + else: + needs_creation = True + + if desired_state == 'absent': + self.result = self.delete_template(template) + else: + if needs_creation: + self.result = self.create_template(name, template_data) + else: + self.result = self.update_template(template, template_data) + + self.exit() + + def get_template(self, predicate): + # -3 means "Resources belonging to the user" + # the other two parameters are used for pagination, -1 for both essentially means "return all" + pool = self.one.templatepool.info(-3, -1, -1) + + for template in pool.VMTEMPLATE: + if predicate(template): + return template + + return None + + def get_template_by_id(self, template_id): + return self.get_template(lambda template: (template.ID == template_id)) + + def get_template_by_name(self, template_name): + return self.get_template(lambda template: (template.NAME == template_name)) + + def get_template_instance(self, requested_id, requested_name): + if requested_id: + return self.get_template_by_id(requested_id) + else: + return self.get_template_by_name(requested_name) + + def get_template_info(self, template): + info = { + 'id': template.ID, + 'name': template.NAME, + 'template': template.TEMPLATE, + 'user_name': template.UNAME, + 'user_id': template.UID, + 'group_name': template.GNAME, + 'group_id': template.GID, + } + + return info + + def create_template(self, name, template_data): + if not self.module.check_mode: + self.one.template.allocate("NAME = \"" + name + "\"\n" + template_data) + + result = self.get_template_info(self.get_template_by_name(name)) + result['changed'] = True + + return result + + def update_template(self, template, template_data): + if not self.module.check_mode: + # 0 = replace the whole template + self.one.template.update(template.ID, template_data, 0) + + result = self.get_template_info(self.get_template_by_id(template.ID)) + if self.module.check_mode: + # Unfortunately it is not easy to detect if the template would have changed, therefore always report a change here. + result['changed'] = True + else: + # if the previous parsed template data is not equal to the updated one, this has changed + result['changed'] = template.TEMPLATE != result['template'] + + return result + + def delete_template(self, template): + if not template: + return {'changed': False} + + if not self.module.check_mode: + self.one.template.delete(template.ID) + + return {'changed': True} + + +def main(): + TemplateModule().run_module() + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/one_template.py b/plugins/modules/one_template.py new file mode 120000 index 0000000000..78637e1843 --- /dev/null +++ b/plugins/modules/one_template.py @@ -0,0 +1 @@ +./cloud/opennebula/one_template.py \ No newline at end of file diff --git a/tests/integration/targets/one_template/aliases b/tests/integration/targets/one_template/aliases new file mode 100644 index 0000000000..1ff4e0b13e --- /dev/null +++ b/tests/integration/targets/one_template/aliases @@ -0,0 +1,2 @@ +cloud/opennebula +shippable/cloud/group1 diff --git a/tests/integration/targets/one_template/files/testhost/tmp/opennebula-fixtures.json.gz b/tests/integration/targets/one_template/files/testhost/tmp/opennebula-fixtures.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..169451a22d0c3956194a56742905497637dd905b GIT binary patch literal 1069 zcmV+|1k(E-iwFq)OIu(718;C;Zf<2_b!=fRW@&hIb#i5ME^2dcZUF6??{C^T7{}k= zU$NtLX?}+=<-jRiNo4;Rg~axMWqRXaHBWve?KQqYG7=-H`DD-1__++^R;6? zcJh$}pF3r?E#vtnOUf)Rv*-1S6gqD|cZxi_Y>(GPvASI5i=qmsGRBKUR77dMP*beT zWlfhCDmK(i!f7gY-rnDrn_YI%F=q|}jz6^!h<(qS4ou-x>0F2p7vHoqVPL}1K-Ue` z6iC60$cJbO)t5P@<#DaMmIDT)>j{2d=EP7Ro8uFi9-H-Nl5dykD(jMHJKS!S6H}lj z9|R5zL+H8T!1Ug`Ac&)ZWD0Gu(Hy2Zt!7pIYK!JL@FJg7sQTIz8_i*w(`pveNL>$2 z>XTLNHN{4AnC7&a#WYe^I2u6!#t4#eNjiC}nr0}b&}t^jw(YqH+@gUw@j^rs@$0=k zvXFIuCSeZ`P3pg~$jesT zzY<$oeHcNBbV#c-cx~N$LQfG{Zy zCmx~hg-7~p1@?s1*&BMSt91<;7FJ4)EsQiZQdL=q2F|qJTubFwTvc?b8zZgNP{i4aTo`dqn%*zBe7{V)VcLNbhvFl;k*0R@J(GX=0e~pf~_I z0673T0675p4vGVW1B3&F1B3(QzXy;XvV&^=6v2OQ;;`ee