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

#
# Dell EMC OpenManage Ansible Modules
# Version 3.1.0
# Copyright (C) 2021 Dell Inc. or its subsidiaries. All Rights Reserved.

# 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


DOCUMENTATION = r'''
---
module: ome_profile
short_description: Create, modify, delete, assign, unassign and migrate a profile on OpenManage Enterprise
version_added: "3.1.0"
description: "This module allows to create, modify, delete, assign, unassign, and migrate a profile on OpenManage Enterprise."
extends_documentation_fragment:
  - dellemc.openmanage.ome_auth_options
options:
  command:
    description:
      - C(create) creates new profiles.
      - "C(modify) modifies an existing profile. Only I(name), I(description), I(boot_to_network_iso), and I(attributes)
      can be modified."
      - C(delete) deletes an existing profile.
      - C(assign) Deploys an existing profile on a target device and returns a task ID.
      - C(unassign) unassigns a profile from a specified target and returns a task ID.
      - C(migrate) migrates an existing profile and returns a task ID.
    choices: [create, modify, delete, assign, unassign, migrate]
    default: create
    type: str
  name_prefix:
    description:
      - The name provided when creating a profile is used a prefix followed by the number assigned to it by OpenManage Enterprise.
      - This is applicable only for a create operation.
      - This option is mutually exclusive with I(name).
    type: str
    default: Profile
  name:
    description:
      - Name of the profile.
      - This is applicable for modify, delete, assign, unassign, and migrate operations.
      - This option is mutually exclusive with I(name_prefix) and I(number_of_profiles).
    type: str
  new_name:
    description:
      - New name of the profile.
      - Applicable when I(command) is C(modify).
    type: str
  number_of_profiles:
    description:
      - Provide the number of profiles to be created.
      - This is applicable when I(name_prefix) is used with C(create).
      - This option is mutually exclusive with I(name).
      - Openmanage Enterprise can create a maximum of 100 profiles.
    type: int
    default: 1
  template_name:
    description:
      - Name of the template for creating the profile(s).
      - This is applicable when I(command) is C(create).
      - This option is mutually exclusive with I(template_id).
    type: str
  template_id:
    description:
      - ID of the template.
      - This is applicable when I(command) is C(create).
      - This option is mutually exclusive with I(template_name).
    type: int
  device_id:
    description:
      - ID of the target device.
      - This is applicable when I(command) is C(assign) and C(migrate).
      - This option is mutually exclusive with I(device_service_tag).
    type: int
  device_service_tag:
    description:
      - Identifier of the target device.
      - This is typically 7 to 8 characters in length.
      - Applicable when I(command) is C(assign), and C(migrate).
      - This option is mutually exclusive with I(device_id).
      - If the device does not exist when I(command) is C(assign) then the profile is auto-deployed.
    type: str
  description:
    description: Description of the profile.
    type: str
  boot_to_network_iso:
    description:
      - Details of the Share iso.
      - Applicable when I(command) is C(create), C(assign), and C(modify).
    type: dict
    suboptions:
      boot_to_network:
        description: Enable or disable a network share.
        type: bool
        required: true
      share_type:
        description: Type of network share.
        type: str
        choices: [NFS, CIFS]
      share_ip:
        description: IP address of the network share.
        type: str
      share_user:
        description: User name when I(share_type) is C(CIFS).
        type: str
      share_password:
        description: User password when I(share_type) is C(CIFS).
        type: str
      workgroup:
        description: User workgroup when I(share_type) is C(CIFS).
        type: str
      iso_path:
        description: Specify the full ISO path including the share name.
        type: str
      iso_timeout:
        description: Set the number of hours that the network ISO file will remain mapped to the target device(s).
        type: int
        choices: [1, 2, 4, 8, 16]
        default: 4
  filters:
    description:
      - Filters the profiles based on selected criteria.
      - This is applicable when I(command) is C(delete) or C(unassign).
      - This supports suboption I(ProfileIds) which takes a list of profile IDs.
      - This also supports OData filter expressions with the suboption I(Filters).
      - See OpenManage Enterprise REST API guide for the filtering options available.
      - I(WARNING) When this option is used in case of C(unassign), task ID is not returned for any of the profiles affected.
    type: dict
  force:
    description:
      - Provides the option to force the migration of a profile even if the source device cannot be contacted.
      - This option is applicable when I(command) is C(migrate).
    type: bool
    default: false
  attributes:
    description: Attributes for C(modify) and C(assign).
    type: dict
    suboptions:
      Attributes:
        description:
          - List of attributes to be modified, when I(command) is C(modify).
          - List of attributes to be overridden when I(command) is C(assign).
        type: list
        elements: dict
      Options:
        description:
          - Provides the different shut down options.
          - This is applicable when I(command) is C(assign).
        type: dict
      Schedule:
        description:
          - Schedule for profile deployment.
          - This is applicable when I(command) is C(assign).
        type: dict
requirements:
    - "python >= 2.7.5"
author: "Jagadeesh N V (@jagadeeshnv)"
notes:
    - Run this module from a system that has direct access to DellEMC OpenManage Enterprise.
    - This module does not support C(check_mode).
    - C(assign) operation on a already assigned profile will not redeploy.
'''

