Skip to content

Commit

Permalink
Merge pull request #175 from DMTF/Fix172-Firmware-Inventory
Browse files Browse the repository at this point in the history
Added 'rf_firmware_inventory.py' script to collect and display firmware versions
  • Loading branch information
mraineri authored Jan 17, 2025
2 parents ad1d84c + da9afce commit 7815632
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 0 deletions.
50 changes: 50 additions & 0 deletions docs/rf_firmware_inventory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Firmware Inventory (rf_firmware_inventory.py)

Copyright 2019-2025 DMTF. All rights reserved.

## About

A tool to collect firmware inventory from a Redfish service.

## Usage

```
usage: rf_firmware_inventory.py [-h] --user USER --password PASSWORD --rhost
RHOST [--details] [--id] [--debug]
A tool to collect firmware inventory from a Redfish service
required arguments:
--user USER, -u USER The user name for authentication
--password PASSWORD, -p PASSWORD
The password for authentication
--rhost RHOST, -r RHOST
The address of the Redfish service (with scheme)
optional arguments:
-h, --help show this help message and exit
--details, -details Indicates details to be shown for each firmware entry
--id, -i Construct inventory names using 'Id' values
--debug Creates debug file showing HTTP traces and exceptions
```

The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments.
It then retrieves the firmware inventory collection under the update service and prints its contents.

Example:

```
$ rf_firmware_inventory.py -u root -p root -r https://192.168.1.100 -details
Contoso BMC Firmware | Version: 1.45.455b66-rev4
| Manufacturer: Contoso
| SoftwareId: 1624A9DF-5E13-47FC-874A-DF3AFF143089
| ReleaseDate: 2017-08-22T12:00:00Z
Contoso Simple Storage Firmware | Version: 2.50
| Manufacturer: Contoso
| ReleaseDate: 2021-10-18T12:00:00Z
Contoso BIOS Firmware | Version: P79 v1.45
| Manufacturer: Contoso
| SoftwareId: FEE82A67-6CE2-4625-9F44-237AD2402C28
| ReleaseDate: 2017-12-06T12:00:00Z
```
4 changes: 4 additions & 0 deletions redfish_utilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@
from .update import get_simple_update_info
from .update import simple_update
from .update import multipart_push_update
from .update import get_firmware_inventory
from .update import print_software_inventory
from .misc import logout, print_password_change_required_and_logout

from . import config
Expand Down Expand Up @@ -204,6 +206,8 @@
"get_simple_update_info",
"simple_update",
"multipart_push_update",
"get_firmware_inventory",
"print_software_inventory",
"logout",
"print_password_change_required_and_logout",
"config",
Expand Down
31 changes: 31 additions & 0 deletions redfish_utilities/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,34 @@ def get_collection_ids(context, collection_uri):
verify_response(collection)

return avail_members


def get_collection_members(context, collection_uri):
"""
Iterates over a collection and returns all members
Args:
context: The Redfish client object with an open session
collection_uri: The URI of the collection to process
Returns:
A list of the members of the collection
"""

# Get the collection and iterate through its collection
members = []
collection = context.get(collection_uri)
if collection.status == 404:
raise RedfishCollectionNotFoundError("Service does not contain a collection at URI {}".format(collection_uri))
verify_response(collection)
while True:
for member in collection.dict["Members"]:
member_response = context.get(member["@odata.id"])
verify_response(member_response)
members.append(member_response.dict)
if "Members@odata.nextLink" not in collection.dict:
break
collection = context.get(collection.dict["Members@odata.nextLink"])
verify_response(collection)

return members
61 changes: 61 additions & 0 deletions redfish_utilities/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import json
import os
import errno
from .collections import get_collection_members
from .messages import verify_response
from enum import Enum

Expand All @@ -27,6 +28,14 @@ class RedfishUpdateServiceNotFoundError(Exception):
pass


class RedfishFirmwareInventoryNotFoundError(Exception):
"""
Raised when the update service does not contain firmware inventory
"""

pass


class operation_apply_times(Enum):
"""
Values for operation apply time settings
Expand Down Expand Up @@ -216,6 +225,58 @@ def multipart_push_update(context, image_path, targets=None, timeout=None, apply
return response


def get_firmware_inventory(context):
"""
Finds the firmware inventory and returns its contents
Args:
context: The Redfish client object with an open session
Returns:
An array of dictionaries of the firmware inventory members
"""

# Get the update service
update_service = get_update_service(context)

# Check that there is a firmware inventory collection
if "FirmwareInventory" not in update_service.dict:
raise RedfishFirmwareInventoryNotFoundError("Service does not have a firmware inventory")

return get_collection_members(context, update_service.dict["FirmwareInventory"]["@odata.id"])


def print_software_inventory(software_list, details=False, use_id=False):
"""
Prints the software inventory list into a table
Args:
software_list: The certificate list to print
details: True to print all the detailed info
use_id: Indicates whether to print names from 'Id' property values
"""

software_line_format = " {:40s} | {}"
software_details_line_format = " {:40s} | {}: {}"
details_properties = ["Manufacturer", "SoftwareId", "ReleaseDate"]

# Go through each software element
for software in software_list:
if use_id:
name = software["Id"]
else:
name = software["Name"]
version = software.get("Version", "No version found")
if details:
print(software_details_line_format.format(name, "Version", version))
for detail in details_properties:
if detail in software:
print(software_details_line_format.format("", detail, software[detail]))
else:
print(software_line_format.format(name, version))
print("")


def get_update_service(context):
"""
Locates and gets the UpdateService resource
Expand Down
66 changes: 66 additions & 0 deletions scripts/rf_firmware_inventory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#! /usr/bin/python
# Copyright Notice:
# Copyright 2019-2025 DMTF. All rights reserved.
# License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md

"""
Redfish Firmware Inventory
File : rf_firmware_inventory.py
Brief : This script uses the redfish_utilities module to manage firmware inventory
"""

import argparse
import datetime
import logging
import redfish
import redfish_utilities
import traceback
import sys
from redfish.messages import RedfishPasswordChangeRequiredError

# Get the input arguments
argget = argparse.ArgumentParser(description="A tool to collect firmware inventory from a Redfish service")
argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication")
argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication")
argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)")
argget.add_argument(
"--details", "-details", action="store_true", help="Indicates details to be shown for each firmware entry"
)
argget.add_argument("--id", "-i", action="store_true", help="Construct inventory names using 'Id' values")
argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions")
args = argget.parse_args()

if args.debug:
log_file = "rf_firmware_inventory-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S"))
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG)
logger.info("rf_firmware_inventory Trace")

# Set up the Redfish object
redfish_obj = None
try:
redfish_obj = redfish.redfish_client(
base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3
)
redfish_obj.login(auth="session")
except RedfishPasswordChangeRequiredError:
redfish_utilities.print_password_change_required_and_logout(redfish_obj, args)
sys.exit(1)
except Exception:
raise

exit_code = 0
try:
firmware_inventory = redfish_utilities.get_firmware_inventory(redfish_obj)
redfish_utilities.print_software_inventory(firmware_inventory, details=args.details, use_id=args.id)
except Exception as e:
if args.debug:
logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc()))
exit_code = 1
print(e)
finally:
# Log out
redfish_utilities.logout(redfish_obj)
sys.exit(exit_code)
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def run(self):
"scripts/rf_diagnostic_data.py",
"scripts/rf_discover.py",
"scripts/rf_event_service.py",
"scripts/rf_firmware_inventory.py",
"scripts/rf_licenses.py",
"scripts/rf_logs.py",
"scripts/rf_manager_config.py",
Expand Down

0 comments on commit 7815632

Please sign in to comment.