Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
ansible / dellemc / openmanage / plugins / modules / redfish_firmware.py
Size: Mime:
#!/usr/bin/python
# -*- coding: utf-8 -*-

#
# Dell EMC OpenManage Ansible Modules
# Version 5.0.1
# Copyright (C) 2019-2022 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: redfish_firmware
short_description: To perform a component firmware update using the image file available on the local or remote system
version_added: "2.1.0"
description:
    - This module allows the firmware update of only one component at a time.
      If the module is run for more than one component, an error message is returned.
    - Depending on the component, the firmware update is applied after an automatic or manual reboot.
extends_documentation_fragment:
  - dellemc.openmanage.redfish_auth_options
options:
    image_uri:
        description:
            - Firmware Image location URI or local path.
            - For example- U(http://<web_address>/components.exe) or /home/firmware_repo/component.exe.
        type: str
        required: True
    transfer_protocol:
        description: Protocol used to transfer the firmware image file. Applicable for URI based update.
        type: str
        default: HTTP
        choices: ["CIFS", "FTP", "HTTP", "HTTPS", "NSF", "OEM", "SCP", "SFTP", "TFTP"]
requirements:
    - "python >= 3.8.6"
    - "urllib3"
author:
    - "Felix Stephen (@felixs88)"
notes:
    - Run this module from a system that has direct access to Redfish APIs.
    - This module does not support C(check_mode).
"""

EXAMPLES = """
---
- name: Update the firmware from a single executable file available in a HTTP protocol
  dellemc.openmanage.redfish_firmware:
    baseuri: "192.168.0.1"
    username: "user_name"
    password: "user_password"
    ca_path: "/path/to/ca_cert.pem"
    image_uri: "http://192.168.0.2/firmware_repo/component.exe"
    transfer_protocol: "HTTP"

- name: Update the firmware from a single executable file available in a local path
  dellemc.openmanage.redfish_firmware:
    baseuri: "192.168.0.1"
    username: "user_name"
    password: "user_password"
    ca_path: "/path/to/ca_cert.pem"
    image_uri: "/home/firmware_repo/component.exe"
"""

RETURN = """
---
msg:
  description: Overall status of the firmware update task.
  returned: always
  type: str
  sample: Successfully submitted the firmware update task.
task:
  description: Returns ID and URI of the created task.
  returned: success
  type: dict
  sample: {
        "id": "JID_XXXXXXXXXXXX",
        "uri": "/redfish/v1/TaskService/Tasks/JID_XXXXXXXXXXXX"
    }
error_info:
  type: dict
  description: Details of http error.
  returned: on http error
  sample:  {
        "error": {
            "@Message.ExtendedInfo": [
                {
                    "Message": "Unable to complete the operation because the JSON data format entered is invalid.",
                    "Resolution": "Do the following and the retry the operation:
                        1) Enter the correct JSON data format and retry the operation.
                        2) Make sure that no syntax error is present in JSON data format.
                        3) Make sure that a duplicate key is not present in JSON data format.",
                    "Severity": "Critical"
                },
                {
                    "Message": "The request body submitted was malformed JSON and
                        could not be parsed by the receiving service.",
                    "Resolution": "Ensure that the request body is valid JSON and resubmit the request.",
                    "Severity": "Critical"
                }
            ],
            "code": "Base.1.2.GeneralError",
            "message": "A general error has occurred. See ExtendedInfo for more information."
        }
    }
