Created
March 29, 2019 16:33
-
-
Save perzizzle/893a53bf6b5d0babed2da3e6f7dda157 to your computer and use it in GitHub Desktop.
Some python for using the redfish api to apply firmware
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Inspired by https://github.com/dell/iDRAC-Redfish-Scripting/blob/master/Redfish%20Python/DeviceFirmwareDellUpdateServiceREDFISH.py | |
| import requests | |
| import json | |
| import time | |
| def main(): | |
| module = AnsibleModule( | |
| argument_spec = dict( | |
| idrac_hostname = dict(type='str', required=True), | |
| idrac_username = dict(type='str', required=True), | |
| idrac_password = dict(type='str', required=True, no_log=True), | |
| firmware_file_path = dict(type='str', required=True), | |
| firmware_image = dict(type='str', required=True), | |
| install_option = dict(type='str', default="NowAndReboot", required=False), | |
| ), | |
| supports_check_mode=False | |
| ) | |
| idrac_hostname = module.params['idrac_hostname'] | |
| idrac_username = module.params['idrac_username'] | |
| idrac_password = module.params['idrac_password'] | |
| firmware_file_path = module.params['firmware_file_path'] | |
| firmware_image = module.params['firmware_image'] | |
| install_option = module.params['install_option'] | |
| changed = False | |
| job_id = None | |
| state = None | |
| status = None | |
| job_state = "unknown" | |
| result = {'changed': changed, 'job_id': job_id} | |
| def check_idrac_api_support(idrac_hostname, idrac_username, idrac_password): | |
| url = 'https://{0}/redfish/v1/UpdateService/FirmwareInventory/'.format(idrac_hostname) | |
| try: | |
| response = requests.get(url, auth=(idrac_username, idrac_password), verify=False) | |
| response.raise_for_status() | |
| except Exception as e: | |
| module.fail_json(msg=str(e)) | |
| #module.fail_json(msg="Current server {0} iDRAC version does not support Redfish firmware features".format(idrac_hostname)) | |
| def check_job_status(idrac_hostname, idrac_username, idrac_password, job_id): | |
| state = None | |
| status = None | |
| job_state = None | |
| url = 'https://{0}/redfish/v1/TaskService/Tasks/{1}'.format(idrac_hostname, job_id) | |
| try: | |
| response = requests.get(url, auth=(idrac_username, idrac_password), verify=False) | |
| response.raise_for_status() | |
| data = response.json() | |
| state = data["TaskState"] | |
| status = data["TaskStatus"] | |
| job_state = data["Oem"]["Dell"]["JobState"] | |
| # TaskState = [Pending, Completed, Completed with Errors, ... ] | |
| # TaskStatus = [OK, Critical ...] | |
| # JobState = [New, Scheduled] | |
| if "failed" in data["Messages"] or "completed with errors" in data["Messages"]: | |
| module.fail_json(msg="Job failed, current message is: {0}".format(data[u"Messages"])) | |
| elif data["TaskState"] == "Completed": | |
| done = True | |
| elif data["TaskState"] == "Completed with Errors" or data["TaskState"] == "Failed": | |
| module.fail_json(msg="Job failed, current message is: {0}".format(data["Messages"])) | |
| except Exception as e: | |
| module.fail_json(msg=str(e)) | |
| return state, status, job_state | |
| def download_image_payload(idrac_hostname, idrac_username, idrac_password, firmware_file_path, firmware_filename): | |
| url = 'https://{0}/redfish/v1/UpdateService/FirmwareInventory/'.format(idrac_hostname) | |
| req = requests.get(url, auth=(idrac_username, idrac_password), verify=False) | |
| etag = req.headers['ETag'] # TODO: what is etag? | |
| image_path = "{0}/{1}".format(firmware_file_path, firmware_filename) | |
| url = 'https://{0}/redfish/v1/UpdateService/FirmwareInventory'.format(idrac_hostname) | |
| files = {'file': (firmware_filename, open(image_path, 'rb'), 'multipart/form-data')} | |
| headers = {"if-match": etag} | |
| try: | |
| response = requests.post(url, files=files, auth=(idrac_username, idrac_password), verify=False, headers=headers) | |
| response.raise_for_status() | |
| firmware_version = response.json()['Id'] | |
| except Exception as e: | |
| error = json.loads(response.text) | |
| if error['error']['@Message.ExtendedInfo'][0]['Severity'] == 'Critical': | |
| module.fail_json(msg="Failed to download image payload", error=response.text, exception = str(e.__class__.__name__)) | |
| location = response.headers['Location'] # TODO: why? | |
| return location, firmware_version | |
| def install_image_payload(idrac_hostname, idrac_username, idrac_password, install_option, location): | |
| url = 'https://{0}/redfish/v1/UpdateService/Actions/Oem/DellUpdateService.Install'.format(idrac_hostname) | |
| payload = {'SoftwareIdentityURIs': [location], 'InstallUpon': install_option} | |
| headers = {'content-type': 'application/json'} | |
| try: | |
| response = requests.post(url, json=payload, auth = (idrac_username, idrac_password), verify=False, headers=headers) | |
| response.raise_for_status() | |
| job_id_location = response.headers['Location'] | |
| job_id = re.search("JID_.+",job_id_location).group() | |
| except Exception as e: | |
| module.fail_json(msg=str(e)) | |
| return job_id | |
| check_idrac_api_support(idrac_hostname, idrac_username, idrac_password) | |
| location, firmware_version = download_image_payload(idrac_hostname, idrac_username, idrac_password, firmware_file_path, firmware_image) | |
| job_id = install_image_payload(idrac_hostname, idrac_username, idrac_password, install_option, location) | |
| counter = 0 | |
| while job_state.lower() not in [ "scheduled", "completed"]: | |
| state, status, job_state = check_job_status(idrac_hostname, idrac_username, idrac_password, job_id) | |
| counter = counter + 1 | |
| time.sleep(10) | |
| if counter > 30: # 5 minutes | |
| module.fail_json(msg="Job state: {0} Job id: {1} has not been scheduled within 5 minutes ".format(job_state, job_id)) | |
| result['changed'] = True | |
| result['job_id'] = str(job_id) | |
| result['job_state'] = str(job_state) | |
| module.exit_json(**result) | |
| # import module snippets | |
| from ansible.module_utils.basic import * | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment