Skip to content

Commit

Permalink
Add content_template module
Browse files Browse the repository at this point in the history
Module to manage template in content library from virtual machine.

Signed-off-by: Ondra Machacek <[email protected]>
  • Loading branch information
machacekondra committed May 27, 2024
1 parent 6b28899 commit 43f06f9
Show file tree
Hide file tree
Showing 9 changed files with 434 additions and 301 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- content_template - Add new module to manage templates in content library
381 changes: 81 additions & 300 deletions plugins/module_utils/vmware_rest_client.py

Large diffs are not rendered by default.

236 changes: 236 additions & 0 deletions plugins/modules/content_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2019, Ansible Project
# Copyright: (c) 2019, Pavan Bidkar <[email protected]>
# 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 = r'''
---
module: content_template
short_description: Manage template in content library from virtual machine.
description:
- Module to manage template in content library from virtual machine.
- Content Library feature is introduced in vSphere 6.0 version.
- This module does not work with vSphere version older than 67U2.
author:
- Ansible Cloud Team (@ansible-collections)
requirements:
- vSphere Automation SDK
options:
template:
description:
- The name of template to manage.
type: str
required: true
library:
description:
- The name of the content library where the template will be created.
type: str
required: true
vm_name:
description:
- The name of the VM to be used to create template.
type: str
required: true
host:
description:
- Host onto which the virtual machine template should be placed.
- If O(host) and O(resource_pool) are both specified, O(resource_pool)
must belong to O(host).
- If O(host) and O(cluster) are both specified, O(host) must be a member of O(cluster).
- This attribute was added in vSphere API 6.8.
type: str
resource_pool:
description:
- Resource pool into which the virtual machine template should be placed.
- This attribute was added in vSphere API 6.8.
- If not specified, the system will attempt to choose a suitable resource pool
for the virtual machine template; if a resource pool cannot be
chosen, the library item creation operation will fail.
type: str
cluster:
description:
- Cluster onto which the virtual machine template should be placed.
- If O(cluster) and O(resource_pool) are both specified,
O(resource_pool) must belong to O(cluster).
- If O(cluster) and O(host) are both specified, O(host) must be a member of O(cluster).
- This attribute was added in vSphere API 6.8.
type: str
folder:
description:
- Virtual machine folder into which the virtual machine template should be placed.
- This attribute was added in vSphere API 6.8.
- If not specified, the virtual machine template will be placed in the same
folder as the source virtual machine.
type: str
state:
description:
- State of the template in content library.
- If C(present), the template will be created in content library.
- If C(absent), the template will be deleted from content library.
type: str
default: present
choices:
- present
- absent
extends_documentation_fragment:
- vmware.vmware.vmware_rest_client.documentation
'''

EXAMPLES = r'''
- name: Create template in content library from Virtual Machine
vmware.vmware.content_template:
hostname: '{{ vcenter_hostname }}'
username: '{{ vcenter_username }}'
password: '{{ vcenter_password }}'
template: mytemplate
library: mylibrary
vm_name: myvm
host: myhost
'''

RETURN = r'''
template_info:
description: Template creation message and template_id
returned: on success
type: dict
sample: {
"msg": "Template 'mytemplate'.",
"template_id": "template-1009"
}
'''

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.vmware.vmware.plugins.module_utils.vmware_rest_client import VmwareRestClient
from ansible.module_utils._text import to_native

HAS_VAUTOMATION_PYTHON_SDK = False
try:
from com.vmware.vcenter.vm_template_client import LibraryItems
from com.vmware.vapi.std.errors_client import Error
HAS_VAUTOMATION_PYTHON_SDK = True
except ImportError:
pass


class VmwareContentTemplate(VmwareRestClient):
def __init__(self, module):
"""Constructor."""
super(VmwareContentTemplate, self).__init__(module)

# Initialize member variables
self.module = module
self._template_service = self.api_client.vcenter.vm_template.LibraryItems
self.result = {'changed': False}

# Get parameters
self.template = self.params.get('template')
self.host = self.params.get('host')
self.cluster = self.params.get('cluster')
self.resource_pool = self.params.get('resource_pool')
self.library = self.params.get('library')
self.folder = self.params.get('folder')
self.vm_name = self.params.get('vm_name')

def create_template_from_vm(self):
template = self.get_library_item_from_content_library_name(self.template, self.library)
if template:
self.result['template_info'] = dict(
msg="Template '%s' already exists." % self.template,
template_id=template,
)
return

# Create template placement specs
placement_spec = LibraryItems.CreatePlacementSpec()
placement_spec.host = self.get_host_by_name(self.host)
placement_spec.resource_pool = self.get_resource_pool_by_name(self.resource_pool)
placement_spec.cluster = self.get_cluster_by_name(self.cluster)
placement_spec.folder = self.get_folder_by_name(self.folder)
create_spec = LibraryItems.CreateSpec(
name=self.template,
placement=placement_spec,
library=self.get_library_by_name(self.library),
source_vm=self.get_vm_by_name(self.vm_name),
)
template_id = ''
try:
template_id = self._template_service.create(create_spec)
except Error as error:
self.module.fail_json(msg="%s" % self.get_error_message(error))
except Exception as err:
self.module.fail_json(msg="%s" % to_native(err))

if not template_id:
self.result['template_info'] = dict(
msg="Template creation failed",
)
self.module.fail_json(**self.result)
self.result['changed'] = True
self.result['template_info'] = dict(
msg="Template '%s'." % self.template,
template_id=template_id,
)

def delete_template(self):
template = self.get_library_item_from_content_library_name(self.template, self.library)
if template is None:
self.result['template_info'] = dict(
msg="Template '%s' doesn't exists." % self.template,
)
return

try:
self.api_client.content.library.Item.delete(template)
except Exception as err:
self.module.fail_json(msg="%s" % to_native(err))

self.result['changed'] = True
self.result['template_info'] = dict(
msg="Template '%s' has been deleted." % self.template,
template_id=template,
)


def main():
argument_spec = VmwareRestClient.vmware_client_argument_spec()
argument_spec.update(
template=dict(type='str', required=True),
library=dict(type='str', required=True),
vm_name=dict(type='str', required=True),
host=dict(type='str'),
cluster=dict(type='str'),
resource_pool=dict(type='str'),
folder=dict(type='str'),
state=dict(type='str', default='present', choices=['present', 'absent']),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_one_of=[('host', 'resource_pool', 'cluster')],
)

result = {'failed': False, 'changed': False}
vmware_contentlib = VmwareContentTemplate(module)
if module.check_mode:
result.update(
vm_name=module.params['name'],
changed=True,
desired_operation='{} template'.format(module.params.get('state')),
)
module.exit_json(**result)
if module.params.get('state') == 'present':
vmware_contentlib.create_template_from_vm()
else:
vmware_contentlib.delete_template()
module.exit_json(**vmware_contentlib.result)


if __name__ == '__main__':
main()
2 changes: 1 addition & 1 deletion tests/integration/inventory
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[testgroup]
testhost ansible_connection="local" ansible_pipelining="yes" ansible_python_interpreter="/usr/bin/python3"
testhost ansible_connection="local" ansible_pipelining="yes" ansible_python_interpreter="/Users/omachace/.local/pipx/venvs/ansible/bin/python"
74 changes: 74 additions & 0 deletions tests/integration/targets/content_template/mock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
[
{
"httpRequest": {
"method": "POST",
"path": "/rest/com/vmware/cis/session"
},
"httpResponse": {
"statusCode": 200,
"body": {"value": "72300ca9ff16c5743fa0a6328c8570ce"}
}
},
{
"httpRequest": {
"method": "POST",
"path": "/api"
},
"httpResponse": {
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {"jsonrpc": "2.0", "result": {"output": 0}, "id": "0"}
}
},
{
"httpRequest": {
"method": "POST",
"path": "/api",
"body": {
"type": "JSON",
"matchType": "PARTIAL",
"json": "{\"params\":{\"serviceId\":\"com.vmware.content.library\",\"operationId\":\"find\"}}"
}
},
"httpResponseTemplate": {
"template": "{\"statusCode\": 200, \"headers\": {\"Content-type\": \"application/json\"}, \"body\": {\"jsonrpc\": \"2.0\", \"result\": {\"output\":[\"11d399c9-92f6-49cd-abaf-ad7e4fdceb49\"]}, 'id': '${json.parse($!request.body)['id']}'}}",
"templateType": "VELOCITY"
},
"priority": 1
},
{
"httpRequest": {
"method": "POST",
"path": "/api",
"body": {
"type": "JSON",
"matchType": "PARTIAL",
"json": "{\"params\":{\"serviceId\":\"com.vmware.content.library.item\",\"operationId\":\"find\"}}"
}
},
"httpResponseTemplate": {
"template": "{\"statusCode\": 200, \"headers\": {\"Content-type\": \"application/json\"}, \"body\": {\"jsonrpc\": \"2.0\", \"result\": {\"output\":[\"11d399c9-92f6-49cd-abaf-ad7e4fdceb49\"]}, 'id': '${json.parse($!request.body)['id']}'}}",
"templateType": "VELOCITY"
},
"priority": 1
},
{
"httpRequest": {
"method": "POST",
"path": "/api",
"body": {
"params" : {
"serviceId" : "com.vmware.cis.session",
"operationId" : "delete"
}
}
},
"httpResponseTemplate": {
"template": "{\"statusCode\": 200, \"headers\": {\"Content-type\": \"application/json\"}, \"body\": {\"jsonrpc\": \"2.0\", \"result\": {\"output\": 0}, \"id\": '${json.parse($!request.body)['id']}'}}",
"templateType": "VELOCITY"
},
"priority": 1
}
]
12 changes: 12 additions & 0 deletions tests/integration/targets/content_template/run.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
- hosts: localhost
gather_facts: no
vars_files:
- vars.yml
tasks:
- name: Prepare rest
ansible.builtin.import_role:
name: prepare_rest

- name: Import content_template role
ansible.builtin.import_role:
name: content_template
3 changes: 3 additions & 0 deletions tests/integration/targets/content_template/runme.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
source ../init.sh
exec ansible-playbook run.yml
19 changes: 19 additions & 0 deletions tests/integration/targets/content_template/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
- name: Create template from vm in content library
vmware.vmware.content_template:
validate_certs: false
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
port: "{{ vcenter_port }}"
template: mytemplate
library: templates
vm_name: test
host: 1.2.3.4.
register: __res

- name: Assert values
ansible.builtin.assert:
that:
- __res.changed == False
- __res.template_info.msg == "Template 'mytemplate' already exists."
6 changes: 6 additions & 0 deletions tests/integration/targets/content_template/vars.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
vcenter_hostname: "127.0.0.1"
vcenter_username: "user"
vcenter_password: "pass"
vcenter_port: 1080

mock_file: "content_template"

0 comments on commit 43f06f9

Please sign in to comment.