#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2021, Ansible Project
# Copyright: (c) 2021, VMware, Inc. All Rights Reserved.
# 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: vmware_guest_tpm
short_description: Add or remove vTPM device for specified VM.
description: >
   This module is used for adding or removing Virtual Trusted Platform Module(vTPM) device for an existing
   Virtual Machine. You must create a key provider on vCenter before you can add a vTPM. The ESXi hosts
   running in your environment must be ESXi 6.7 or later (Windows guest OS), or 7.0 Update 2 (Linux guest OS).
author:
- Diane Wang (@Tomorrow9) <dianew@vmware.com>
options:
  name:
    description:
    - Name of the virtual machine.
    - This is required if parameter O(uuid) or O(moid) is not supplied.
    type: str
  uuid:
    description:
    - UUID of the instance to manage if known, this is VMware's unique identifier.
    - This is required if parameter O(name) or O(moid) is not supplied.
    type: str
  moid:
    description:
    - Managed Object ID of the instance to manage if known, this is a unique identifier only within a single vCenter instance.
    - This is required if O(name) or O(uuid) is not supplied.
    type: str
  folder:
    description:
    - VM folder, absolute or relative path to find an existing VM.
    - This parameter is not required, only when multiple VMs are found with the same name.
    - The folder should include the datacenter name.
    - 'Examples:'
    - '   folder: /datacenter1/vm'
    - '   folder: datacenter1/vm'
    - '   folder: /datacenter1/vm/folder1'
    - '   folder: datacenter1/vm/folder1'
    - '   folder: /folder1/datacenter1/vm'
    - '   folder: folder1/datacenter1/vm'
    - '   folder: /folder1/datacenter1/vm/folder2'
    type: str
  datacenter:
    description:
    - The vCenter datacenter name used to get specified cluster or host.
    type: str
    required: true
  state:
    description:
    - State of vTPM device.
    - If set to 'absent', vTPM device will be removed from VM.
    - If set to 'present', vTPM device will be added if not present.
    - Virtual machine should be turned off before add or remove vTPM device.
    - Virtual machine should not contain snapshots before add vTPM device.
    type: str
    choices: ['present', 'absent']
    default: 'present'
extends_documentation_fragment:
- community.vmware.vmware.documentation
'''

EXAMPLES = r'''
- name: Add vTPM to specified VM
  community.vmware.vmware_guest_tpm:
    hostname: "{{ vcenter_hostname }}"
    username: "{{ vcenter_username }}"
    password: "{{ vcenter_password }}"
    datacenter: "{{ datacenter }}"
    name: "Test_VM"
    state: present
  delegate_to: localhost

- name: Remove vTPM from specified VM
  community.vmware.vmware_guest_tpm:
    hostname: "{{ vcenter_hostname }}"
    username: "{{ vcenter_username }}"
    password: "{{ vcenter_password }}"
    datacenter: "{{ datacenter }}"
    name: "Test_VM"
    state: absent
  delegate_to: localhost
'''

RETURN = r'''
instance:
    description: metadata about the VM vTPM device
    returned: always
    type: dict
    sample: None