EXAMPLES = r'''
---
- name: Create two profiles from a template
  dellemc.openmanage.ome_profile:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    template_name: "template 1"
    name_prefix: "omam_profile"
    number_of_profiles: 2

- name: Create profile with NFS share
  dellemc.openmanage.ome_profile:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    command: create
    template_name: "template 1"
    name_prefix: "omam_profile"
    number_of_profiles: 1
    boot_to_network_iso:
      boot_to_network: True
      share_type: NFS
      share_ip: "192.168.0.1"
      iso_path: "path/to/my_iso.iso"
      iso_timeout: 8

- name: Create profile with CIFS share
  dellemc.openmanage.ome_profile:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    command: create
    template_name: "template 1"
    name_prefix: "omam_profile"
    number_of_profiles: 1
    boot_to_network_iso:
      boot_to_network: True
      share_type: CIFS
      share_ip: "192.168.0.2"
      share_user: "username"
      share_password: "password"
      workgroup: "workgroup"
      iso_path: "\\path\\to\\my_iso.iso"
      iso_timeout: 8

- name: Modify profile name with NFS share and attributes
  dellemc.openmanage.ome_profile:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    command: modify
    name: "Profile 00001"
    new_name: "modified profile"
    description: "new description"
    boot_to_network_iso:
      boot_to_network: True
      share_type: NFS
      share_ip: "192.168.0.3"
      iso_path: "path/to/my_iso.iso"
      iso_timeout: 8
    attributes:
      Attributes:
        - Id: 4506
          Value: "server attr 1"
          IsIgnored: true
        - Id: 4507
          Value: "server attr 2"
          IsIgnored: true

- name: Delete a profile using profile name
  dellemc.openmanage.ome_profile:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    command: "delete"
    name: "Profile 00001"

- name: Delete profiles using filters
  dellemc.openmanage.ome_profile:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    command: "delete"
    filters:
      SelectAll: True
      Filters: =contains(ProfileName,'Profile 00002')

- name: Delete profiles using profile list filter
  dellemc.openmanage.ome_profile:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    command: "delete"
    filters:
      ProfileIds:
        - 17123
        - 16124

- name: Assign a profile to target along with network share
  dellemc.openmanage.ome_profile:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    command: assign
    name: "Profile 00001"
    device_id: 12456
    boot_to_network_iso:
      boot_to_network: True
      share_type: NFS
      share_ip: "192.168.0.1"
      iso_path: "path/to/my_iso.iso"
      iso_timeout: 8
    attributes:
      Attributes:
        - Id: 4506
          Value: "server attr 1"
          IsIgnored: true
      Options:
        ShutdownType: 0
        TimeToWaitBeforeShutdown: 300
        EndHostPowerState: 1
        StrictCheckingVlan: True
      Schedule:
        RunNow: True
        RunLater: False

- name: Unassign a profile using profile name
  dellemc.openmanage.ome_profile:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    command: "unassign"
    name: "Profile 00003"

- name: Unassign profiles using filters
  dellemc.openmanage.ome_profile:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    command: "unassign"
    filters:
      SelectAll: True
      Filters: =contains(ProfileName,'Profile 00003')

- name: Unassign profiles using profile list filter
  dellemc.openmanage.ome_profile:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    command: "unassign"
    filters:
      ProfileIds:
        - 17123
        - 16123

- name: Migrate a profile
  dellemc.openmanage.ome_profile:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    command: "migrate"
    name: "Profile 00001"
    device_id: 12456
'''

