451 lines
16 KiB
Python
451 lines
16 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
# Dell EMC OpenManage Ansible Modules
|
|
# Version 3.5.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 = """
|
|
---
|
|
module: ome_groups
|
|
short_description: Manages static device groups on OpenManage Enterprise
|
|
description: This module allows to create, modify, and delete static device groups on OpenManage Enterprise.
|
|
version_added: "3.5.0"
|
|
author:
|
|
- Jagadeesh N V(@jagadeeshnv)
|
|
extends_documentation_fragment:
|
|
- dellemc.openmanage.oment_auth_options
|
|
options:
|
|
state:
|
|
type: str
|
|
description:
|
|
- C(present) allows to create or modify a device group.
|
|
- C(absent) allows to delete a device group.
|
|
choices: [present, absent]
|
|
default: present
|
|
name:
|
|
type: list
|
|
elements: str
|
|
description:
|
|
- Name of the device group to be created, modified, or deleted.
|
|
- If I(state) is absent, multiple names can be provided.
|
|
- This option is case insensitive.
|
|
- This option is mutually exclusive with I(group_id).
|
|
group_id:
|
|
type: list
|
|
elements: int
|
|
description:
|
|
- ID of the device group to be created, modified, or deleted.
|
|
- If I(state) is absent, multiple IDs can be provided.
|
|
- This option is mutually exclusive with I(name).
|
|
new_name:
|
|
type: str
|
|
description:
|
|
- New name for the existing device group.
|
|
- This is applicable only when I(state) is C(present).
|
|
description:
|
|
type: str
|
|
description:
|
|
- Description for the device group.
|
|
- This is applicable only when I(state) is C(present).
|
|
parent_group_name:
|
|
type: str
|
|
default: "Static Groups"
|
|
description:
|
|
- Name of the parent device group under which the device group to be created or modified.
|
|
- This is applicable only when I(state) is C(present).
|
|
- C(NOTE) If device group with such a name does not exist, device group with I(parent_group_name) is created.
|
|
- This option is case insensitive.
|
|
- This option is mutually exclusive with I(parent_group_id).
|
|
parent_group_id:
|
|
type: int
|
|
description:
|
|
- ID of the parent device group under which the device group to be created or modified.
|
|
- This is applicable only when I(state) is C(present).
|
|
- This option is mutually exclusive with I(parent_group_name).
|
|
requirements:
|
|
- "python >= 2.7.5"
|
|
notes:
|
|
- This module manages only static device groups on Dell EMC OpenManage Enterprise.
|
|
- If a device group with the name I(parent_group_name) does not exist, a new device group with the same name is created.
|
|
- Make sure the entered parent group is not the descendant of the provided group.
|
|
- Run this module from a system that has direct access to Dell EMC OpenManage Enterprise.
|
|
- This module supports C(check_mode).
|
|
"""
|
|
|
|
EXAMPLES = """
|
|
---
|
|
- name: Create a new device group
|
|
dellemc.openmanage.ome_groups:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
name: "group 1"
|
|
description: "Group 1 description"
|
|
parent_group_name: "group parent 1"
|
|
|
|
- name: Modify a device group using the group ID
|
|
dellemc.openmanage.ome_groups:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
group_id: 1234
|
|
description: "Group description updated"
|
|
parent_group_name: "group parent 2"
|
|
|
|
- name: Delete a device group using the device group name
|
|
dellemc.openmanage.ome_groups:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
state: absent
|
|
name: "group 1"
|
|
|
|
- name: Delete multiple device groups using the group IDs
|
|
dellemc.openmanage.ome_groups:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
state: absent
|
|
group_id:
|
|
- 1234
|
|
- 5678
|
|
"""
|
|
|
|
RETURN = """
|
|
---
|
|
msg:
|
|
type: str
|
|
description: Overall status of the device group operation.
|
|
returned: always
|
|
sample: "Successfully deleted the device group(s)."
|
|
group_status:
|
|
description: Details of the device group operation status.
|
|
returned: success
|
|
type: dict
|
|
sample: {
|
|
"Description": "my group description",
|
|
"Id": 12123,
|
|
"MembershipTypeId": 12,
|
|
"Name": "group 1",
|
|
"ParentId": 12345,
|
|
"TypeId": 3000,
|
|
"IdOwner": 30,
|
|
"CreatedBy": "admin",
|
|
"CreationTime": "2021-01-01 10:10:10.100",
|
|
"DefinitionDescription": "UserDefined",
|
|
"DefinitionId": 400,
|
|
"GlobalStatus": 5000,
|
|
"HasAttributes": false,
|
|
"UpdatedBy": "",
|
|
"UpdatedTime": "2021-01-01 11:11:10.100",
|
|
"Visible": true
|
|
}
|
|
group_ids:
|
|
type: list
|
|
elements: int
|
|
description: List of the deleted device group IDs.
|
|
returned: when I(state) is C(absent)
|
|
sample: [1234, 5678]
|
|
invalid_groups:
|
|
type: list
|
|
elements: str
|
|
description: List of the invalid device group IDs or names.
|
|
returned: when I(state) is C(absent)
|
|
sample: [1234, 5678]
|
|
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": "CGRP9013",
|
|
"RelatedProperties": [],
|
|
"Message": "Unable to update group 12345 with the provided parent 54321 because a group/parent
|
|
relationship already exists.",
|
|
"MessageArgs": [
|
|
"12345",
|
|
"54321"
|
|
],
|
|
"Severity": "Warning",
|
|
"Resolution": "Make sure the entered parent ID does not create a bidirectional relationship and retry
|
|
the operation."
|
|
}
|
|
]
|
|
}
|
|
}
|
|
"""
|
|
|
|
import json
|
|
import time
|
|
from ssl import SSLError
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.six.moves.urllib.error import URLError, HTTPError
|
|
from ansible.module_utils.urls import ConnectionError
|
|
from ansible_collections.dellemc.openmanage.plugins.module_utils.ome import RestOME
|
|
|
|
GROUP_URI = "GroupService/Groups"
|
|
OP_URI = "GroupService/Actions/GroupService.{op}Group"
|
|
# GROUPS_HIERARCHY = "GroupService/AllGroupsHierarchy"
|
|
MULTIPLE_GROUPS_MSG = "Provide only one unique device group when state is present."
|
|
NONEXIST_GROUP_ID = "A device group with the provided ID does not exist."
|
|
NONEXIST_PARENT_ID = "A parent device group with the provided ID does not exist."
|
|
INVALID_PARENT = "The provided parent device group is not a valid user-defined static device group."
|
|
INVALID_GROUPS_DELETE = "Provide valid static device group(s) for deletion."
|
|
INVALID_GROUPS_MODIFY = "Provide valid static device group for modification."
|
|
PARENT_CREATION_FAILED = "Unable to create a parent device group with the name {pname}."
|
|
CREATE_SUCCESS = "Successfully {op}d the device group."
|
|
GROUP_PARENT_SAME = "Provided parent and the device group cannot be the same."
|
|
GROUP_NAME_EXISTS = "Unable to rename the group because a group with the provided name '{gname}' already exists."
|
|
DELETE_SUCCESS = "Successfully deleted the device group(s)."
|
|
NO_CHANGES_MSG = "No changes found to be applied."
|
|
CHANGES_FOUND = "Changes found to be applied."
|
|
STATIC_ROOT = 'Static Groups'
|
|
SETTLING_TIME = 2
|
|
|
|
|
|
def get_valid_groups(module, rest_obj, group_arg, group_set):
|
|
parent = {}
|
|
static_root = {}
|
|
group_dict = {}
|
|
group_resp = rest_obj.get_all_items_with_pagination(GROUP_URI)
|
|
if module.params.get('state') == 'absent':
|
|
group_dict = dict([(str(g[group_arg]).lower(), g) for g in group_resp.get("value")
|
|
if str(g[group_arg]).lower() in group_set])
|
|
else:
|
|
parg = module.params.get('parent_group_id')
|
|
if parg: # Checking id first as name has a default value
|
|
pkey = 'Id'
|
|
else:
|
|
pkey = 'Name'
|
|
parg = module.params.get('parent_group_name')
|
|
count = 0
|
|
for g in group_resp.get("value"):
|
|
if str(g[group_arg]).lower() in group_set:
|
|
group_dict = g
|
|
count = count + 1
|
|
if str(g[pkey]).lower() == str(parg).lower():
|
|
parent = g
|
|
count = count + 1
|
|
if g['Name'] == STATIC_ROOT:
|
|
static_root = g
|
|
count = count + 1
|
|
if count == 3:
|
|
break
|
|
return group_dict, parent, static_root
|
|
|
|
|
|
def is_valid_static_group(grp):
|
|
if grp['TypeId'] == 3000 and grp['MembershipTypeId'] == 12:
|
|
return True
|
|
return False
|
|
|
|
|
|
def create_parent(rest_obj, module, static_root):
|
|
try:
|
|
prt = static_root
|
|
payload = {}
|
|
payload['MembershipTypeId'] = 12 # Static members
|
|
payload['Name'] = module.params.get('parent_group_name')
|
|
payload['ParentId'] = prt['Id']
|
|
prt_resp = rest_obj.invoke_request('POST', OP_URI.format(op='Create'), data={"GroupModel": payload})
|
|
return int(prt_resp.json_data)
|
|
except Exception:
|
|
return static_root['Id']
|
|
|
|
|
|
def get_parent_id(rest_obj, module, parent, static_root):
|
|
parent_id = module.params.get("parent_group_id")
|
|
if parent_id: # Checking id first as name has a default value
|
|
if not parent:
|
|
module.fail_json(msg=NONEXIST_PARENT_ID)
|
|
if parent['Name'] != STATIC_ROOT:
|
|
if not is_valid_static_group(parent):
|
|
module.fail_json(msg=INVALID_PARENT)
|
|
return parent['Id']
|
|
else:
|
|
if parent:
|
|
if parent['Name'] != STATIC_ROOT:
|
|
if not is_valid_static_group(parent):
|
|
module.fail_json(msg=INVALID_PARENT)
|
|
return parent['Id']
|
|
else:
|
|
if module.check_mode:
|
|
return 0
|
|
else:
|
|
prtid = create_parent(rest_obj, module, static_root)
|
|
time.sleep(SETTLING_TIME)
|
|
return prtid
|
|
return static_root['Id']
|
|
|
|
|
|
def get_ome_group_by_name(rest_obj, name):
|
|
grp = {}
|
|
try:
|
|
resp = rest_obj.invoke_request("GET", GROUP_URI, query_param={"$filter": "Name eq '{0}'".format(name)})
|
|
group_resp = resp.json_data.get('value')
|
|
if group_resp:
|
|
grp = group_resp[0]
|
|
except Exception:
|
|
grp = {}
|
|
return grp
|
|
|
|
|
|
def get_ome_group_by_id(rest_obj, id):
|
|
grp = {}
|
|
try:
|
|
resp = rest_obj.invoke_request('GET', GROUP_URI + "({0})".format(id))
|
|
grp = resp.json_data
|
|
except Exception:
|
|
grp = {}
|
|
return grp
|
|
|
|
|
|
def exit_group_operation(module, rest_obj, payload, operation):
|
|
group_resp = rest_obj.invoke_request('POST', OP_URI.format(op=operation), data={"GroupModel": payload})
|
|
cid = int(group_resp.json_data)
|
|
time.sleep(SETTLING_TIME)
|
|
try:
|
|
grp = get_ome_group_by_id(rest_obj, cid)
|
|
group = rest_obj.strip_substr_dict(grp)
|
|
except Exception:
|
|
payload['Id'] = cid
|
|
group = payload
|
|
module.exit_json(changed=True, msg=CREATE_SUCCESS.format(op=operation.lower()), group_status=group)
|
|
|
|
|
|
def create_group(rest_obj, module, parent, static_root):
|
|
payload = {}
|
|
payload['MembershipTypeId'] = 12 # Static members
|
|
mparams = module.params
|
|
payload['Name'] = mparams.get('name')[0]
|
|
if mparams.get('parent_group_name').lower() == payload['Name'].lower():
|
|
module.fail_json(msg=GROUP_PARENT_SAME)
|
|
parent_id = get_parent_id(rest_obj, module, parent, static_root)
|
|
payload['ParentId'] = parent_id
|
|
if mparams.get('description'):
|
|
payload['Description'] = mparams.get('description')
|
|
if module.check_mode:
|
|
module.exit_json(changed=True, msg=CHANGES_FOUND)
|
|
exit_group_operation(module, rest_obj, payload, 'Create')
|
|
|
|
|
|
def modify_group(rest_obj, module, valid_group_dict, parent, static_root):
|
|
if not is_valid_static_group(valid_group_dict):
|
|
module.fail_json(msg=INVALID_GROUPS_MODIFY)
|
|
grp = valid_group_dict
|
|
diff = 0
|
|
payload = dict([(k, grp.get(k)) for k in ["Name", "Description", "MembershipTypeId", "ParentId", "Id"]])
|
|
new_name = module.params.get('new_name')
|
|
if new_name:
|
|
if new_name != payload['Name']:
|
|
dup_grp = get_ome_group_by_name(rest_obj, new_name)
|
|
if dup_grp:
|
|
module.fail_json(msg=GROUP_NAME_EXISTS.format(gname=new_name))
|
|
payload['Name'] = new_name
|
|
diff += 1
|
|
desc = module.params.get('description')
|
|
if desc:
|
|
if desc != payload['Description']:
|
|
payload['Description'] = desc
|
|
diff += 1
|
|
parent_id = get_parent_id(rest_obj, module, parent, static_root)
|
|
if parent_id == payload['Id']:
|
|
module.fail_json(msg=GROUP_PARENT_SAME)
|
|
if parent_id != payload['ParentId']:
|
|
payload['ParentId'] = parent_id
|
|
diff += 1
|
|
if diff == 0:
|
|
gs = rest_obj.strip_substr_dict(grp)
|
|
module.exit_json(msg=NO_CHANGES_MSG, group_status=gs)
|
|
if module.check_mode:
|
|
module.exit_json(changed=True, msg=CHANGES_FOUND)
|
|
exit_group_operation(module, rest_obj, payload, 'Update')
|
|
|
|
|
|
def delete_groups(rest_obj, module, group_set, group_dict):
|
|
deletables = []
|
|
invalids = []
|
|
for g in group_set:
|
|
grp = group_dict.get(str(g).lower())
|
|
if grp:
|
|
if is_valid_static_group(grp): # For Query Groups MembershipTypeId = 24
|
|
deletables.append(grp['Id'])
|
|
else:
|
|
invalids.append(g)
|
|
if invalids:
|
|
module.fail_json(msg=INVALID_GROUPS_DELETE, invalid_groups=invalids)
|
|
if module.check_mode:
|
|
module.exit_json(changed=True, msg=CHANGES_FOUND, group_ids=deletables)
|
|
rest_obj.invoke_request("POST", OP_URI.format(op='Delete'), data={"GroupIds": deletables})
|
|
module.exit_json(changed=True, msg=DELETE_SUCCESS, group_ids=deletables)
|
|
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec={
|
|
"hostname": {"required": True, "type": "str"},
|
|
"username": {"required": True, "type": "str"},
|
|
"password": {"required": True, "type": "str", "no_log": True},
|
|
"port": {"type": "int", "default": 443},
|
|
"name": {"type": "list", "elements": 'str'},
|
|
"group_id": {"type": "list", "elements": 'int'},
|
|
"state": {"type": "str", "choices": ["present", "absent"], "default": "present"},
|
|
"description": {"type": "str"},
|
|
"new_name": {"type": "str"},
|
|
"parent_group_name": {"type": "str", "default": STATIC_ROOT},
|
|
"parent_group_id": {"type": "int"},
|
|
},
|
|
required_if=[
|
|
("state", "present", ("new_name", "description", "parent_group_name", "parent_group_id"), True),
|
|
],
|
|
mutually_exclusive=[
|
|
("name", "group_id"), ("parent_group_name", "parent_group_id"),
|
|
],
|
|
required_one_of=[("name", "group_id")],
|
|
supports_check_mode=True
|
|
)
|
|
|
|
try:
|
|
if module.params.get('name'):
|
|
group_arg = 'Name'
|
|
group_set = set(v.lower() for v in module.params.get('name'))
|
|
else:
|
|
group_arg = 'Id'
|
|
group_set = set(str(v).lower() for v in module.params.get('group_id'))
|
|
if len(group_set) != 1 and module.params['state'] == 'present':
|
|
module.fail_json(msg=MULTIPLE_GROUPS_MSG)
|
|
with RestOME(module.params, req_session=True) as rest_obj:
|
|
valid_group_dict, parent, static_root = get_valid_groups(module, rest_obj, group_arg, group_set)
|
|
if module.params["state"] == "absent":
|
|
if valid_group_dict:
|
|
delete_groups(rest_obj, module, group_set, valid_group_dict)
|
|
module.exit_json(msg=NO_CHANGES_MSG)
|
|
else:
|
|
if valid_group_dict:
|
|
modify_group(rest_obj, module, valid_group_dict, parent, static_root)
|
|
elif group_arg == 'Id':
|
|
module.fail_json(msg=NONEXIST_GROUP_ID)
|
|
create_group(rest_obj, module, parent, static_root)
|
|
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, SSLError, TypeError, ConnectionError, AttributeError, IndexError, KeyError) as err:
|
|
module.fail_json(msg=str(err))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|