645 lines
25 KiB
Python
645 lines
25 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
# Dell EMC OpenManage Ansible Modules
|
|
# Version 4.4.0
|
|
# Copyright (C) 2019-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_firmware
|
|
short_description: Update firmware on PowerEdge devices and its components through OpenManage Enterprise
|
|
version_added: "2.0.0"
|
|
description: This module updates the firmware of PowerEdge devices and all its components through OpenManage Enterprise.
|
|
extends_documentation_fragment:
|
|
- dellemc.openmanage.ome_auth_options
|
|
options:
|
|
device_service_tag:
|
|
description:
|
|
- List of service tags of the targeted devices.
|
|
- Either I(device_id) or I(device_service_tag) can be used individually or together.
|
|
- This option is mutually exclusive with I(device_group_names) and I(devices).
|
|
type: list
|
|
elements: str
|
|
device_id:
|
|
description:
|
|
- List of ids of the targeted device.
|
|
- Either I(device_id) or I(device_service_tag) can be used individually or together.
|
|
- This option is mutually exclusive with I(device_group_names) and I(devices).
|
|
type: list
|
|
elements: int
|
|
device_group_names:
|
|
description:
|
|
- Enter the name of the device group that contains the devices on which firmware needs to be updated.
|
|
- This option is mutually exclusive with I(device_id) and I(device_service_tag).
|
|
type: list
|
|
elements: str
|
|
dup_file:
|
|
description:
|
|
- "The path of the Dell Update Package (DUP) file that contains the firmware or drivers required to update the
|
|
target system device or individual device components."
|
|
- This is mutually exclusive with I(baseline_name), I(components), and I(devices).
|
|
type: path
|
|
baseline_name:
|
|
description:
|
|
- Enter the baseline name to update the firmware of all devices or list of devices that are not complaint.
|
|
- This option is mutually exclusive with I(dup_file) and I(device_group_names).
|
|
type: str
|
|
components:
|
|
description:
|
|
- List of components to be updated.
|
|
- If not provided, all components applicable are considered.
|
|
- This option is case sensitive.
|
|
- This is applicable to I(device_service_tag), I(device_id), and I(baseline_name).
|
|
type: list
|
|
elements: str
|
|
devices:
|
|
description:
|
|
- This option allows to select components on each device for firmware update.
|
|
- This option is mutually exclusive with I(dup_file), I(device_group_names), I(device_id), and I(device_service_tag).
|
|
type: list
|
|
elements: dict
|
|
suboptions:
|
|
id:
|
|
type: int
|
|
description:
|
|
- The id of the target device to be updated.
|
|
- This option is mutually exclusive with I(service_tag).
|
|
service_tag:
|
|
type: str
|
|
description:
|
|
- The service tag of the target device to be updated.
|
|
- This option is mutually exclusive with I(id).
|
|
components:
|
|
description: The target components to be updated. If not specified, all applicable device components are considered.
|
|
type: list
|
|
elements: str
|
|
schedule:
|
|
type: str
|
|
description:
|
|
- Select the schedule for the firmware update.
|
|
- if C(StageForNextReboot) is chosen, the firmware will be staged and updated during the next reboot
|
|
of the target device.
|
|
- if C(RebootNow) will apply the firmware updates immediately.
|
|
choices:
|
|
- RebootNow
|
|
- StageForNextReboot
|
|
default: RebootNow
|
|
requirements:
|
|
- "python >= 2.7.17"
|
|
author:
|
|
- "Felix Stephen (@felixs88)"
|
|
- "Jagadeesh N V (@jagadeeshnv)"
|
|
notes:
|
|
- Run this module from a system that has direct access to Dell EMC OpenManage Enterprise.
|
|
- This module supports C(check_mode).
|
|
'''
|
|
|
|
EXAMPLES = r'''
|
|
---
|
|
- name: Update firmware from DUP file using device ids
|
|
dellemc.openmanage.ome_firmware:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
device_id:
|
|
- 11111
|
|
- 22222
|
|
dup_file: "/path/Chassis-System-Management_Firmware_6N9WN_WN64_1.00.01_A00.EXE"
|
|
|
|
- name: Update firmware from a DUP file using a device service tags
|
|
dellemc.openmanage.ome_firmware:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
device_service_tag:
|
|
- KLBR111
|
|
- KLBR222
|
|
dup_file: "/path/Network_Firmware_NTRW0_WN64_14.07.07_A00-00_01.EXE"
|
|
|
|
- name: Update firmware from a DUP file using a device group names
|
|
dellemc.openmanage.ome_firmware:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
device_group_names:
|
|
- servers
|
|
dup_file: "/path/BIOS_87V69_WN64_2.4.7.EXE"
|
|
|
|
- name: Update firmware using baseline name
|
|
dellemc.openmanage.ome_firmware:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
baseline_name: baseline_devices
|
|
|
|
- name: Stage firmware for the next reboot using baseline name
|
|
dellemc.openmanage.ome_firmware:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
baseline_name: baseline_devices
|
|
schedule: StageForNextReboot
|
|
|
|
- name: "Update firmware using baseline name and components."
|
|
dellemc.openmanage.ome_firmware:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
baseline_name: baseline_devices
|
|
components:
|
|
- BIOS
|
|
|
|
- name: Update firmware of device components from a DUP file using a device ids in a baseline
|
|
dellemc.openmanage.ome_firmware:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
baseline_name: baseline_devices
|
|
device_id:
|
|
- 11111
|
|
- 22222
|
|
components:
|
|
- iDRAC with Lifecycle Controller
|
|
|
|
- name: Update firmware of device components from a baseline using a device service tags under a baseline
|
|
dellemc.openmanage.ome_firmware:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
baseline_name: baseline_devices
|
|
device_service_tag:
|
|
- KLBR111
|
|
- KLBR222
|
|
components:
|
|
- IOM-SAS
|
|
|
|
- name: Update firmware using baseline name with a device id and required components
|
|
dellemc.openmanage.ome_firmware:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
baseline_name: baseline_devices
|
|
devices:
|
|
- id: 12345
|
|
components:
|
|
- Lifecycle Controller
|
|
- id: 12346
|
|
components:
|
|
- Enterprise UEFI Diagnostics
|
|
- BIOS
|
|
|
|
- name: "Update firmware using baseline name with a device service tag and required components."
|
|
dellemc.openmanage.ome_firmware:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
baseline_name: baseline_devices
|
|
devices:
|
|
- service_tag: ABCDE12
|
|
components:
|
|
- PERC H740P Adapter
|
|
- BIOS
|
|
- service_tag: GHIJK34
|
|
components:
|
|
- OS Drivers Pack
|
|
|
|
- name: "Update firmware using baseline name with a device service tag or device id and required components."
|
|
dellemc.openmanage.ome_firmware:
|
|
hostname: "192.168.0.1"
|
|
username: "username"
|
|
password: "password"
|
|
baseline_name: baseline_devices
|
|
devices:
|
|
- service_tag: ABCDE12
|
|
components:
|
|
- BOSS-S1 Adapter
|
|
- PowerEdge Server BIOS
|
|
- id: 12345
|
|
components:
|
|
- iDRAC with Lifecycle Controller
|
|
'''
|
|
|
|
RETURN = r'''
|
|
---
|
|
msg:
|
|
type: str
|
|
description: "Overall firmware update status."
|
|
returned: always
|
|
sample: Successfully submitted the firmware update job.
|
|
update_status:
|
|
type: dict
|
|
description: The firmware update job and progress details from the OME.
|
|
returned: success
|
|
sample: {
|
|
'LastRun': None,
|
|
'CreatedBy': 'user',
|
|
'Schedule': 'startnow',
|
|
'LastRunStatus': {
|
|
'Id': 1111,
|
|
'Name': 'NotRun'
|
|
},
|
|
'Builtin': False,
|
|
'Editable': True,
|
|
'NextRun': None,
|
|
'JobStatus': {
|
|
'Id': 1111,
|
|
'Name': 'New'
|
|
},
|
|
'JobName': 'Firmware Update Task',
|
|
'Visible': True,
|
|
'State': 'Enabled',
|
|
'JobDescription': 'dup test',
|
|
'Params': [{
|
|
'Value': 'true',
|
|
'Key': 'signVerify',
|
|
'JobId': 11111}, {
|
|
'Value': 'false',
|
|
'Key': 'stagingValue',
|
|
'JobId': 11112}, {
|
|
'Value': 'false',
|
|
'Key': 'complianceUpdate',
|
|
'JobId': 11113}, {
|
|
'Value': 'INSTALL_FIRMWARE',
|
|
'Key': 'operationName',
|
|
'JobId': 11114}],
|
|
'Targets': [{
|
|
'TargetType': {
|
|
'Id': 1000,
|
|
'Name': 'DEVICE'},
|
|
'Data': 'DCIM:INSTALLED#701__NIC.Mezzanine.1A-1-1=1234567654321',
|
|
'Id': 11115,
|
|
'JobId': 11116}],
|
|
'StartTime': None,
|
|
'UpdatedBy': None,
|
|
'EndTime': None,
|
|
'Id': 11117,
|
|
'JobType': {
|
|
'Internal': False,
|
|
'Id': 5,
|
|
'Name': 'Update_Task'}
|
|
}
|
|
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
|
|
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.urls import open_url, ConnectionError, SSLValidationError
|
|
from ansible.module_utils.six.moves.urllib.error import URLError, HTTPError
|
|
|
|
|
|
COMPLIANCE_URI = "UpdateService/Baselines({0})/DeviceComplianceReports"
|
|
BASELINE_URI = "UpdateService/Baselines"
|
|
FW_JOB_DESC = "Firmware update task initiated from OpenManage Ansible Module collections"
|
|
NO_CHANGES_MSG = "No changes found to be applied. Either there are no updates present or components specified are not" \
|
|
" found in the baseline."
|
|
COMPLIANCE_READ_FAIL = "Failed to read compliance report."
|
|
DUP_REQ_MSG = "Parameter 'dup_file' to be provided along with 'device_id'|'device_service_tag'|'device_group_names'"
|
|
APPLICABLE_DUP = "Unable to get applicable components DUP."
|
|
CHANGES_FOUND = "Changes found to be applied."
|
|
|
|
|
|
def spawn_update_job(rest_obj, job_payload):
|
|
"""Spawns an update job and tracks it to completion."""
|
|
job_uri, job_details = "JobService/Jobs", {}
|
|
job_resp = rest_obj.invoke_request("POST", job_uri, data=job_payload)
|
|
if job_resp.status_code == 201:
|
|
job_details = job_resp.json_data
|
|
return job_details
|
|
|
|
|
|
def job_payload_for_update(rest_obj, module, target_data, baseline=None):
|
|
"""Formulate the payload to initiate a firmware update job."""
|
|
resp = rest_obj.get_job_type_id("Update_Task")
|
|
if resp is None:
|
|
module.fail_json(msg="Unable to fetch the job type Id.")
|
|
stage_dict = {"StageForNextReboot": 'true', "RebootNow": 'false'}
|
|
schedule = module.params["schedule"]
|
|
params = [{"Key": "operationName", "Value": "INSTALL_FIRMWARE"},
|
|
{"Key": "stagingValue", "Value": stage_dict[schedule]},
|
|
{"Key": "signVerify", "Value": "true"}]
|
|
# reboot applicable only if staging false
|
|
if schedule == "RebootNow":
|
|
params.append({"Key": "rebootType", "Value": "3"})
|
|
# reboot_dict = {"GracefulReboot": "2", "GracefulRebootForce": "3", "PowerCycle": "1"}
|
|
payload = {
|
|
"Id": 0, "JobName": "Firmware Update Task",
|
|
"JobDescription": FW_JOB_DESC, "Schedule": "startnow",
|
|
"State": "Enabled", "JobType": {"Id": resp, "Name": "Update_Task"},
|
|
"Targets": target_data,
|
|
"Params": params
|
|
}
|
|
|
|
if baseline is not None:
|
|
payload["Params"].append({"Key": "complianceReportId", "Value": "{0}".format(baseline["baseline_id"])})
|
|
payload["Params"].append({"Key": "repositoryId", "Value": "{0}".format(baseline["repo_id"])})
|
|
payload["Params"].append({"Key": "catalogId", "Value": "{0}".format(baseline["catalog_id"])})
|
|
payload["Params"].append({"Key": "complianceUpdate", "Value": "true"})
|
|
else:
|
|
payload["Params"].append({"JobId": 0, "Key": "complianceUpdate", "Value": "false"})
|
|
return payload
|
|
|
|
|
|
def get_applicable_components(rest_obj, dup_payload, module):
|
|
"""Get the target array to be used in spawning jobs for update."""
|
|
target_data = []
|
|
dup_url = "UpdateService/Actions/UpdateService.GetSingleDupReport"
|
|
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
|
dup_resp = rest_obj.invoke_request("POST", dup_url, data=dup_payload,
|
|
headers=headers, api_timeout=60)
|
|
if dup_resp.status_code == 200:
|
|
dup_data = dup_resp.json_data
|
|
file_token = str(dup_payload['SingleUpdateReportFileToken'])
|
|
for device in dup_data:
|
|
for component in device['DeviceReport']['Components']:
|
|
temp_map = {}
|
|
temp_map['Id'] = device['DeviceId']
|
|
temp_map['Data'] = "{0}={1}".format(component['ComponentSourceName'], file_token)
|
|
temp_map['TargetType'] = {}
|
|
temp_map['TargetType']['Id'] = int(device['DeviceReport']['DeviceTypeId'])
|
|
temp_map['TargetType']['Name'] = str(device['DeviceReport']['DeviceTypeName'])
|
|
target_data.append(temp_map)
|
|
else:
|
|
module.fail_json(msg=APPLICABLE_DUP)
|
|
return target_data
|
|
|
|
|
|
def get_dup_applicability_payload(file_token, device_ids=None, group_ids=None, baseline_ids=None):
|
|
"""Returns the DUP applicability JSON payload."""
|
|
dup_applicability_payload = {'SingleUpdateReportBaseline': [],
|
|
'SingleUpdateReportGroup': [],
|
|
'SingleUpdateReportTargets': [],
|
|
'SingleUpdateReportFileToken': file_token}
|
|
if device_ids is not None:
|
|
dup_applicability_payload.update({"SingleUpdateReportTargets": list(map(int, device_ids))})
|
|
elif group_ids is not None:
|
|
dup_applicability_payload.update({"SingleUpdateReportGroup": list(map(int, group_ids))})
|
|
elif baseline_ids is not None:
|
|
dup_applicability_payload.update({"SingleUpdateReportBaseline": list(map(int, baseline_ids))})
|
|
return dup_applicability_payload
|
|
|
|
|
|
def upload_dup_file(rest_obj, module):
|
|
"""Upload DUP file to OME and get a file token."""
|
|
upload_uri = "UpdateService/Actions/UpdateService.UploadFile"
|
|
headers = {"Content-Type": "application/octet-stream", "Accept": "application/octet-stream"}
|
|
upload_success, token = False, None
|
|
dup_file = module.params['dup_file']
|
|
with open(dup_file, 'rb') as payload:
|
|
payload = payload.read()
|
|
response = rest_obj.invoke_request("POST", upload_uri, data=payload, headers=headers,
|
|
api_timeout=100, dump=False)
|
|
if response.status_code == 200:
|
|
upload_success = True
|
|
token = str(response.json_data)
|
|
else:
|
|
module.fail_json(msg="Unable to upload {0} to {1}".format(dup_file, module.params['hostname']))
|
|
return upload_success, token
|
|
|
|
|
|
def get_device_ids(rest_obj, module, device_id_tags):
|
|
"""Getting the list of device ids filtered from the device inventory."""
|
|
device_id = []
|
|
resp = rest_obj.get_all_report_details("DeviceService/Devices")
|
|
if resp.get("report_list"):
|
|
device_resp = dict([(str(device['Id']), device['DeviceServiceTag']) for device in resp["report_list"]])
|
|
device_tags = map(str, device_id_tags)
|
|
invalid_tags = []
|
|
for tag in device_tags:
|
|
if tag in device_resp.keys():
|
|
device_id.append(tag)
|
|
elif tag in device_resp.values():
|
|
ids = list(device_resp.keys())[list(device_resp.values()).index(tag)]
|
|
device_id.append(ids)
|
|
else:
|
|
invalid_tags.append(tag)
|
|
if invalid_tags:
|
|
module.fail_json(
|
|
msg="Unable to complete the operation because the entered target device service"
|
|
" tag(s) or device id(s) '{0}' are invalid.".format(",".join(set(invalid_tags))))
|
|
else:
|
|
module.fail_json(msg="Failed to fetch the device facts.")
|
|
return device_id, device_resp
|
|
|
|
|
|
def get_group_ids(rest_obj, module):
|
|
"""Getting the list of group ids filtered from the groups."""
|
|
resp = rest_obj.get_all_report_details("GroupService/Groups")
|
|
group_name = module.params.get('device_group_names')
|
|
if resp["report_list"]:
|
|
grp_ids = [grp['Id'] for grp in resp["report_list"] for grpname in group_name if grp['Name'] == grpname]
|
|
if len(set(group_name)) != len(set(grp_ids)):
|
|
module.fail_json(
|
|
msg="Unable to complete the operation because the entered target device group name(s)"
|
|
" '{0}' are invalid.".format(",".join(set(group_name))))
|
|
return grp_ids
|
|
|
|
|
|
def get_baseline_ids(rest_obj, module):
|
|
"""Getting the list of group ids filtered from the groups."""
|
|
resp = rest_obj.get_all_report_details(BASELINE_URI)
|
|
baseline, baseline_details = module.params.get('baseline_name'), {}
|
|
if resp["report_list"]:
|
|
for bse in resp["report_list"]:
|
|
if bse['Name'] == baseline:
|
|
baseline_details["baseline_id"] = bse["Id"]
|
|
baseline_details["repo_id"] = bse["RepositoryId"]
|
|
baseline_details["catalog_id"] = bse["CatalogId"]
|
|
if not baseline_details:
|
|
module.fail_json(
|
|
msg="Unable to complete the operation because the entered target baseline name"
|
|
" '{0}' is invalid.".format(baseline))
|
|
else:
|
|
module.fail_json(msg="Unable to complete the operation because the entered "
|
|
"target baseline name does not exist.")
|
|
return baseline_details
|
|
|
|
|
|
def single_dup_update(rest_obj, module):
|
|
target_data, device_ids, group_ids, baseline_ids = None, None, None, None
|
|
if module.params.get("device_group_names") is not None:
|
|
group_ids = get_group_ids(rest_obj, module)
|
|
else:
|
|
device_id_tags = _validate_device_attributes(module)
|
|
device_ids, id_tag_map = get_device_ids(rest_obj, module, device_id_tags)
|
|
if module.check_mode:
|
|
module.exit_json(msg=CHANGES_FOUND)
|
|
upload_status, token = upload_dup_file(rest_obj, module)
|
|
if upload_status:
|
|
report_payload = get_dup_applicability_payload(token, device_ids=device_ids, group_ids=group_ids,
|
|
baseline_ids=baseline_ids)
|
|
if report_payload:
|
|
target_data = get_applicable_components(rest_obj, report_payload, module)
|
|
return target_data
|
|
|
|
|
|
def baseline_based_update(rest_obj, module, baseline, dev_comp_map):
|
|
compliance_uri = COMPLIANCE_URI.format(baseline["baseline_id"])
|
|
resp = rest_obj.get_all_report_details(compliance_uri)
|
|
compliance_report_list = []
|
|
update_actions = ["UPGRADE", "DOWNGRADE"]
|
|
if resp["report_list"]:
|
|
comps = []
|
|
if not dev_comp_map:
|
|
comps = module.params.get('components')
|
|
dev_comp_map = dict([(str(dev["DeviceId"]), comps) for dev in resp["report_list"]])
|
|
for dvc in resp["report_list"]:
|
|
dev_id = dvc["DeviceId"]
|
|
if str(dev_id) in dev_comp_map:
|
|
comps = dev_comp_map.get(str(dev_id), [])
|
|
compliance_report = dvc.get("ComponentComplianceReports")
|
|
if compliance_report is not None:
|
|
data_dict = {}
|
|
comp_list = []
|
|
if not comps:
|
|
comp_list = list(icomp["SourceName"] for icomp in compliance_report
|
|
if icomp["UpdateAction"] in update_actions)
|
|
else:
|
|
comp_list = list(icomp["SourceName"] for icomp in compliance_report
|
|
if ((icomp["UpdateAction"] in update_actions) and
|
|
(icomp.get('Name') in comps))) # regex filtering ++
|
|
if comp_list:
|
|
data_dict["Id"] = dev_id
|
|
data_dict["Data"] = str(";").join(comp_list)
|
|
data_dict["TargetType"] = {"Id": dvc['DeviceTypeId'], "Name": dvc["DeviceTypeName"]}
|
|
compliance_report_list.append(data_dict)
|
|
else:
|
|
module.fail_json(msg=COMPLIANCE_READ_FAIL)
|
|
if not compliance_report_list:
|
|
module.exit_json(msg=NO_CHANGES_MSG)
|
|
if module.check_mode:
|
|
module.exit_json(msg=CHANGES_FOUND)
|
|
return compliance_report_list
|
|
|
|
|
|
def _validate_device_attributes(module):
|
|
device_id_tags = []
|
|
service_tag = module.params.get('device_service_tag')
|
|
device_id = module.params.get('device_id')
|
|
devices = module.params.get('devices')
|
|
if devices:
|
|
for dev in devices:
|
|
if dev.get('id'):
|
|
device_id_tags.append(dev.get('id'))
|
|
else:
|
|
device_id_tags.append(dev.get('service_tag'))
|
|
if device_id is not None:
|
|
device_id_tags.extend(device_id)
|
|
if service_tag is not None:
|
|
device_id_tags.extend(service_tag)
|
|
return device_id_tags
|
|
|
|
|
|
def get_device_component_map(rest_obj, module):
|
|
device_id_tags = _validate_device_attributes(module)
|
|
device_ids, id_tag_map = get_device_ids(rest_obj, module, device_id_tags)
|
|
comps = module.params.get('components')
|
|
dev_comp_map = {}
|
|
if device_ids:
|
|
dev_comp_map = dict([(dev, comps) for dev in device_ids])
|
|
devices = module.params.get('devices')
|
|
if devices:
|
|
for dev in devices:
|
|
if dev.get('id'):
|
|
dev_comp_map[str(dev.get('id'))] = dev.get('components')
|
|
else:
|
|
id = list(id_tag_map.keys())[list(id_tag_map.values()).index(dev.get('service_tag'))]
|
|
dev_comp_map[str(id)] = dev.get('components')
|
|
return dev_comp_map
|
|
|
|
|
|
def validate_inputs(module):
|
|
param = module.params
|
|
if param.get("dup_file"):
|
|
if not any([param.get("device_id"), param.get("device_service_tag"), param.get("device_group_names")]):
|
|
module.fail_json(msg=DUP_REQ_MSG)
|
|
|
|
|
|
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},
|
|
"device_service_tag": {"type": "list", "elements": 'str'},
|
|
"device_id": {"type": "list", "elements": 'int'},
|
|
"dup_file": {"type": "path"},
|
|
"device_group_names": {"type": "list", "elements": 'str'},
|
|
"components": {"type": "list", "elements": 'str', "default": []},
|
|
"baseline_name": {"type": "str"},
|
|
"schedule": {"type": 'str', "choices": ['RebootNow', 'StageForNextReboot'], "default": 'RebootNow'},
|
|
"devices": {
|
|
"type": 'list', "elements": 'dict',
|
|
"options": {
|
|
"id": {'type': 'int'},
|
|
"service_tag": {"type": 'str'},
|
|
"components": {"type": "list", "elements": 'str', "default": []},
|
|
},
|
|
"mutually_exclusive": [('id', 'service_tag')],
|
|
"required_one_of": [('id', 'service_tag')]
|
|
},
|
|
},
|
|
required_one_of=[["dup_file", "baseline_name"]],
|
|
mutually_exclusive=[
|
|
["baseline_name", "dup_file"],
|
|
["device_group_names", "device_id", "devices"],
|
|
["device_group_names", "device_service_tag", "devices"],
|
|
["baseline_name", "device_group_names"],
|
|
["dup_file", "components", "devices"]],
|
|
supports_check_mode=True
|
|
)
|
|
validate_inputs(module)
|
|
update_status, baseline_details = {}, None
|
|
try:
|
|
with RestOME(module.params, req_session=True) as rest_obj:
|
|
if module.params.get("baseline_name"):
|
|
baseline_details = get_baseline_ids(rest_obj, module)
|
|
device_comp_map = get_device_component_map(rest_obj, module)
|
|
target_data = baseline_based_update(rest_obj, module, baseline_details, device_comp_map)
|
|
else:
|
|
target_data = single_dup_update(rest_obj, module)
|
|
job_payload = job_payload_for_update(rest_obj, module, target_data, baseline=baseline_details)
|
|
update_status = spawn_update_job(rest_obj, job_payload)
|
|
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) as err:
|
|
module.fail_json(msg=str(err))
|
|
module.exit_json(msg="Successfully submitted the firmware update job.", update_status=update_status, changed=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|