650 lines
30 KiB
Python
650 lines
30 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
# Dell EMC OpenManage Ansible Modules
|
|
# Version 4.0.0
|
|
# Copyright (C) 2018-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: idrac_firmware
|
|
short_description: Firmware update from a repository on a network share (CIFS, NFS, HTTP, HTTPS, FTP)
|
|
version_added: "2.1.0"
|
|
description:
|
|
- Update the Firmware by connecting to a network share (CIFS, NFS, HTTP, HTTPS, FTP) that contains a catalog of
|
|
available updates.
|
|
- Network share should contain a valid repository of Update Packages (DUPs) and a catalog file describing the DUPs.
|
|
- All applicable updates contained in the repository are applied to the system.
|
|
- This feature is available only with iDRAC Enterprise License.
|
|
extends_documentation_fragment:
|
|
- dellemc.openmanage.idrac_auth_options
|
|
options:
|
|
share_name:
|
|
description: Network share path of update repository. CIFS, NFS, HTTP, HTTPS and FTP share types are supported.
|
|
type: str
|
|
required: True
|
|
share_user:
|
|
description: Network share user in the format 'user@domain' or 'domain\\user' if user is
|
|
part of a domain else 'user'. This option is mandatory for CIFS Network Share.
|
|
type: str
|
|
share_password:
|
|
description: Network share user password. This option is mandatory for CIFS Network Share.
|
|
type: str
|
|
aliases: ['share_pwd']
|
|
share_mnt:
|
|
description:
|
|
- Local mount path of the network share with read-write permission for ansible user.
|
|
- This option is not applicable for HTTP, HTTPS, and FTP shares.
|
|
type: str
|
|
job_wait:
|
|
description: Whether to wait for job completion or not.
|
|
type: bool
|
|
default: True
|
|
catalog_file_name:
|
|
description: Catalog file name relative to the I(share_name).
|
|
type: str
|
|
default: 'Catalog.xml'
|
|
ignore_cert_warning:
|
|
description: Specifies if certificate warnings are ignored when HTTPS share is used.
|
|
If C(True) option is set, then the certificate warnings are ignored.
|
|
type: bool
|
|
default: True
|
|
apply_update:
|
|
description:
|
|
- If I(apply_update) is set to C(True), then the packages are applied.
|
|
- If I(apply_update) is set to C(False), no updates are applied, and a catalog report
|
|
of packages is generated and returned.
|
|
type: bool
|
|
default: True
|
|
reboot:
|
|
description:
|
|
- Provides the option to apply the update packages immediately or in the next reboot.
|
|
- If I(reboot) is set to C(True), then the packages are applied immediately.
|
|
- If I(reboot) is set to C(False), then the packages are staged and applied in the next reboot.
|
|
- Packages that do not require a reboot are applied immediately irrespective of I (reboot).
|
|
type: bool
|
|
default: False
|
|
|
|
requirements:
|
|
- "omsdk"
|
|
- "python >= 2.7.5"
|
|
author:
|
|
- "Rajeev Arakkal (@rajeevarakkal)"
|
|
- "Felix Stephen (@felixs88)"
|
|
notes:
|
|
- Run this module from a system that has direct access to DellEMC iDRAC.
|
|
- Module will report success based on the iDRAC firmware update parent job status if there are no individual
|
|
component jobs present.
|
|
- For server with iDRAC firmware 5.00.00.00 and later, if the repository contains unsupported packages, then the
|
|
module will return success with a proper message.
|
|
- This module supports C(check_mode).
|
|
'''
|
|
|
|
EXAMPLES = """
|
|
---
|
|
- name: Update firmware from repository on a NFS Share
|
|
dellemc.openmanage.idrac_firmware:
|
|
idrac_ip: "192.168.0.1"
|
|
idrac_user: "user_name"
|
|
idrac_password: "user_password"
|
|
share_name: "192.168.0.0:/share"
|
|
reboot: True
|
|
job_wait: True
|
|
apply_update: True
|
|
catalog_file_name: "Catalog.xml"
|
|
|
|
- name: Update firmware from repository on a CIFS Share
|
|
dellemc.openmanage.idrac_firmware:
|
|
idrac_ip: "192.168.0.1"
|
|
idrac_user: "user_name"
|
|
idrac_password: "user_password"
|
|
share_name: "full_cifs_path"
|
|
share_user: "share_user"
|
|
share_password: "share_password"
|
|
reboot: True
|
|
job_wait: True
|
|
apply_update: True
|
|
catalog_file_name: "Catalog.xml"
|
|
|
|
- name: Update firmware from repository on a HTTP
|
|
dellemc.openmanage.idrac_firmware:
|
|
idrac_ip: "192.168.0.1"
|
|
idrac_user: "user_name"
|
|
idrac_password: "user_password"
|
|
share_name: "http://downloads.dell.com"
|
|
reboot: True
|
|
job_wait: True
|
|
apply_update: True
|
|
|
|
- name: Update firmware from repository on a HTTPS
|
|
dellemc.openmanage.idrac_firmware:
|
|
idrac_ip: "192.168.0.1"
|
|
idrac_user: "user_name"
|
|
idrac_password: "user_password"
|
|
share_name: "https://downloads.dell.com"
|
|
reboot: True
|
|
job_wait: True
|
|
apply_update: True
|
|
|
|
- name: Update firmware from repository on a FTP
|
|
dellemc.openmanage.idrac_firmware:
|
|
idrac_ip: "192.168.0.1"
|
|
idrac_user: "user_name"
|
|
idrac_password: "user_password"
|
|
share_name: "ftp://ftp.dell.com"
|
|
reboot: True
|
|
job_wait: True
|
|
apply_update: True
|
|
"""
|
|
|
|
RETURN = """
|
|
---
|
|
msg:
|
|
type: str
|
|
description: Overall firmware update status.
|
|
returned: always
|
|
sample: "Successfully updated the firmware."
|
|
update_status:
|
|
type: dict
|
|
description: Firmware Update job and progress details from the iDRAC.
|
|
returned: success
|
|
sample: {
|
|
'InstanceID': 'JID_XXXXXXXXXXXX',
|
|
'JobState': 'Completed',
|
|
'Message': 'Job completed successfully.',
|
|
'MessageId': 'REDXXX',
|
|
'Name': 'Repository Update',
|
|
'JobStartTime': 'NA',
|
|
'Status': 'Success',
|
|
}
|
|
"""
|
|
|
|
|
|
import os
|
|
import re
|
|
import json
|
|
import time
|
|
from ssl import SSLError
|
|
from xml.etree import ElementTree as ET
|
|
from ansible_collections.dellemc.openmanage.plugins.module_utils.dellemc_idrac import iDRACConnection
|
|
from ansible_collections.dellemc.openmanage.plugins.module_utils.idrac_redfish import iDRACRedfishAPI
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.six.moves.urllib.parse import urlparse
|
|
from ansible.module_utils.urls import open_url, ConnectionError, SSLValidationError
|
|
from ansible.module_utils.six.moves.urllib.error import URLError, HTTPError
|
|
try:
|
|
from omsdk.sdkcreds import UserCredentials
|
|
from omsdk.sdkfile import FileOnShare
|
|
from omsdk.http.sdkwsmanbase import WsManProtocolBase
|
|
HAS_OMSDK = True
|
|
except ImportError:
|
|
HAS_OMSDK = False
|
|
|
|
SHARE_TYPE = {'nfs': 'NFS', 'cifs': 'CIFS', 'ftp': 'FTP',
|
|
'http': 'HTTP', 'https': 'HTTPS', 'tftp': 'TFTP'}
|
|
CERT_WARN = {True: 'On', False: 'Off'}
|
|
IDRAC_PATH = "/redfish/v1/Managers/iDRAC.Embedded.1"
|
|
PATH = "/redfish/v1/Dell/Systems/System.Embedded.1/DellSoftwareInstallationService/Actions/" \
|
|
"DellSoftwareInstallationService.InstallFromRepository"
|
|
GET_REPO_BASED_UPDATE_LIST_PATH = "/redfish/v1/Dell/Systems/System.Embedded.1/DellSoftwareInstallationService/" \
|
|
"Actions/DellSoftwareInstallationService.GetRepoBasedUpdateList"
|
|
JOB_URI = "/redfish/v1/JobService/Jobs/{job_id}"
|
|
iDRAC_JOB_URI = "/redfish/v1/Managers/iDRAC.Embedded.1/Jobs/{job_id}"
|
|
MESSAGE = "Firmware versions on server match catalog, applicable updates are not present in the repository."
|
|
EXIT_MESSAGE = "The catalog in the repository specified in the operation has the same firmware versions " \
|
|
"as currently present on the server."
|
|
IDEM_MSG_ID = "SUP029"
|
|
REDFISH_VERSION = "3.30"
|
|
INTERVAL = 30 # polling interval
|
|
WAIT_COUNT = 240
|
|
JOB_WAIT_MSG = 'Job wait timed out after {0} minutes'
|
|
|
|
|
|
def wait_for_job_completion(module, job_uri, job_wait=False, reboot=False, apply_update=False):
|
|
track_counter = 0
|
|
response = {}
|
|
msg = None
|
|
while track_counter < 5:
|
|
try:
|
|
# For job_wait False return a valid response, try 5 times
|
|
with iDRACRedfishAPI(module.params) as redfish:
|
|
response = redfish.invoke_request(job_uri, "GET")
|
|
track_counter += 5
|
|
msg = None
|
|
except (SSLError, URLError, ConnectionError, HTTPError) as error_message:
|
|
msg = str(error_message)
|
|
track_counter += 1
|
|
time.sleep(10)
|
|
if track_counter < 5:
|
|
msg = None
|
|
# reset track counter
|
|
track_counter = 0
|
|
while job_wait and track_counter <= WAIT_COUNT:
|
|
try:
|
|
with iDRACRedfishAPI(module.params) as redfish:
|
|
response = redfish.invoke_request(job_uri, "GET")
|
|
job_state = response.json_data.get("JobState")
|
|
msg = None
|
|
except (SSLError, URLError, ConnectionError, HTTPError) as error_message:
|
|
track_counter += 1
|
|
time.sleep(INTERVAL)
|
|
else:
|
|
if response.json_data.get("PercentComplete") == 100 and job_state == "Completed": # apply now
|
|
break
|
|
if job_state in ["Starting", "Running", "Pending", "New"] and not reboot and apply_update: # apply on
|
|
break
|
|
track_counter += 1
|
|
time.sleep(INTERVAL)
|
|
if track_counter > WAIT_COUNT:
|
|
# TIMED OUT
|
|
msg = JOB_WAIT_MSG.format((WAIT_COUNT * INTERVAL) / 60)
|
|
return response, msg
|
|
|
|
|
|
def _validate_catalog_file(catalog_file_name):
|
|
normilized_file_name = catalog_file_name.lower()
|
|
if not normilized_file_name:
|
|
raise ValueError('catalog_file_name should be a non-empty string.')
|
|
elif not normilized_file_name.endswith("xml"):
|
|
raise ValueError('catalog_file_name should be an XML file.')
|
|
|
|
|
|
def get_check_mode_status(status, module):
|
|
if status['job_details']["Data"]["GetRepoBasedUpdateList_OUTPUT"].get("Message") == MESSAGE.rstrip(".") and \
|
|
status.get('JobStatus') == "Completed":
|
|
if module.check_mode:
|
|
module.exit_json(msg="No changes found to commit!")
|
|
module.exit_json(msg=EXIT_MESSAGE)
|
|
|
|
|
|
def get_job_status(module, each_comp, idrac):
|
|
failed, each_comp['JobStatus'], each_comp['Message'] = False, None, None
|
|
job_wait = module.params['job_wait']
|
|
reboot = module.params['reboot']
|
|
apply_update = module.params['apply_update']
|
|
if each_comp.get("JobID") is not None:
|
|
if idrac:
|
|
resp = idrac.job_mgr.job_wait(each_comp.get("JobID"))
|
|
while reboot and apply_update:
|
|
resp = idrac.job_mgr.job_wait(each_comp.get("JobID"))
|
|
if resp.get("JobStatus") is not None and (not resp.get('JobStatus') == "Scheduled"):
|
|
break
|
|
# module.warn("omsdk job wait 315: "+json.dumps(resp))
|
|
each_comp['Message'] = resp.get('Message')
|
|
each_comp['JobStatus'] = "OK"
|
|
fail_words_lower = ['fail', 'invalid', 'unable', 'not', 'cancel']
|
|
if any(x in resp.get('Message').lower() for x in fail_words_lower):
|
|
each_comp['JobStatus'] = "Critical"
|
|
failed = True
|
|
else:
|
|
resp, msg = wait_for_job_completion(module, JOB_URI.format(job_id=each_comp.get("JobID")), job_wait, reboot,
|
|
apply_update)
|
|
if not msg:
|
|
resp_data = resp.json_data
|
|
if resp_data.get('Messages'):
|
|
each_comp['Message'] = resp_data.get('Messages')[0]['Message']
|
|
each_comp['JobStatus'] = resp_data.get('JobStatus')
|
|
if each_comp['JobStatus'] == "Critical":
|
|
failed = True
|
|
else:
|
|
failed = True
|
|
return each_comp, failed
|
|
|
|
|
|
def _convert_xmltojson(module, job_details, idrac):
|
|
"""get all the xml data from PackageList and returns as valid json."""
|
|
data, repo_status, failed_status = [], False, False
|
|
try:
|
|
xmldata = ET.fromstring(job_details['PackageList'])
|
|
for iname in xmldata.iter('INSTANCENAME'):
|
|
comp_data = dict([(attr.attrib['NAME'], txt.text) for attr in iname.iter("PROPERTY") for txt in attr])
|
|
component, failed = get_job_status(module, comp_data, idrac)
|
|
# get the any single component update failure and record the only very first failure on failed_status True
|
|
if not failed_status and failed:
|
|
failed_status = True
|
|
data.append(component)
|
|
repo_status = True
|
|
except ET.ParseError:
|
|
data = job_details['PackageList']
|
|
return data, repo_status, failed_status
|
|
|
|
|
|
def get_jobid(module, resp):
|
|
"""Get the Job ID from the response header."""
|
|
jobid = None
|
|
if resp.status_code == 202:
|
|
joburi = resp.headers.get('Location')
|
|
if joburi is None:
|
|
module.fail_json(msg="Failed to update firmware.")
|
|
jobid = joburi.split("/")[-1]
|
|
else:
|
|
module.fail_json(msg="Failed to update firmware.")
|
|
return jobid
|
|
|
|
|
|
def handle_HTTP_error(module, httperr):
|
|
err_message = json.load(httperr)
|
|
err_list = err_message.get('error', {}).get('@Message.ExtendedInfo', [{"Message": EXIT_MESSAGE}])
|
|
if err_list:
|
|
err_reason = err_list[0].get("Message", EXIT_MESSAGE)
|
|
if IDEM_MSG_ID in err_list[0].get('MessageId'):
|
|
module.exit_json(msg=err_reason)
|
|
if "error" in err_message:
|
|
module.fail_json(msg=err_message)
|
|
|
|
|
|
def update_firmware_url_redfish(module, idrac, share_name, catalog_file_name, apply_update, reboot,
|
|
ignore_cert_warning, job_wait, payload):
|
|
"""Update firmware through HTTP/HTTPS/FTP and return the job details."""
|
|
repo_url = urlparse(share_name)
|
|
job_details, status = {}, {}
|
|
ipaddr = repo_url.netloc
|
|
share_type = repo_url.scheme
|
|
sharename = repo_url.path.strip('/')
|
|
payload['IPAddress'] = ipaddr
|
|
if repo_url.path:
|
|
payload['ShareName'] = sharename
|
|
payload['ShareType'] = SHARE_TYPE[share_type]
|
|
resp = idrac.invoke_request(PATH, method="POST", data=payload)
|
|
job_id = get_jobid(module, resp)
|
|
resp, msg = wait_for_job_completion(module, JOB_URI.format(job_id=job_id), job_wait, reboot, apply_update)
|
|
if not msg:
|
|
status = resp.json_data
|
|
else:
|
|
status['update_msg'] = msg
|
|
try:
|
|
resp_repo_based_update_list = idrac.invoke_request(GET_REPO_BASED_UPDATE_LIST_PATH, method="POST", data="{}",
|
|
dump=False)
|
|
job_details = resp_repo_based_update_list.json_data
|
|
except HTTPError as err:
|
|
handle_HTTP_error(module, err)
|
|
raise err
|
|
return status, job_details
|
|
|
|
|
|
def update_firmware_url_omsdk(module, idrac, share_name, catalog_file_name, apply_update, reboot,
|
|
ignore_cert_warning, job_wait, payload):
|
|
"""Update firmware through HTTP/HTTPS/FTP and return the job details."""
|
|
repo_url = urlparse(share_name)
|
|
job_details, status = {}, {}
|
|
ipaddr = repo_url.netloc
|
|
share_type = repo_url.scheme
|
|
sharename = repo_url.path.strip('/')
|
|
if ipaddr == "downloads.dell.com":
|
|
status = idrac.update_mgr.update_from_dell_repo_url(ipaddress=ipaddr, share_type=share_type,
|
|
share_name=sharename, catalog_file=catalog_file_name,
|
|
apply_update=apply_update, reboot_needed=reboot,
|
|
ignore_cert_warning=ignore_cert_warning, job_wait=job_wait)
|
|
# module.warn(json.dumps(status))
|
|
get_check_mode_status(status, module)
|
|
else:
|
|
status = idrac.update_mgr.update_from_repo_url(ipaddress=ipaddr, share_type=share_type,
|
|
share_name=sharename, catalog_file=catalog_file_name,
|
|
apply_update=apply_update, reboot_needed=reboot,
|
|
ignore_cert_warning=ignore_cert_warning, job_wait=job_wait)
|
|
get_check_mode_status(status, module)
|
|
return status, job_details
|
|
|
|
|
|
def update_firmware_omsdk(idrac, module):
|
|
"""Update firmware from a network share and return the job details."""
|
|
msg = {}
|
|
msg['changed'], msg['failed'], msg['update_status'] = False, False, {}
|
|
msg['update_msg'] = "Successfully triggered the job to update the firmware."
|
|
try:
|
|
share_name = module.params['share_name']
|
|
catalog_file_name = module.params['catalog_file_name']
|
|
share_user = module.params['share_user']
|
|
share_pwd = module.params['share_password']
|
|
reboot = module.params['reboot']
|
|
job_wait = module.params['job_wait']
|
|
ignore_cert_warning = module.params['ignore_cert_warning']
|
|
apply_update = module.params['apply_update']
|
|
payload = {"RebootNeeded": reboot, "CatalogFile": catalog_file_name, "ApplyUpdate": str(apply_update),
|
|
"IgnoreCertWarning": CERT_WARN[ignore_cert_warning]}
|
|
if share_user is not None:
|
|
payload['UserName'] = share_user
|
|
if share_pwd is not None:
|
|
payload['Password'] = share_pwd
|
|
|
|
if share_name.lower().startswith(('http://', 'https://', 'ftp://')):
|
|
msg['update_status'], job_details = update_firmware_url_omsdk(module, idrac, share_name, catalog_file_name,
|
|
apply_update, reboot, ignore_cert_warning,
|
|
job_wait, payload)
|
|
if job_details:
|
|
msg['update_status']['job_details'] = job_details
|
|
else:
|
|
upd_share = FileOnShare(remote="{0}{1}{2}".format(share_name, os.sep, catalog_file_name),
|
|
mount_point=module.params['share_mnt'], isFolder=False,
|
|
creds=UserCredentials(share_user, share_pwd))
|
|
msg['update_status'] = idrac.update_mgr.update_from_repo(upd_share, apply_update=apply_update,
|
|
reboot_needed=reboot, job_wait=job_wait)
|
|
get_check_mode_status(msg['update_status'], module)
|
|
|
|
json_data, repo_status, failed = msg['update_status']['job_details'], False, False
|
|
if "PackageList" not in json_data:
|
|
job_data = json_data.get('Data')
|
|
pkglst = job_data['body'] if 'body' in job_data else job_data.get('GetRepoBasedUpdateList_OUTPUT')
|
|
if 'PackageList' in pkglst: # Returns from OMSDK
|
|
pkglst['PackageList'], repo_status, failed = _convert_xmltojson(module, pkglst, idrac)
|
|
else: # Redfish
|
|
json_data['PackageList'], repo_status, failed = _convert_xmltojson(module, json_data, None)
|
|
|
|
if not apply_update and not failed:
|
|
msg['update_msg'] = "Successfully fetched the applicable firmware update package list."
|
|
elif apply_update and not reboot and not job_wait and not failed:
|
|
msg['update_msg'] = "Successfully triggered the job to stage the firmware."
|
|
elif apply_update and job_wait and not reboot and not failed:
|
|
msg['update_msg'] = "Successfully staged the applicable firmware update packages."
|
|
msg['changed'] = True
|
|
elif apply_update and job_wait and not reboot and failed:
|
|
msg['update_msg'] = "Successfully staged the applicable firmware update packages with error(s)."
|
|
msg['failed'] = True
|
|
|
|
except RuntimeError as e:
|
|
module.fail_json(msg=str(e))
|
|
|
|
if module.check_mode and not (json_data.get('PackageList') or json_data.get('Data')) and \
|
|
msg['update_status']['JobStatus'] == 'Completed':
|
|
module.exit_json(msg="No changes found to commit!")
|
|
elif module.check_mode and (json_data.get('PackageList') or json_data.get('Data')) and \
|
|
msg['update_status']['JobStatus'] == 'Completed':
|
|
module.exit_json(msg="Changes found to commit!", changed=True,
|
|
update_status=msg['update_status'])
|
|
elif module.check_mode and not msg['update_status']['JobStatus'] == 'Completed':
|
|
msg['update_status'].pop('job_details')
|
|
module.fail_json(msg="Unable to complete the firmware repository download.",
|
|
update_status=msg['update_status'])
|
|
elif not module.check_mode and "Status" in msg['update_status']:
|
|
if msg['update_status']['Status'] in ["Success", "InProgress"]:
|
|
if module.params['job_wait'] and module.params['apply_update'] and module.params['reboot'] and (
|
|
'job_details' in msg['update_status'] and repo_status) and not failed:
|
|
msg['changed'] = True
|
|
msg['update_msg'] = "Successfully updated the firmware."
|
|
elif module.params['job_wait'] and module.params['apply_update'] and module.params['reboot'] and (
|
|
'job_details' in msg['update_status'] and repo_status) and failed:
|
|
msg['failed'], msg['changed'] = True, False
|
|
msg['update_msg'] = "Firmware update failed."
|
|
else:
|
|
failed_msg = "Firmware update failed."
|
|
if not apply_update:
|
|
failed_msg = "Unable to complete the repository update."
|
|
module.fail_json(msg=failed_msg, update_status=msg['update_status'])
|
|
return msg
|
|
|
|
|
|
def update_firmware_redfish(idrac, module):
|
|
"""Update firmware from a network share and return the job details."""
|
|
msg = {}
|
|
msg['changed'], msg['failed'] = False, False
|
|
msg['update_msg'] = "Successfully triggered the job to update the firmware."
|
|
try:
|
|
share_name = module.params['share_name']
|
|
catalog_file_name = module.params['catalog_file_name']
|
|
share_user = module.params['share_user']
|
|
share_pwd = module.params['share_password']
|
|
reboot = module.params['reboot']
|
|
job_wait = module.params['job_wait']
|
|
ignore_cert_warning = module.params['ignore_cert_warning']
|
|
apply_update = module.params['apply_update']
|
|
payload = {"RebootNeeded": reboot, "CatalogFile": catalog_file_name, "ApplyUpdate": str(apply_update),
|
|
"IgnoreCertWarning": CERT_WARN[ignore_cert_warning]}
|
|
if share_user is not None:
|
|
payload['UserName'] = share_user
|
|
if share_pwd is not None:
|
|
payload['Password'] = share_pwd
|
|
|
|
if share_name.lower().startswith(('http://', 'https://', 'ftp://')):
|
|
msg['update_status'], job_details = update_firmware_url_redfish(module, idrac, share_name, catalog_file_name,
|
|
apply_update, reboot, ignore_cert_warning,
|
|
job_wait, payload)
|
|
if job_details:
|
|
msg['update_status']['job_details'] = job_details
|
|
else:
|
|
if share_name.startswith('\\\\'):
|
|
cifs = share_name.split('\\')
|
|
payload['IPAddress'] = cifs[2]
|
|
payload['ShareName'] = '\\'.join(cifs[3:])
|
|
payload['ShareType'] = 'CIFS'
|
|
else:
|
|
nfs = urlparse(share_name)
|
|
payload['IPAddress'] = nfs.scheme
|
|
payload['ShareName'] = nfs.path.strip('/')
|
|
payload['ShareType'] = 'NFS'
|
|
resp = idrac.invoke_request(PATH, method="POST", data=payload)
|
|
job_id = get_jobid(module, resp)
|
|
resp, mesg = wait_for_job_completion(module, JOB_URI.format(job_id=job_id), job_wait, reboot, apply_update)
|
|
if not mesg:
|
|
msg['update_status'] = resp.json_data
|
|
else:
|
|
msg['update_status'] = mesg
|
|
try:
|
|
repo_based_update_list = idrac.invoke_request(GET_REPO_BASED_UPDATE_LIST_PATH, method="POST",
|
|
data="{}", dump=False)
|
|
msg['update_status']['job_details'] = repo_based_update_list.json_data
|
|
except HTTPError as err:
|
|
handle_HTTP_error(module, err)
|
|
raise err
|
|
json_data, repo_status, failed = msg['update_status']['job_details'], False, False
|
|
if "PackageList" not in json_data:
|
|
job_data = json_data.get('Data')
|
|
pkglst = job_data['body'] if 'body' in job_data else job_data.get('GetRepoBasedUpdateList_OUTPUT')
|
|
if 'PackageList' in pkglst:
|
|
pkglst['PackageList'], repo_status, failed = _convert_xmltojson(module, pkglst, idrac)
|
|
else:
|
|
json_data['PackageList'], repo_status, failed = _convert_xmltojson(module, json_data, None)
|
|
|
|
if not apply_update and not failed:
|
|
msg['update_msg'] = "Successfully fetched the applicable firmware update package list."
|
|
elif apply_update and not reboot and not job_wait and not failed:
|
|
msg['update_msg'] = "Successfully triggered the job to stage the firmware."
|
|
elif apply_update and job_wait and not reboot and not failed:
|
|
msg['update_msg'] = "Successfully staged the applicable firmware update packages."
|
|
msg['changed'] = True
|
|
elif apply_update and job_wait and not reboot and failed:
|
|
msg['update_msg'] = "Successfully staged the applicable firmware update packages with error(s)."
|
|
msg['failed'] = True
|
|
|
|
except RuntimeError as e:
|
|
module.fail_json(msg=str(e))
|
|
|
|
if module.check_mode and not (json_data.get('PackageList') or json_data.get('Data')) and \
|
|
msg['update_status']['JobStatus'] == 'OK':
|
|
module.exit_json(msg="No changes found to commit!")
|
|
elif module.check_mode and (json_data.get('PackageList') or json_data.get('Data')) and \
|
|
msg['update_status']['JobStatus'] == 'OK':
|
|
module.exit_json(msg="Changes found to commit!", changed=True,
|
|
update_status=msg['update_status'])
|
|
elif module.check_mode and not msg['update_status']['JobStatus'] == 'OK':
|
|
msg['update_status'].pop('job_details')
|
|
module.fail_json(msg="Unable to complete the firmware repository download.",
|
|
update_status=msg['update_status'])
|
|
elif not module.check_mode and "JobStatus" in msg['update_status']:
|
|
if not msg['update_status']['JobStatus'] == "Critical":
|
|
if module.params['job_wait'] and module.params['apply_update'] and module.params['reboot'] and \
|
|
('job_details' in msg['update_status'] and repo_status) and not failed:
|
|
msg['changed'] = True
|
|
msg['update_msg'] = "Successfully updated the firmware."
|
|
elif module.params['job_wait'] and module.params['apply_update'] and module.params['reboot'] and \
|
|
('job_details' in msg['update_status'] and repo_status) and failed:
|
|
msg['failed'], msg['changed'] = True, False
|
|
msg['update_msg'] = "Firmware update failed."
|
|
else:
|
|
failed_msg = "Firmware update failed."
|
|
if not apply_update:
|
|
failed_msg = "Unable to complete the repository update."
|
|
module.fail_json(msg=failed_msg, update_status=msg['update_status'])
|
|
return msg
|
|
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec={
|
|
"idrac_ip": {"required": True, "type": 'str'},
|
|
"idrac_user": {"required": True, "type": 'str'},
|
|
"idrac_password": {"required": True, "type": 'str', "aliases": ['idrac_pwd'], "no_log": True},
|
|
"idrac_port": {"required": False, "default": 443, "type": 'int'},
|
|
|
|
"share_name": {"required": True, "type": 'str'},
|
|
"share_user": {"required": False, "type": 'str'},
|
|
"share_password": {"required": False, "type": 'str', "aliases": ['share_pwd'], "no_log": True},
|
|
"share_mnt": {"required": False, "type": 'str'},
|
|
|
|
"catalog_file_name": {"required": False, "type": 'str', "default": "Catalog.xml"},
|
|
"reboot": {"required": False, "type": 'bool', "default": False},
|
|
"job_wait": {"required": False, "type": 'bool', "default": True},
|
|
"ignore_cert_warning": {"required": False, "type": 'bool', "default": True},
|
|
"apply_update": {"required": False, "type": 'bool', "default": True},
|
|
},
|
|
|
|
supports_check_mode=True)
|
|
|
|
redfish_check = False
|
|
try:
|
|
with iDRACRedfishAPI(module.params) as obj:
|
|
resp = obj.invoke_request(IDRAC_PATH, method="GET")
|
|
idrac_data = resp.json_data
|
|
if idrac_data:
|
|
firm_id = idrac_data.get("FirmwareVersion")
|
|
firmware_version = re.match(r"^\d.\d{2}", firm_id).group()
|
|
if float(firmware_version) >= float(REDFISH_VERSION):
|
|
redfish_check = True
|
|
except (HTTPError, RuntimeError, URLError, SSLValidationError, ConnectionError, KeyError, ImportError, ValueError,
|
|
TypeError, SSLError) as e:
|
|
redfish_check = False
|
|
|
|
try:
|
|
# Validate the catalog file
|
|
_validate_catalog_file(module.params['catalog_file_name'])
|
|
if module.check_mode:
|
|
module.params['apply_update'] = False
|
|
module.params['reboot'] = False
|
|
module.params['job_wait'] = True
|
|
# Connect to iDRAC and update firmware
|
|
if redfish_check:
|
|
with iDRACRedfishAPI(module.params) as redfish_obj:
|
|
status = update_firmware_redfish(redfish_obj, module)
|
|
else:
|
|
with iDRACConnection(module.params) as idrac:
|
|
status = update_firmware_omsdk(idrac, module)
|
|
except HTTPError as err:
|
|
module.fail_json(msg=str(err), update_status=json.load(err))
|
|
except (RuntimeError, URLError, SSLValidationError, ConnectionError, KeyError,
|
|
ImportError, ValueError, TypeError) as e:
|
|
module.fail_json(msg=str(e))
|
|
|
|
module.exit_json(msg=status['update_msg'], update_status=status['update_status'],
|
|
changed=status['changed'], failed=status['failed'])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|