"""


import json
import os
from ssl import SSLError
from ansible_collections.dellemc.openmanage.plugins.module_utils.redfish import Redfish, redfish_auth_params
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.urls import ConnectionError, SSLValidationError
from ansible.module_utils.six.moves.urllib.error import URLError, HTTPError

try:
    from urllib3.fields import RequestField
    from urllib3.filepost import encode_multipart_formdata
    HAS_LIB = True
except ImportError:
    HAS_LIB = False

UPDATE_SERVICE = "UpdateService"


def _encode_form_data(payload_file):
    """Encode multipart/form-data for file upload."""
    fields = []
    f_name, f_data, f_type = payload_file.get("file")
    f_binary = f_data.read()
    req_field = RequestField(name="file", data=f_binary, filename=f_name)
    req_field.make_multipart(content_type=f_type)
    fields.append(req_field)
    data, content_type = encode_multipart_formdata(fields)
    return data, content_type


def _get_update_service_target(obj, module):
    """Returns all the URI which is required for firmware update dynamically."""
    action_resp = obj.invoke_request("GET", "{0}{1}".format(obj.root_uri, UPDATE_SERVICE))
    action_attr = action_resp.json_data["Actions"]
    protocol = module.params["transfer_protocol"]
    update_uri = None
    push_uri = action_resp.json_data.get('HttpPushUri')
    inventory_uri = action_resp.json_data.get('FirmwareInventory').get('@odata.id')
    if "#UpdateService.SimpleUpdate" in action_attr:
        update_service = action_attr.get("#UpdateService.SimpleUpdate")
        proto = update_service.get("TransferProtocol@Redfish.AllowableValues")
        if isinstance(proto, list) and protocol in proto and 'target' in update_service:
            update_uri = update_service.get('target')
        else:
            module.fail_json(msg="Target firmware version does not support {0} protocol.".format(protocol))
    if update_uri is None or push_uri is None or inventory_uri is None:
        module.fail_json(msg="Target firmware version does not support redfish firmware update.")
    return str(inventory_uri), str(push_uri), str(update_uri)


def firmware_update(obj, module):
    """Firmware update using single binary file from Local path or HTTP location."""
    image_path = module.params.get("image_uri")
    trans_proto = module.params["transfer_protocol"]
    inventory_uri, push_uri, update_uri = _get_update_service_target(obj, module)
    if image_path.startswith("http"):
        payload = {"ImageURI": image_path, "TransferProtocol": trans_proto}
        update_status = obj.invoke_request("POST", update_uri, data=payload)
    else:
        resp_inv = obj.invoke_request("GET", inventory_uri)
        with open(os.path.join(image_path), "rb") as img_file:
            binary_payload = {"file": (image_path.split(os.sep)[-1], img_file, "multipart/form-data")}
            data, ctype = _encode_form_data(binary_payload)
        headers = {"If-Match": resp_inv.headers.get("etag")}
        headers.update({"Content-Type": ctype})
        upload_status = obj.invoke_request("POST", push_uri, data=data, headers=headers, dump=False,
                                           api_timeout=100)
        if upload_status.status_code == 201:
            payload = {"ImageURI": upload_status.headers.get("location")}
            update_status = obj.invoke_request("POST", update_uri, data=payload)
        else:
            update_status = upload_status
    return update_status


def main():
    specs = {
        "image_uri": {"required": True, "type": "str"},
        "transfer_protocol": {"type": "str", "default": "HTTP",
                              "choices": ["CIFS", "FTP", "HTTP", "HTTPS", "NSF", "OEM", "SCP", "SFTP", "TFTP"]},
    }
    specs.update(redfish_auth_params)
    module = AnsibleModule(
        argument_spec=specs,
        supports_check_mode=False)
    if not HAS_LIB:
        module.fail_json(msg=missing_required_lib("urllib3"))
    try:
        message = "Failed to submit the firmware update task."
        with Redfish(module.params, req_session=True) as obj:
            status = firmware_update(obj, module)
            if status.success:
                message = "Successfully submitted the firmware update task."
                task_uri = status.headers.get("Location")
                task_id = task_uri.split("/")[-1]
                module.exit_json(msg=message, task={"id": task_id, "uri": task_uri}, changed=True)
            module.fail_json(msg=message, error_info=json.loads(status))
    except HTTPError as err:
        module.fail_json(msg=str(err), error_info=json.load(err))
    except (RuntimeError, URLError, SSLValidationError, ConnectionError, KeyError,
            ImportError, ValueError, TypeError, IOError, AssertionError, OSError, SSLError) as e:
        module.fail_json(msg=str(e))


if __name__ == '__main__':
    main()