'''

HAS_PYVMOMI = False
try:
    from pyVmomi import vim
    HAS_PYVMOMI = True
except ImportError:
    pass

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible_collections.community.vmware.plugins.module_utils.vmware import vmware_argument_spec, PyVmomi, wait_for_task
from ansible_collections.community.vmware.plugins.module_utils.vm_device_helper import PyVmomiDeviceHelper


class PyVmomiHelper(PyVmomi):
    def __init__(self, module):
        super(PyVmomiHelper, self).__init__(module)
        self.device_helper = PyVmomiDeviceHelper(self.module)
        self.config_spec = vim.vm.ConfigSpec()
        self.config_spec.deviceChange = []
        self.vm = None
        self.vtpm_device = None

    def get_vtpm_info(self, vm_obj=None, vtpm_device=None):
        vtpm_info = dict()
        if vm_obj:
            for device in vm_obj.config.hardware.device:
                if self.device_helper.is_tpm_device(device):
                    vtpm_device = device
        if vtpm_device:
            vtpm_info = dict(
                key=vtpm_device.key,
                label=vtpm_device.deviceInfo.label,
                summary=vtpm_device.deviceInfo.summary,
            )

        return vtpm_info

    def vtpm_operation(self, vm_obj=None):
        results = {'failed': False, 'changed': False}
        if not self.is_vcenter():
            self.module.fail_json(msg="Please connect to vCenter Server to configure vTPM device of virtual machine.")

        self.vm = vm_obj
        if self.vm.runtime.powerState != vim.VirtualMachinePowerState.poweredOff:
            self.module.fail_json(msg="Please make sure VM is powered off before configuring vTPM device,"
                                      " current state is '%s'" % self.vm.runtime.powerState)

        for device in self.vm.config.hardware.device:
            if self.device_helper.is_tpm_device(device):
                self.vtpm_device = device

        if self.module.params['state'] == 'present':
            if self.module.check_mode:
                results['desired_operation'] = "add vTPM"
            else:
                results['vtpm_operation'] = "add vTPM"
            if self.vtpm_device:
                results['vtpm_info'] = self.get_vtpm_info(vtpm_device=self.vtpm_device)
                results['msg'] = "vTPM device already exist on VM"
                self.module.exit_json(**results)
            else:
                if self.module.check_mode:
                    results['changed'] = True
                    self.module.exit_json(**results)
                vtpm_device_spec = self.device_helper.create_tpm()
        if self.module.params['state'] == 'absent':
            if self.module.check_mode:
                results['desired_operation'] = "remove vTPM"
            else:
                results['vtpm_operation'] = "remove vTPM"
            if self.vtpm_device is None:
                results['msg'] = "No vTPM device found on VM"
                self.module.exit_json(**results)
            else:
                if self.module.check_mode:
                    results['changed'] = True
                    self.module.exit_json(**results)
                vtpm_device_spec = self.device_helper.remove_tpm(self.vtpm_device)
        self.config_spec.deviceChange.append(vtpm_device_spec)

        try:
            task = self.vm.ReconfigVM_Task(spec=self.config_spec)
            wait_for_task(task)
        except Exception as e:
            self.module.fail_json(msg="Failed to configure vTPM device on virtual machine due to '%s'" % to_native(e))
        if task.info.state == 'error':
            self.module.fail_json(msg='Failed to reconfigure VM with vTPM device', detail=task.info.error.msg)
        results['changed'] = True
        results['vtpm_info'] = self.get_vtpm_info(vm_obj=self.vm)
        self.module.exit_json(**results)


def main():
    argument_spec = vmware_argument_spec()
    argument_spec.update(
        name=dict(type='str'),
        uuid=dict(type='str'),
        moid=dict(type='str'),
        folder=dict(type='str'),
        datacenter=dict(type='str', required=True),
        state=dict(type='str', default='present', choices=['present', 'absent']),
    )
    module = AnsibleModule(
        argument_spec=argument_spec,
        supports_check_mode=True,
        required_one_of=[['name', 'uuid', 'moid']],
    )
    if module.params['folder']:
        # FindByInventoryPath() does not require an absolute path
        # so we should leave the input folder path unmodified
        module.params['folder'] = module.params['folder'].rstrip('/')

    vm_config_vtpm = PyVmomiHelper(module)
    vm = vm_config_vtpm.get_vm()
    if not vm:
        vm_id = (module.params.get('name') or module.params.get('uuid') or module.params.get('moid'))
        module.fail_json(msg="Unable to configure vTPM device for non-existing virtual machine '%s'." % vm_id)
    try:
        vm_config_vtpm.vtpm_operation(vm_obj=vm)
    except Exception as e:
        module.fail_json(msg="Failed to configure vTPM device of virtual machine '%s' with exception : %s"
                             % (vm.name, to_native(e)))


if __name__ == "__main__":
    main()
