1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3
4#
5# Dell EMC OpenManage Ansible Modules
6# Version 3.2.0
7# Copyright (C) 2019-2021 Dell Inc. or its subsidiaries. All Rights Reserved.
8
9# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
10#
11
12
13from __future__ import (absolute_import, division, print_function)
14__metaclass__ = type
15
16DOCUMENTATION = r'''
17---
18module: ome_template
19short_description: Create, modify, deploy, delete, export, import and clone a template on OpenManage Enterprise
20version_added: "2.0.0"
21description: "This module creates, modifies, deploys, deletes, exports, imports and clones a template on
22OpenManage Enterprise."
23extends_documentation_fragment:
24  - dellemc.openmanage.ome_auth_options
25options:
26  command:
27    description:
28      - C(create) creates a new template.
29      - C(modify) modifies an existing template.
30      - C(deploy) creates a template-deployment job.
31      - C(delete) deletes an existing template.
32      - C(export) exports an existing template.
33      - C(import) creates a template from a specified configuration text in SCP XML format.
34      - C(clone) creates a clone of a existing template.
35    choices: [create, modify, deploy, delete, export, import, clone]
36    default: create
37    aliases: ['state']
38    type: str
39  template_id:
40    description:
41      - ID of the existing template.
42      - This option is applicable when I(command) is C(modify), C(deploy), C(delete) and C(export).
43      - This option is mutually exclusive with I(template_name).
44    type: int
45  template_name:
46    description:
47      - Name of the existing template.
48      - This option is applicable when I(command) is C(modify), C(deploy), C(delete) and C(export).
49      - This option is mutually exclusive with I(template_id).
50    type: str
51  device_id:
52    description:
53      - >-
54        Specify the list of targeted device ID(s) when I(command) is C(deploy). When I (command) is C(create),
55        specify the ID of a single device.
56      - Either I(device_id) or I(device_service_tag) is mandatory or both can be applicable.
57    type: list
58    elements: int
59    default: []
60  device_service_tag:
61    description:
62      - >-
63        Specify the list of targeted device service tags when I (command) is C(deploy). When I(command) is C(create),
64        specify the service tag of a single device.
65      - Either I(device_id) or I(device_service_tag) is mandatory or both can be applicable.
66    type: list
67    elements: str
68    default: []
69  device_group_names:
70    description:
71      - Specify the list of groups when I (command) is C(deploy).
72      - Provide at least one of the mandatory options I(device_id), I(device_service_tag), or I(device_group_names).
73    type: list
74    elements: str
75    default: []
76  template_view_type:
77    description:
78      - Select the type of view of the OME template.
79      - This is applicable when I(command) is C(create),C(clone) and C(import).
80    choices: [Deployment, Compliance, Inventory, Sample, None]
81    type: str
82    default: Deployment
83  attributes:
84    type: dict
85    description:
86      - >-
87        Payload data for the template operations. All the variables in this option are added as payload for C(create),
88        C(modify), C(deploy), C(import), and C(clone) operations. It takes the following attributes.
89      - >-
90        Attributes: List of dictionaries of attributes (if any) to be modified in the deployment template. This is
91        applicable when I(command) is C(deploy) and C(modify).
92      - >-
93        Name: Name of the template. This is mandatory when I(command) is C(create), C(import), C(clone), and
94        optional when I(command) is C(modify).
95      - >-
96        Description: Description for the template. This is applicable when I(command) is C(create) or C(modify).
97      - >-
98        Fqdds: This allows to create a template using components from a specified reference server. One or more, of the
99        following values must be specified in a comma-separated string: iDRAC, System, BIOS, NIC, LifeCycleController,
100        RAID, and EventFilters. If none of the values are specified, the default value 'All' is selected.
101        This is applicable when I (command) is C(create).
102      - >-
103        Options: Options to control device shutdown or end power state post template deployment. This is applicable
104        for C(deploy) operation.
105      - >-
106        Schedule: Provides options to schedule the deployment task immediately, or at a specified time. This is
107        applicable when I(command) is C(deploy).
108      - >-
109        NetworkBootIsoModel: Payload to specify the ISO deployment details. This is applicable when I(command) is
110        C(deploy).
111      - >-
112        Content: The XML content of template. This is applicable when I(command) is C(import).
113      - >-
114        Type: Template type ID, indicating the type of device for which configuration is supported, such as chassis
115        and servers. This is applicable when I(command) is C(import).
116      - >-
117        TypeId: Template type ID, indicating the type of device for which configuration is supported, such as chassis
118        and servers. This is applicable when I(command) is C(create).
119      - >-
120        Refer OpenManage Enterprise API Reference Guide for more details.
121requirements:
122    - "python >= 2.7.5"
123author: "Jagadeesh N V (@jagadeeshnv)"
124notes:
125    - Run this module from a system that has direct access to DellEMC OpenManage Enterprise.
126    - This module does not support C(check_mode).
127'''
128
129EXAMPLES = r'''
130---
131- name: Create a template from a reference device
132  dellemc.openmanage.ome_template:
133    hostname: "192.168.0.1"
134    username: "username"
135    password: "password"
136    device_id: 25123
137    attributes:
138      Name: "New Template"
139      Description: "New Template description"
140
141- name: Modify template name, description, and attribute value
142  dellemc.openmanage.ome_template:
143    hostname: "192.168.0.1"
144    username: "username"
145    password: "password"
146    command: "modify"
147    template_id: 12
148    attributes:
149      Name: "New Custom Template"
150      Description: "Custom Template Description"
151      # Attributes to be modified in the template.
152      # For information on any attribute id, use API /TemplateService/Templates(Id)/Views(Id)/AttributeViewDetails
153      # This section is optional
154      Attributes:
155        - Id: 1234
156          Value: "Test Attribute"
157          IsIgnored: false
158
159- name: Deploy template on multiple devices
160  dellemc.openmanage.ome_template:
161    hostname:  "192.168.0.1"
162    username: "username"
163    password: "password"
164    command: "deploy"
165    template_id: 12
166    device_id:
167      - 12765
168      - 10173
169    device_service_tag:
170      - 'SVTG123'
171      - 'SVTG456'
172
173- name: Deploy template on groups
174  dellemc.openmanage.ome_template:
175    hostname:  "192.168.0.1"
176    username: "username"
177    password: "password"
178    command: "deploy"
179    template_id: 12
180    device_group_names:
181      - server_group_1
182      - server_group_2
183
184- name: Deploy template on multiple devices along with the attributes values to be modified on the target devices
185  dellemc.openmanage.ome_template:
186    hostname:  "192.168.0.1"
187    username: "username"
188    password: "password"
189    command: "deploy"
190    template_id: 12
191    device_id:
192      - 12765
193      - 10173
194    device_service_tag:
195      - 'SVTG123'
196    attributes:
197      # Device specific attributes to be modified during deployment.
198      # For information on any attribute id, use API /TemplateService/Templates(Id)/Views(Id)/AttributeViewDetails
199      # This section is optional
200      Attributes:
201        # specific device where attribute to be modified at deployment run-time.
202        # The DeviceId should be mentioned above in the 'device_id' section.
203        # Service tags not allowed.
204        - DeviceId: 12765
205          Attributes:
206            - Id : 15645
207              Value : "0.0.0.0"
208              IsIgnored : false
209        - DeviceId: 10173
210          Attributes:
211            - Id : 18968,
212              Value : "hostname-1"
213              IsIgnored : false
214
215- name: Deploy template and Operating System (OS) on multiple devices
216  dellemc.openmanage.ome_template:
217    hostname:  "192.168.0.1"
218    username: "username"
219    password: "password"
220    command: "deploy"
221    template_id: 12
222    device_id:
223      - 12765
224    device_service_tag:
225      - 'SVTG123'
226    attributes:
227      # Include this to install OS on the devices.
228      # This section is optional
229      NetworkBootIsoModel:
230        BootToNetwork: true
231        ShareType: "NFS"
232        IsoTimeout: 1 # allowable values(1,2,4,8,16) in hours
233        IsoPath: "/home/iso_path/filename.iso"
234        ShareDetail:
235          IpAddress: "192.168.0.2"
236          ShareName: "sharename"
237          User: "share_user"
238          Password: "share_password"
239      Options:
240        EndHostPowerState: 1
241        ShutdownType: 0
242        TimeToWaitBeforeShutdown: 300
243      Schedule:
244        RunLater: true
245        RunNow: false
246
247- name: "Deploy template on multiple devices and changes the device-level attributes. After the template is deployed,
248install OS using its image"
249  dellemc.openmanage.ome_template:
250    hostname:  "192.168.0.1"
251    username: "username"
252    password: "password"
253    command: "deploy"
254    template_id: 12
255    device_id:
256      - 12765
257      - 10173
258    device_service_tag:
259      - 'SVTG123'
260      - 'SVTG456'
261    attributes:
262      Attributes:
263        - DeviceId: 12765
264          Attributes:
265            - Id : 15645
266              Value : "0.0.0.0"
267              IsIgnored : false
268        - DeviceId: 10173
269          Attributes:
270            - Id : 18968,
271              Value : "hostname-1"
272              IsIgnored : false
273      NetworkBootIsoModel:
274        BootToNetwork: true
275        ShareType: "NFS"
276        IsoTimeout: 1 # allowable values(1,2,4,8,16) in hours
277        IsoPath: "/home/iso_path/filename.iso"
278        ShareDetail:
279          IpAddress: "192.168.0.2"
280          ShareName: "sharename"
281          User: "share_user"
282          Password: "share_password"
283      Options:
284        EndHostPowerState: 1
285        ShutdownType: 0
286        TimeToWaitBeforeShutdown: 300
287      Schedule:
288        RunLater: true
289        RunNow: false
290
291- name: Delete template
292  dellemc.openmanage.ome_template:
293    hostname: "192.168.0.1"
294    username: "username"
295    password: "password"
296    command: "delete"
297    template_id: 12
298
299- name: Export a template
300  dellemc.openmanage.ome_template:
301    hostname: "192.168.0.1"
302    username: "username"
303    password: "password"
304    command: "export"
305    template_id: 12
306
307# Start of example to export template to a local xml file
308- name: Export template to a local xml file
309  dellemc.openmanage.ome_template:
310    hostname: "192.168.0.1"
311    username: "username"
312    password: "password"
313    command: "export"
314    template_name: "my_template"
315  register: result
316- name: Save template into a file
317  ansible.builtin.copy:
318    content: "{{ result.Content}}"
319    dest: "/path/to/exported_template.xml"
320# End of example to export template to a local xml file
321
322- name: Clone a template
323  dellemc.openmanage.ome_template:
324    hostname: "192.168.0.1"
325    username: "username"
326    password: "password"
327    command: "clone"
328    template_id: 12
329    attributes:
330      Name: "New Cloned Template Name"
331
332- name: Import template from XML content
333  dellemc.openmanage.ome_template:
334    hostname: "192.168.0.1"
335    username: "username"
336    password: "password"
337    command: "import"
338    attributes:
339      Name: "Imported Template Name"
340      # Template Type from TemplateService/TemplateTypes
341      Type: 2
342      # xml string content
343      Content: "<SystemConfiguration Model=\"PowerEdge R940\" ServiceTag=\"SVCTAG1\"
344      TimeStamp=\"Tue Sep 24 09:20:57.872551 2019\">\n<Component FQDD=\"AHCI.Slot.6-1\">\n<Attribute
345      Name=\"RAIDresetConfig\">True</Attribute>\n<Attribute Name=\"RAIDforeignConfig\">Clear</Attribute>\n
346      </Component>\n<Component FQDD=\"Disk.Direct.0-0:AHCI.Slot.6-1\">\n<Attribute Name=\"RAIDPDState\">Ready
347      </Attribute>\n<Attribute Name=\"RAIDHotSpareStatus\">No</Attribute>\n</Component>\n
348      <Component FQDD=\"Disk.Direct.1-1:AHCI.Slot.6-1\">\n<Attribute Name=\"RAIDPDState\">Ready</Attribute>\n
349      <Attribute Name=\"RAIDHotSpareStatus\">No</Attribute>\n</Component>\n</SystemConfiguration>\n"
350
351- name: Import template from local XML file
352  dellemc.openmanage.ome_template:
353    hostname: "192.168.0.1"
354    username: "username"
355    password: "password"
356    command: "import"
357    attributes:
358      Name: "Imported Template Name"
359      Type: 2
360      Content: "{{ lookup('ansible.builtin.file.', '/path/to/xmlfile') }}"
361'''
362
363RETURN = r'''
364---
365msg:
366  description: Overall status of the template operation.
367  returned: always
368  type: str
369  sample: "Successfully created a template with ID 23"
370return_id:
371  description: ID of the template for C(create), C(modify), C(import) and C(clone) or task created in case of C(deploy).
372  returned: success, when I(command) is C(create), C(modify), C(import), C(clone) and C(deploy)
373  type: int
374  sample: 12
375TemplateId:
376  description: ID of the template for C(export).
377  returned: success, when I(command) is C(export)
378  type: int
379  sample: 13
380Content:
381  description: XML content of the exported template. This content can be written to a xml file.
382  returned: success, when I(command) is C(export)
383  type: str
384  sample: "<SystemConfiguration Model=\"PowerEdge R940\" ServiceTag=\"DG22TR2\" TimeStamp=\"Tue Sep 24 09:20:57.872551
385     2019\">\n<Component FQDD=\"AHCI.Slot.6-1\">\n<Attribute Name=\"RAIDresetConfig\">True</Attribute>\n<Attribute
386     Name=\"RAIDforeignConfig\">Clear</Attribute>\n</Component>\n<Component FQDD=\"Disk.Direct.0-0:AHCI.Slot.6-1\">\n
387     <Attribute Name=\"RAIDPDState\">Ready</Attribute>\n<Attribute Name=\"RAIDHotSpareStatus\">No</Attribute>\n
388     </Component>\n<Component FQDD=\"Disk.Direct.1-1:AHCI.Slot.6-1\">\n<Attribute Name=\"RAIDPDState\">Ready
389     </Attribute>\n<Attribute Name=\"RAIDHotSpareStatus\">No</Attribute>\n</Component>\n</SystemConfiguration>\n"
390error_info:
391  description: Details of the HTTP Error.
392  returned: on HTTP error
393  type: dict
394  sample: {
395    "error": {
396      "code": "Base.1.0.GeneralError",
397      "message": "A general error has occurred. See ExtendedInfo for more information.",
398      "@Message.ExtendedInfo": [
399        {
400          "MessageId": "GEN1234",
401          "RelatedProperties": [],
402          "Message": "Unable to process the request because an error occurred.",
403          "MessageArgs": [],
404          "Severity": "Critical",
405          "Resolution": "Retry the operation. If the issue persists, contact your system administrator."
406        }
407      ]
408    }
409  }
410'''
411
412import json
413from ssl import SSLError
414from ansible.module_utils.basic import AnsibleModule
415from ansible_collections.dellemc.openmanage.plugins.module_utils.ome import RestOME
416from ansible.module_utils.six.moves.urllib.error import URLError, HTTPError
417from ansible.module_utils.urls import ConnectionError, SSLValidationError
418
419
420TEMPLATES_URI = "TemplateService/Templates"
421TEMPLATE_PATH = "TemplateService/Templates({template_id})"
422TEMPALTE_ACTION = "TemplateService/Actions/TemplateService.{op}"
423DEVICE_URI = "DeviceService/Devices"
424GROUP_URI = "GroupService/Groups"
425
426
427def get_group_devices_all(rest_obj, uri):
428    total_items = []
429    next_link = uri
430    while next_link:
431        resp = rest_obj.invoke_request('GET', next_link)
432        data = resp.json_data
433        total_items.extend(data.get("value", []))
434        next_link = str(data.get('@odata.nextLink', '')).split('/api')[-1]
435    return total_items
436
437
438def get_group(rest_obj, module, group_name):
439    query_param = {"$filter": "Name eq '{0}'".format(group_name)}
440    group_req = rest_obj.invoke_request("GET", GROUP_URI, query_param=query_param)
441    for grp in group_req.json_data.get('value'):
442        if grp['Name'] == group_name:
443            return grp
444    module.fail_json(msg="Group name '{0}' is invalid. Please provide a valid group name.".format(group_name))
445
446
447def get_group_details(rest_obj, module):
448    group_name_list = module.params.get('device_group_names')
449    device_ids = []
450    for group_name in group_name_list:
451        group = get_group(rest_obj, module, group_name)
452        group_uri = GROUP_URI + "({0})/Devices".format(group['Id'])
453        group_device_list = get_group_devices_all(rest_obj, group_uri)
454        device_ids.extend([dev['Id'] for dev in group_device_list])
455    return device_ids
456
457
458def get_device_ids(module, rest_obj):
459    """Getting the list of device ids filtered from the device inventory."""
460    target_ids = []
461    if module.params.get('device_service_tag') or module.params.get('device_id'):
462        # device_list = get_group_devices_all(rest_obj, DEVICE_URI)
463        device_list = rest_obj.get_all_report_details(DEVICE_URI)['report_list']
464        device_tag_id_map = dict([(device.get('DeviceServiceTag'), device.get('Id')) for device in device_list])
465        device_id = module.params.get('device_id')
466        invalid_ids = set(device_id) - set(device_tag_id_map.values())
467        if invalid_ids:
468            fail_module(module, msg="Unable to complete the operation because the entered target device"
469                                    " id(s) '{0}' are invalid.".format(",".join(list(map(str, set(invalid_ids))))))
470        target_ids.extend(device_id)
471        service_tags = module.params.get('device_service_tag')
472        invalid_tags = set(service_tags) - set(device_tag_id_map.keys())
473        if invalid_tags:
474            fail_module(module, msg="Unable to complete the operation because the entered target service"
475                                    " tag(s) '{0}' are invalid.".format(",".join(set(invalid_tags))))
476        for tag in service_tags:  # append ids for service tags
477            target_ids.append(device_tag_id_map.get(tag))
478    if module.params.get('device_group_names'):
479        target_ids.extend(get_group_details(rest_obj, module))
480    return list(set(target_ids))  # set to eliminate duplicates
481
482
483def get_view_id(rest_obj, viewstr):
484    resp = rest_obj.invoke_request('GET', "TemplateService/TemplateViewTypes")
485    if resp.success and resp.json_data.get('value'):
486        tlist = resp.json_data.get('value', [])
487        for xtype in tlist:
488            if xtype.get('Description', "") == viewstr:
489                return xtype.get('Id')
490    viewmap = {"Deployment": 2, "Compliance": 1, "Inventory": 3, "Sample": 4, "None": 0}
491    return viewmap.get(viewstr)
492
493
494def get_type_id_valid(rest_obj, typeid):
495    resp = rest_obj.invoke_request('GET', "TemplateService/TemplateTypes")
496    if resp.success and resp.json_data.get('value'):
497        tlist = resp.json_data.get('value', [])
498        for xtype in tlist:
499            if xtype.get('Id') == typeid:  # use Name if str is passed
500                return True
501    return False
502
503
504def get_create_payload(module_params, deviceid, view_id):
505    create_payload = {"Fqdds": "All",
506                      "ViewTypeId": view_id}
507    if isinstance(module_params.get("attributes"), dict):
508        create_payload.update(module_params.get("attributes"))
509    create_payload["SourceDeviceId"] = int(deviceid)
510    return create_payload
511
512
513def get_modify_payload(module_params, template_id, template_dict):
514    modify_payload = {}
515    if isinstance(module_params.get("attributes"), dict):
516        modify_payload.update(module_params.get("attributes"))
517    modify_payload['Id'] = template_id
518    # Update with old template values
519    if not modify_payload.get("Name"):
520        modify_payload["Name"] = template_dict["Name"]
521    if not modify_payload.get("Description"):
522        modify_payload["Description"] = template_dict["Description"]
523    return modify_payload
524
525
526def get_deploy_payload(module_params, deviceidlist, template_id):
527    deploy_payload = {}
528    if isinstance(module_params.get("attributes"), dict):
529        deploy_payload.update(module_params.get("attributes"))
530    deploy_payload["Id"] = template_id
531    deploy_payload["TargetIds"] = deviceidlist
532    return deploy_payload
533
534
535def get_import_payload(module, rest_obj, view_id):
536    attrib_dict = module.params.get("attributes").copy()
537    import_payload = {}
538    import_payload["Name"] = attrib_dict.pop("Name")
539    import_payload["ViewTypeId"] = view_id
540    import_payload["Type"] = 2
541    typeid = attrib_dict.get("Type")
542    if typeid:
543        if get_type_id_valid(rest_obj, typeid):
544            import_payload["Type"] = typeid   # Type is mandatory for import
545        else:
546            fail_module(module, msg="Type provided for 'import' operation is invalid")
547    import_payload["Content"] = attrib_dict.pop("Content")
548    if isinstance(attrib_dict, dict):
549        import_payload.update(attrib_dict)
550    return import_payload
551
552
553def get_clone_payload(module_params, template_id, view_id):
554    attrib_dict = module_params.get("attributes").copy()
555    clone_payload = {}
556    clone_payload["SourceTemplateId"] = template_id
557    clone_payload["NewTemplateName"] = attrib_dict.pop("Name")
558    clone_payload["ViewTypeId"] = view_id
559    if isinstance(attrib_dict, dict):
560        clone_payload.update(attrib_dict)
561    return clone_payload
562
563
564def get_template_by_id(module, rest_obj, template_id):
565    path = TEMPLATE_PATH.format(template_id=template_id)
566    template_req = rest_obj.invoke_request("GET", path)
567    if template_req.success:
568        return template_req.json_data
569    else:
570        fail_module(module, msg="Unable to complete the operation because the"
571                                " requested template is not present.")
572
573
574def get_template_by_name(template_name, module, rest_obj):
575    """Filter out specific template based on name, and it returns template_id.
576
577    :param template_name: string
578    :param module: dictionary
579    :param rest_obj: object
580    :return: template_id: integer
581    """
582    template_id = None
583    template = None
584    template_path = TEMPLATES_URI
585    query_param = {"$filter": "Name eq '{0}'".format(template_name)}
586    template_req = rest_obj.invoke_request("GET", template_path, query_param=query_param)
587    for each in template_req.json_data.get('value'):
588        if each['Name'] == template_name:
589            template_id = each['Id']
590            template = each
591            break
592    else:
593        fail_module(module, msg="Unable to complete the operation because the"
594                                " requested template with name {0} is not present.".format(template_name))
595    return template, template_id
596
597
598def _get_resource_parameters(module, rest_obj):
599    command = module.params.get("command")
600    rest_method = 'POST'
601    payload = {}
602    template_id = module.params.get("template_id")
603    template_name = module.params.get("template_name")
604    if template_name:
605        template, template_id = get_template_by_name(template_name, module, rest_obj)
606    if command not in ["import", "create"] and template_id is None:
607        fail_module(module, msg="Enter a valid template_name or template_id")
608    if command == "create":
609        devid_list = get_device_ids(module, rest_obj)
610        if len(devid_list) != 1:
611            fail_module(module, msg="Create template requires only one reference device")
612        view_id = get_view_id(rest_obj, module.params['template_view_type'])
613        payload = get_create_payload(module.params, devid_list[0], view_id)
614        path = TEMPLATES_URI
615    elif command == "modify":
616        path = TEMPLATE_PATH.format(template_id=template_id)
617        template_dict = get_template_by_id(module, rest_obj, template_id)
618        payload = get_modify_payload(module.params, template_id, template_dict)
619        rest_method = 'PUT'
620    elif command == "delete":
621        path = TEMPLATE_PATH.format(template_id=template_id)
622        rest_method = 'DELETE'
623    elif command == "export":
624        path = TEMPALTE_ACTION.format(op="Export")
625        payload = {'TemplateId': template_id}
626    elif command == "deploy":
627        devid_list = get_device_ids(module, rest_obj)
628        if not devid_list:
629            fail_module(module, msg="There are no devices provided for deploy operation")
630        path = TEMPALTE_ACTION.format(op="Deploy")
631        payload = get_deploy_payload(module.params, devid_list, template_id)
632    elif command == "clone":
633        view_id = get_view_id(rest_obj, module.params['template_view_type'])
634        path = TEMPALTE_ACTION.format(op="Clone")
635        payload = get_clone_payload(module.params, template_id, view_id)
636    else:
637        view_id = get_view_id(rest_obj, module.params['template_view_type'])
638        path = TEMPALTE_ACTION.format(op="Import")
639        payload = get_import_payload(module, rest_obj, view_id)
640    return path, payload, rest_method
641
642
643def _validate_inputs(module):
644    """validates input parameters"""
645    command = module.params.get("command")
646    if command in ["create", "deploy"]:
647        dev_id = module.params["device_id"]
648        dev_st = module.params["device_service_tag"]
649        if None in dev_id or None in dev_st:
650            fail_module(module, msg="Argument device_id or device_service_tag has null values")
651    attrib_dict = {}
652    if module.params.get("attributes"):
653        attrib_dict = module.params.get("attributes")
654    if command in ["import", "clone", "create"]:
655        if not attrib_dict.get("Name"):
656            fail_module(module, msg="Argument 'Name' required in attributes for {0} operation".format(command))
657    if command == "import":
658        if not attrib_dict.get("Content"):
659            fail_module(module, msg="Argument 'Content' required in attributes for {0} operation".format(command))
660
661
662def password_no_log(attributes):
663    if isinstance(attributes, dict):
664        netdict = attributes.get("NetworkBootIsoModel")
665        if isinstance(netdict, dict):
666            sharedet = netdict.get("ShareDetail")
667            if isinstance(sharedet, dict) and 'Password' in sharedet:
668                sharedet['Password'] = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
669
670
671def fail_module(module, **failmsg):
672    password_no_log(module.params.get("attributes"))
673    module.fail_json(**failmsg)
674
675
676def exit_module(module, response):
677    password_no_log(module.params.get("attributes"))
678    resp = None
679    my_change = True
680    command = module.params.get('command')
681    result = {}
682    if command in ["create", "modify", "deploy", "import", "clone"]:
683        result["return_id"] = response.json_data
684        resp = result["return_id"]
685        if command == 'deploy' and result["return_id"] == 0:
686            result["failed"] = True
687            command = 'deploy_fail'
688            my_change = False
689    if command == 'export':
690        my_change = False
691        result = response.json_data
692    msg_dict = {'create': "Successfully created a template with ID {0}".format(resp),
693                'modify': "Successfully modified the template with ID {0}".format(resp),
694                'deploy': "Successfully created the template-deployment job with ID {0}".format(resp),
695                'deploy_fail': 'Failed to deploy template.',
696                'delete': "Deleted successfully",
697                'export': "Exported successfully",
698                'import': "Imported successfully",
699                'clone': "Cloned successfully"}
700    module.exit_json(msg=msg_dict.get(command), changed=my_change, **result)
701
702
703def main():
704    module = AnsibleModule(
705        argument_spec={
706            "hostname": {"required": True, "type": 'str'},
707            "username": {"required": True, "type": 'str'},
708            "password": {"required": True, "type": 'str', "no_log": True},
709            "port": {"required": False, "default": 443, "type": 'int'},
710            "command": {"required": False, "default": "create", "aliases": ['state'],
711                        "choices": ['create', 'modify', 'deploy', 'delete', 'export', 'import', 'clone']},
712            "template_id": {"required": False, "type": 'int'},
713            "template_name": {"required": False, "type": 'str'},
714            "template_view_type": {"required": False, "default": 'Deployment',
715                                   "choices": ['Deployment', 'Compliance', 'Inventory', 'Sample', 'None']},
716            "device_id": {"required": False, "type": 'list', "default": [], "elements": 'int'},
717            "device_service_tag": {"required": False, "type": 'list', "default": [], "elements": 'str'},
718            "device_group_names": {"required": False, "type": 'list', "default": [], "elements": 'str'},
719            "attributes": {"required": False, "type": 'dict'},
720        },
721        required_if=[
722            ['command', 'create', ['attributes']],
723            ['command', 'modify', ['attributes']],
724            ['command', 'import', ['attributes']],
725            ['command', 'modify', ['template_id', 'template_name'], True],
726            ['command', 'delete', ['template_id', 'template_name'], True],
727            ['command', 'export', ['template_id', 'template_name'], True],
728            ['command', 'clone', ['template_id', 'template_name'], True],
729            ['command', 'deploy', ['template_id', 'template_name'], True],
730            ['command', 'deploy', ['device_id', 'device_service_tag', 'device_group_names'], True],
731        ],
732        mutually_exclusive=[["template_id", "template_name"]],
733        supports_check_mode=False)
734
735    try:
736        _validate_inputs(module)
737        with RestOME(module.params, req_session=True) as rest_obj:
738            path, payload, rest_method = _get_resource_parameters(module, rest_obj)
739            resp = rest_obj.invoke_request(rest_method, path, data=payload)
740            if resp.success:
741                exit_module(module, resp)
742    except HTTPError as err:
743        fail_module(module, msg=str(err), error_info=json.load(err))
744    except URLError as err:
745        password_no_log(module.params.get("attributes"))
746        module.exit_json(msg=str(err), unreachable=True)
747    except (IOError, SSLError, SSLValidationError, ConnectionError, TypeError, ValueError, KeyError) as err:
748        fail_module(module, msg=str(err))
749
750
751if __name__ == '__main__':
752    main()
753