RETURN = r'''
---
msg:
  description: Overall status of the profile operation.
  returned: always
  type: str
  sample: "Successfully created 2 profile(s)."
profile_ids:
  description: IDs of the profiles created.
  returned: when I(command) is C(create)
  type: list
  sample: [1234, 5678]
job_id:
  description:
    - Task ID created when I(command) is C(assign), C(migrate) or C(unassign).
    - C(assign) and C(unassign) operations do not trigger a task if a profile is auto-deployed.
  returned: when I(command) is C(assign), C(migrate) or C(unassign)
  type: int
  sample: 14123
error_info:
  description: Details of the HTTP Error.
  returned: on HTTP error
  type: dict
  sample: {
    "error": {
      "code": "Base.1.0.GeneralError",
      "message": "A general error has occurred. See ExtendedInfo for more information.",
      "@Message.ExtendedInfo": [
        {
          "MessageId": "GEN1234",
          "RelatedProperties": [],
          "Message": "Unable to process the request because an error occurred.",
          "MessageArgs": [],
          "Severity": "Critical",
          "Resolution": "Retry the operation. If the issue persists, contact your system administrator."
        }
      ]
    }
  }
'''

import json
import time
from ssl import SSLError
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.dellemc.openmanage.plugins.module_utils.ome import RestOME
from ansible.module_utils.six.moves.urllib.error import URLError, HTTPError
from ansible.module_utils.urls import ConnectionError, SSLValidationError
from ansible.module_utils.common.dict_transformations import recursive_diff

PROFILE_VIEW = "ProfileService/Profiles"
TEMPLATE_VIEW = "TemplateService/Templates"
DEVICE_VIEW = "DeviceService/Devices"
JOB_URI = "JobService/Jobs({job_id})"
PROFILE_ACTION = "ProfileService/Actions/ProfileService.{action}"
PROFILE_NOT_FOUND = "Profile with the name '{name}' not found."


def get_template_details(module, rest_obj):
    id = module.params.get('template_id')
    query_param = {"$filter": "Id eq {0}".format(id)}
    srch = 'Id'
    if not id:
        id = module.params.get('template_name')
        query_param = {"$filter": "Name eq '{0}'".format(id)}
        srch = 'Name'
    resp = rest_obj.invoke_request('GET', TEMPLATE_VIEW, query_param=query_param)
    if resp.success and resp.json_data.get('value'):
        tlist = resp.json_data.get('value', [])
        for xtype in tlist:
            if xtype.get(srch) == id:
                return xtype
    module.fail_json(msg="Template with {0} '{1}' not found.".format(srch, id))


