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

# 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: ucs_managed_objects
short_description: Configures Managed Objects on Cisco UCS Manager
description:
- Configures Managed Objects on Cisco UCS Manager.
- The Python SDK module, Python class within the module (UCSM Class), and all properties must be directly specified.
- More information on the UCSM Python SDK and how to directly configure Managed Objects is available at L(UCSM Python SDK,http://ucsmsdk.readthedocs.io/).
extends_documentation_fragment: cisco.ucs.ucs
options:
  state:
    description:
    - If C(present), will verify that the Managed Objects are present and will create if needed.
    - If C(absent), will verify that the Managed Objects are absent and will delete if needed.
    choices: [ absent, present ]
    default: present
    type: str
  objects:
    description:
    - List of managed objects to configure.  Each managed object has suboptions the specify the Python SDK module, class, and properties to configure.
    type: list
    elements: dict
    suboptions:
      module:
        description:
        - Name of the Python SDK module implementing the required class.
        required: true
        type: str
      class_name:
        description:
        - Name of the Python class that will be used to configure the Managed Object.
        required: true
        type: str
        aliases: [class]
      properties:
        description:
        - List of properties to configure on the Managed Object.  See the UCSM Python SDK for information on properties for each class.
        required: true
        type: dict
      children:
        description:
        - Optional list of child objects.  Each child has its own module, class, and properties suboptions.
        - The parent_mo_or_dn property for child objects is automatically set as the list of children is configured.
        type: list
        elements: dict
    required: true
requirements:
- ucsmsdk
author:
- David Soper (@dsoper2)
- CiscoUcs (@CiscoUcs)
'''

EXAMPLES = r'''
- name: Configure Network Control Policy
  cisco.ucs.ucs_managed_objects:
    hostname: 172.16.143.150
    username: admin
    password: password
    objects:
    - module: ucsmsdk.mometa.nwctrl.NwctrlDefinition
      class: NwctrlDefinition
      properties:
        parent_mo_or_dn: org-root
        cdp: enabled
        descr: ''
        lldp_receive: enabled
        lldp_transmit: enabled
        name: Enable-CDP-LLDP

- name: Remove Network Control Policy
  cisco.ucs.ucs_managed_objects:
    hostname: 172.16.143.150
    username: admin
    password: password
    objects:
    - module: ucsmsdk.mometa.nwctrl.NwctrlDefinition
      class: NwctrlDefinition
      properties:
        parent_mo_or_dn: org-root
        name: Enable-CDP-LLDP
    state: absent

- name: Configure Boot Policy Using JSON objects list with children
  cisco.ucs.ucs_managed_objects:
    hostname: 172.16.143.150
    username: admin
    password: password
    objects:
    - {
          "module": "ucsmsdk.mometa.lsboot.LsbootPolicy",
          "class": "LsbootPolicy",
          "properties": {
              "parent_mo_or_dn": "org-root",
              "name": "Python_SDS",
              "enforce_vnic_name": "yes",
              "boot_mode": "legacy",
              "reboot_on_update": "no"
          },
          "children": [
              {
                  "module": "ucsmsdk.mometa.lsboot.LsbootVirtualMedia",
                  "class": "LsbootVirtualMedia",
                  "properties": {
                      "access": "read-only-local",
                      "lun_id": "0",
                      "order": "2"
                  }
              },
              {
                  "module": "ucsmsdk.mometa.lsboot.LsbootStorage",
                  "class": "LsbootStorage",
                  "properties": {
                      "order": "1"
                  },
                  "children": [
                      {
                          "module": "ucsmsdk.mometa.lsboot.LsbootLocalStorage",
                          "class": "LsbootLocalStorage",
                          "properties": {},
                          "children": [
                              {
                                  "module": "ucsmsdk.mometa.lsboot.LsbootDefaultLocalImage",
                                  "class": "LsbootDefaultLocalImage",
                                  "properties": {
                                      "order": "1"
                                  }
                              }
                          ]
                      }
                  ]
              }
          ]
      }

- name: Remove Boot Policy Using JSON objects list
  cisco.ucs.ucs_managed_objects:
    hostname: 172.16.143.150
    username: admin
    password: password
    objects:
    - {
          "module": "ucsmsdk.mometa.lsboot.LsbootPolicy",
          "class": "LsbootPolicy",
          "properties": {
              "parent_mo_or_dn": "org-root",
              "name": "Python_SDS"
          }
      }
    state: absent


'''

RETURN = r'''
#
'''

try:
    from importlib import import_module
    HAS_IMPORT_MODULE = True
except ImportError:
    HAS_IMPORT_MODULE = False

from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.ucs.plugins.module_utils.ucs import UCSModule, ucs_argument_spec


def traverse_objects(module, ucs, managed_object, mo=''):
    props_match = False

    mo_module = import_module(managed_object['module'])
    mo_class = getattr(mo_module, managed_object['class'])

    if not managed_object['properties'].get('parent_mo_or_dn'):
        managed_object['properties']['parent_mo_or_dn'] = mo

    mo = mo_class(**managed_object['properties'])

    existing_mo = ucs.login_handle.query_dn(mo.dn)

    if module.params['state'] == 'absent':
        # mo must exist, but all properties do not have to match
        if existing_mo:
            if not module.check_mode:
                ucs.login_handle.remove_mo(existing_mo)
            ucs.result['changed'] = True
    else:
        if existing_mo:
            # check mo props
            kwargs = dict(managed_object['properties'])
            # remove parent info and passwords because those aren't presented in the actual props
            kwargs.pop('parent_mo_or_dn', None)
            kwargs.pop('pwd', None)
            kwargs.pop('password', None)
            if existing_mo.check_prop_match(**kwargs):
                props_match = True

        if not props_match:
            if not module.check_mode:
                ucs.login_handle.add_mo(mo, modify_present=True)
            ucs.result['changed'] = True

    if managed_object.get('children'):
        for child in managed_object['children']:
            # explicit deep copy of child object since traverse_objects may modify parent mo information
            copy_of_child = deepcopy(child)
            traverse_objects(module, ucs, copy_of_child, mo)


def main():
    object_dict = dict(
        module=dict(type='str', required=True),
        class_name=dict(type='str', aliases=['class'], required=True),
        properties=dict(type='dict', required=True),
        children=dict(type='list', elements='dict'),
    )
    argument_spec = ucs_argument_spec.copy()
    argument_spec.update(
        objects=dict(type='list', elements='dict', options=object_dict, required=True),
        state=dict(type='str', choices=['present', 'absent'], default='present'),
    )

    module = AnsibleModule(
        argument_spec,
        supports_check_mode=True,
    )

    if not HAS_IMPORT_MODULE:
        module.fail_json(msg='import_module is required for this module')
    ucs = UCSModule(module)

    # note that all objects specified in the object list report a single result (including a single changed).
    ucs.result['changed'] = False

    for managed_object in module.params['objects']:
        traverse_objects(module, ucs, managed_object)
        # single commit for object and any children
        if not module.check_mode and ucs.result['changed']:
            try:
                ucs.login_handle.commit()
            except Exception as e:
                # generic Exception because UCSM can throw a variety of exceptions
                ucs.result['msg'] = "setup error: %s " % str(e)
                module.fail_json(**ucs.result)

    module.exit_json(**ucs.result)


if __name__ == '__main__':
    main()