def get_target_details(module, rest_obj):
    id = module.params.get('device_id')
    query_param = {"$filter": "Id eq {0}".format(id)}
    srch = 'Id'
    if not id:
        id = module.params.get('device_service_tag')
        query_param = {"$filter": "Identifier eq '{0}'".format(id)}
        srch = 'Identifier'
    resp = rest_obj.invoke_request('GET', DEVICE_VIEW, query_param=query_param)
    if resp.success and resp.json_data.get('value'):
        tlist = resp.json_data.get('value', [])
        for xtype in tlist:
            if xtype.get(srch) == id:
                return xtype
    return "Target with {0} '{1}' not found.".format(srch, id)


def get_profile(rest_obj, module):
    """Get profile id based on requested profile name."""
    profile_name = module.params["name"]
    profile = None
    query_param = {"$filter": "ProfileName eq '{0}'".format(profile_name)}
    profile_req = rest_obj.invoke_request("GET", PROFILE_VIEW, query_param=query_param)
    for each in profile_req.json_data.get('value'):
        if each['ProfileName'] == profile_name:
            profile = each
            break
    return profile


def get_network_iso_payload(module):
    boot_iso_dict = module.params.get("boot_to_network_iso")
    iso_payload = {}
    if boot_iso_dict:
        iso_payload = {"BootToNetwork": False}
        if boot_iso_dict.get("boot_to_network"):
            iso_payload["BootToNetwork"] = True
            share_type = boot_iso_dict.get("share_type")
            iso_payload["ShareType"] = share_type
            share_detail = {}
            sh_ip = boot_iso_dict.get("share_ip")
            share_detail["IpAddress"] = sh_ip
            share_detail["ShareName"] = sh_ip
            # share_detail["ShareName"] = boot_iso_dict.get("share_name") if boot_iso_dict.get("share_name") else sh_ip
            share_detail["User"] = boot_iso_dict.get("share_user")
            share_detail["Password"] = boot_iso_dict.get("share_password")
            share_detail["WorkGroup"] = boot_iso_dict.get("workgroup")
            iso_payload["ShareDetail"] = share_detail
            if str(boot_iso_dict.get("iso_path")).lower().endswith('.iso'):
                iso_payload["IsoPath"] = boot_iso_dict.get("iso_path")
            else:
                module.fail_json(msg="ISO path does not have extension '.iso'")
            iso_payload["IsoTimeout"] = boot_iso_dict.get("iso_timeout")
    return iso_payload


def assign_profile(module, rest_obj):
    mparam = module.params
    payload = {}
    if mparam.get('name'):
        prof = get_profile(rest_obj, module)
        if prof:
            payload['Id'] = prof['Id']
        else:
            module.fail_json(msg=PROFILE_NOT_FOUND.format(name=mparam.get('name')))
    target = get_target_details(module, rest_obj)
    if isinstance(target, dict):
        payload['TargetId'] = target['Id']
        if prof['ProfileState'] == 4:
            if prof['TargetId'] == target['Id']:
                module.exit_json(msg="The profile is assigned to the target {0}.".format(target['Id']))
            else:
                module.fail_json(msg="The profile is assigned to a different target. Use the migrate command or "
                                     "unassign the profile and then proceed with assigning the profile to the target.")
        action = "AssignProfile"
        msg = "Successfully applied the assign operation."
        try:
            resp = rest_obj.invoke_request('POST', PROFILE_ACTION.format(action='GetInvalidTargetsForAssignProfile'),
                                           data={'Id': prof['Id']})
            if target['Id'] in list(resp.json_data):
                module.fail_json(msg="The target device is invalid for the given profile.")
        except HTTPError:
            resp = None
        ad_opts_list = ['Attributes', 'Options', 'Schedule']
    else:
        if mparam.get('device_id'):
            module.fail_json(msg=target)
        action = "AssignProfileForAutoDeploy"
        msg = "Successfully applied the assign operation for auto-deployment."
        payload['Identifier'] = mparam.get('device_service_tag')
        if prof['ProfileState'] == 1:
            if prof['TargetName'] == payload['Identifier']:
                module.exit_json(msg="The profile is assigned to the target {0}.".format(payload['Identifier']))
            else:
                module.fail_json(msg="The profile is assigned to a different target. "
                                     "Unassign the profile and then proceed with assigning the profile to the target.")
        ad_opts_list = ['Attributes']
    boot_iso_dict = get_network_iso_payload(module)
    if boot_iso_dict:
        payload["NetworkBootToIso"] = boot_iso_dict
    ad_opts = mparam.get("attributes")
    for opt in ad_opts_list:
        if ad_opts and ad_opts.get(opt):
            payload[opt] = ad_opts.get(opt)
    resp = rest_obj.invoke_request('POST', PROFILE_ACTION.format(action=action), data=payload)
    res_dict = {'msg': msg, 'changed': True}
    if action == 'AssignProfile':
        try:
            res_prof = get_profile(rest_obj, module)
            time.sleep(5)
            if res_prof.get('DeploymentTaskId'):
                res_dict['job_id'] = res_prof.get('DeploymentTaskId')
                res_dict['msg'] = "Successfully triggered the job for the assign operation."
        except HTTPError:
            res_dict['msg'] = "Successfully applied the assign operation. Failed to fetch job details."
    module.exit_json(**res_dict)


def unassign_profile(module, rest_obj):
    mparam = module.params
    prof = {}
    if mparam.get('name'):
        payload = {}
        prof = get_profile(rest_obj, module)
        if prof:
            if prof['ProfileState'] == 0:
                module.exit_json(msg="Profile is in an unassigned state.")
            if prof['DeploymentTaskId']:
                try:
                    resp = rest_obj.invoke_request('GET', JOB_URI.format(job_id=prof['DeploymentTaskId']))
                    job_dict = resp.json_data
                    job_status = job_dict.get('LastRunStatus')
                    if job_status.get('Name') == 'Running':
                        module.fail_json(msg="Profile deployment task is in progress. Wait for the job to finish.")
                except HTTPError:
                    msg = "Unable to fetch job details. Applied the unassign operation"
            payload['ProfileIds'] = [prof['Id']]
        else:
            module.fail_json(msg=PROFILE_NOT_FOUND.format(name=mparam.get('name')))
    if mparam.get('filters'):
        payload = mparam.get('filters')
    msg = "Successfully applied the unassign operation. No job was triggered."
    resp = rest_obj.invoke_request('POST', PROFILE_ACTION.format(action='UnassignProfiles'), data=payload)
    res_dict = {'msg': msg, 'changed': True}
    try:
        res_prof = get_profile(rest_obj, module)
        time.sleep(3)
        if res_prof.get('DeploymentTaskId'):
            res_dict['job_id'] = res_prof.get('DeploymentTaskId')
            res_dict['msg'] = "Successfully triggered a job for the unassign operation."
    except HTTPError:
        res_dict['msg'] = "Successfully triggered a job for the unassign operation. Failed to fetch the job details."
    module.exit_json(**res_dict)


def create_profile(module, rest_obj):
    mparam = module.params
    payload = {}
    template = get_template_details(module, rest_obj)
    payload["TemplateId"] = template["Id"]
    payload["NamePrefix"] = mparam.get("name_prefix")
    payload["NumberOfProfilesToCreate"] = mparam["number_of_profiles"]
    if mparam.get("description"):
        payload["Description"] = mparam["description"]
    boot_iso_dict = get_network_iso_payload(module)
    if boot_iso_dict:
        payload["NetworkBootToIso"] = boot_iso_dict
    # module.exit_json(msg=payload)
    resp = rest_obj.invoke_request('POST', PROFILE_VIEW, data=payload)
    profile_id_list = resp.json_data
    module.exit_json(msg="Successfully created {0} profile(s).".format(len(profile_id_list)),
                     changed=True, profile_ids=profile_id_list)


def modify_profile(module, rest_obj):
    mparam = module.params
    payload = {}
    prof = get_profile(rest_obj, module)
    if not prof:
        module.fail_json(msg=PROFILE_NOT_FOUND.format(name=mparam.get('name')))
    diff = 0
    new_name = mparam.get('new_name')
    payload['Name'] = new_name if new_name else prof['ProfileName']
    if new_name and new_name != prof['ProfileName']:
        diff += 1
    desc = mparam.get('description')
    if desc and desc != prof['ProfileDescription']:
        payload['Description'] = desc
        diff += 1
    boot_iso_dict = get_network_iso_payload(module)
    rdict = prof.get('NetworkBootToIso') if prof.get('NetworkBootToIso') else {}
    if boot_iso_dict:
        nest_diff = recursive_diff(boot_iso_dict, rdict)
        if nest_diff:
            module.warn(json.dumps(nest_diff))
            if nest_diff[0]:
                diff += 1
        payload["NetworkBootToIso"] = boot_iso_dict
    ad_opts = mparam.get("attributes")
    if ad_opts and ad_opts.get("Attributes"):
        payload["Attributes"] = ad_opts.get("Attributes")
        diff += 1
    payload['Id'] = prof['Id']
    if diff:
        resp = rest_obj.invoke_request('PUT', PROFILE_VIEW + "({0})".format(payload['Id']), data=payload)
        module.exit_json(msg="Successfully modified the profile.", changed=True)
    module.exit_json(msg="No changes found to be applied.")


def delete_profile(module, rest_obj):
    mparam = module.params
    if mparam.get('name'):
        prof = get_profile(rest_obj, module)
        if prof:
            if prof['ProfileState'] > 0:
                module.fail_json(msg="Profile has to be in an unassigned state for it to be deleted.")
            resp = rest_obj.invoke_request('DELETE', PROFILE_VIEW + "({0})".format(prof['Id']))
            module.exit_json(msg="Successfully deleted the profile.", changed=True)
        else:
            module.exit_json(msg=PROFILE_NOT_FOUND.format(name=mparam.get('name')))
    if mparam.get('filters'):
        payload = mparam.get('filters')
        resp = rest_obj.invoke_request('POST', PROFILE_ACTION.format(action='Delete'), data=payload)
        module.exit_json(msg="Successfully completed the delete operation.", changed=True)


def migrate_profile(module, rest_obj):
    mparam = module.params
    payload = {}
    payload['ForceMigrate'] = mparam.get('force')
    target = get_target_details(module, rest_obj)
    if not isinstance(target, dict):
        module.fail_json(msg=target)
    payload['TargetId'] = target['Id']
    prof = get_profile(rest_obj, module)
    if prof:
        if target['Id'] == prof['TargetId']:
            module.exit_json(msg="No changes found to be applied.")
        try:
            resp = rest_obj.invoke_request('POST', PROFILE_ACTION.format(action='GetInvalidTargetsForAssignProfile'),
                                           data={'Id': prof['Id']})
            if target['Id'] in list(resp.json_data):
                module.fail_json(msg="The target device is invalid for the given profile.")
        except HTTPError:
            resp = None
        if prof['ProfileState'] == 4:  # migrate applicable in deployed state only
            payload['ProfileId'] = prof['Id']
            resp = rest_obj.invoke_request('POST', PROFILE_ACTION.format(action='MigrateProfile'), data=payload)
            msg = "Successfully applied the migrate operation."
            res_dict = {'msg': msg, 'changed': True}
            try:
                time.sleep(5)
                res_prof = get_profile(rest_obj, module)
                if res_prof.get('DeploymentTaskId'):
                    res_dict['job_id'] = res_prof.get('DeploymentTaskId')
                    res_dict['msg'] = "Successfully triggered the job for the migrate operation."
            except HTTPError:
                res_dict['msg'] = "Successfully applied the migrate operation. Failed to fetch job details."
            module.exit_json(**res_dict)
        else:
            module.fail_json(msg="Profile needs to be in a deployed state for a migrate operation.")
    else:
        module.fail_json(msg=PROFILE_NOT_FOUND.format(name=mparam.get('name')))


def profile_operation(module, rest_obj):
    command = module.params.get("command")
    if command == "create":
        create_profile(module, rest_obj)
    if command == "modify":
        modify_profile(module, rest_obj)
    if command == "delete":
        delete_profile(module, rest_obj)
    if command == "assign":
        assign_profile(module, rest_obj)
    if command == "unassign":
        unassign_profile(module, rest_obj)
    if command == "migrate":
        migrate_profile(module, rest_obj)


def main():
    network_iso_spec = {"boot_to_network": {"required": True, "type": 'bool'},
                        "share_type": {"choices": ['NFS', 'CIFS']},
                        "share_ip": {"type": 'str'},
                        "share_user": {"type": 'str'},
                        "share_password": {"type": 'str', "no_log": True},
                        "workgroup": {"type": 'str'},
                        "iso_path": {"type": 'str'},
                        "iso_timeout": {"type": 'int', "default": 4,
                                        "choices": [1, 2, 4, 8, 16]}}
    assign_spec = {"Attributes": {"type": 'list', "elements": 'dict'},
                   "Options": {"type": 'dict'},
                   "Schedule": {"type": 'dict'}}
    module = AnsibleModule(
        argument_spec={
            "hostname": {"required": True, "type": 'str'},
            "username": {"required": True, "type": 'str'},
            "password": {"required": True, "type": 'str', "no_log": True},
            "port": {"required": False, "default": 443, "type": 'int'},
            "command": {"default": "create",
                        "choices": ['create', 'modify', 'delete', 'assign', 'unassign', 'migrate']},
            "name_prefix": {"default": "Profile", "type": 'str'},
            "name": {"type": 'str'},
            "new_name": {"type": 'str'},
            "number_of_profiles": {"default": 1, "type": 'int'},
            "template_name": {"type": 'str'},
            "template_id": {"type": "int"},
            "device_id": {"type": 'int'},
            "device_service_tag": {"type": 'str'},
            "description": {"type": 'str'},
            "boot_to_network_iso": {"type": 'dict', "options": network_iso_spec,
                                    "required_if": [
                                        ['boot_to_network', True, ['share_type', 'share_ip', 'iso_path']],
                                        ['share_type', 'CIFS', ['share_user', 'share_password']]
                                    ]},
            "filters": {"type": 'dict'},
            "attributes": {"type": 'dict', "options": assign_spec},
            "force": {"default": False, "type": 'bool'}
        },
        required_if=[
            ['command', 'create', ['template_name', 'template_id'], True],
            ['command', 'modify', ['name']],
            ['command', 'modify', ['new_name', 'description', 'attributes', 'boot_to_network_iso'], True],
            ['command', 'assign', ['name']],
            ['command', 'assign', ['device_id', 'device_service_tag'], True],
            ['command', 'unassign', ['name', "filters"], True],
            ['command', 'delete', ['name', "filters"], True],
            ['command', 'migrate', ['name']],
            ['command', 'migrate', ['device_id', 'device_service_tag'], True],
        ],
        mutually_exclusive=[
            ['name', 'name_prefix'],
            ['name', 'number_of_profiles'],
            ['name', 'filters'],
            ['device_id', 'device_service_tag'],
            ['template_name', 'template_id']],
        supports_check_mode=False)
    try:
        with RestOME(module.params, req_session=True) as rest_obj:
            profile_operation(module, rest_obj)
    except HTTPError as err:
        module.fail_json(msg=str(err), error_info=json.load(err))
    except URLError as err:
        module.exit_json(msg=str(err), unreachable=True)
    except (IOError, ValueError, TypeError, SSLError, ConnectionError, SSLValidationError) as err:
        module.fail_json(msg=str(err))


if __name__ == '__main__':
    main()
