├── .github └── workflows │ ├── docker.yml │ ├── linters.yml │ └── release.yml ├── .gitignore ├── AUTHORS.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.md ├── Makefile ├── README.md ├── docs ├── rf_accounts.md ├── rf_assembly.md ├── rf_bios_settings.md ├── rf_boot_override.md ├── rf_certificates.md ├── rf_diagnostic_data.md ├── rf_discover.md ├── rf_event_service.md ├── rf_firmware_inventory.md ├── rf_licenses.md ├── rf_logs.md ├── rf_manager_config.md ├── rf_power_equipment.md ├── rf_power_reset.md ├── rf_raw_request.md ├── rf_sel.md ├── rf_sensor_list.md ├── rf_sys_inventory.md ├── rf_test_event_listener.md ├── rf_thermal_equipment.md ├── rf_update.md └── rf_virtual_media.md ├── pyproject.toml ├── redfish_utilities ├── __init__.py ├── accounts.py ├── assembly.py ├── certificates.py ├── collections.py ├── config.py ├── event_service.py ├── inventory.py ├── licenses.py ├── logs.py ├── managers.py ├── messages.py ├── misc.py ├── power_equipment.py ├── resets.py ├── sensors.py ├── systems.py ├── tasks.py ├── thermal_equipment.py └── update.py ├── requirements.txt ├── scripts ├── rf_accounts.py ├── rf_assembly.py ├── rf_bios_settings.py ├── rf_boot_override.py ├── rf_certificates.py ├── rf_diagnostic_data.py ├── rf_discover.py ├── rf_event_service.py ├── rf_firmware_inventory.py ├── rf_licenses.py ├── rf_logs.py ├── rf_manager_config.py ├── rf_power_equipment.py ├── rf_power_reset.py ├── rf_raw_request.py ├── rf_sel.py ├── rf_sensor_list.py ├── rf_sys_inventory.py ├── rf_test_event_listener.py ├── rf_thermal_equipment.py ├── rf_update.py └── rf_virtual_media.py └── setup.py /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | services: 8 | bmc: 9 | image: docker.io/dmtf/redfish-mockup-server:latest 10 | ports: 11 | - 8000:8000 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: docker/setup-qemu-action@v3 15 | - uses: docker/setup-buildx-action@v3.0.0 16 | - name: Start containers 17 | run: docker run --rm --network=host docker.io/dmtf/redfish-tacklebox:latest rf_manager_config.py --user root --password 123 --rhost http://localhost:8000 18 | -------------------------------------------------------------------------------- /.github/workflows/linters.yml: -------------------------------------------------------------------------------- 1 | name: Linters 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | ruff-lint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - uses: actions/setup-python@v5 10 | with: 11 | python-version: '3.x' 12 | - run: pip install ruff 13 | - run: ruff check . 14 | 15 | docker-lint: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: hadolint/hadolint-action@v3.1.0 20 | with: 21 | recursive: true 22 | ignore: DL3018,DL3008,DL3013 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release and Publish 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: 'Version number' 7 | required: true 8 | changes_1: 9 | description: 'Change entry' 10 | required: true 11 | changes_2: 12 | description: 'Change entry' 13 | required: false 14 | changes_3: 15 | description: 'Change entry' 16 | required: false 17 | changes_4: 18 | description: 'Change entry' 19 | required: false 20 | changes_5: 21 | description: 'Change entry' 22 | required: false 23 | changes_6: 24 | description: 'Change entry' 25 | required: false 26 | changes_7: 27 | description: 'Change entry' 28 | required: false 29 | changes_8: 30 | description: 'Change entry' 31 | required: false 32 | jobs: 33 | release_build: 34 | name: Build the release 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v2 38 | with: 39 | token: ${{secrets.GITHUB_TOKEN}} 40 | - name: Build the changelog text 41 | run: | 42 | echo 'CHANGES<> $GITHUB_ENV 43 | echo "## [${{github.event.inputs.version}}] - $(date +'%Y-%m-%d')" >> $GITHUB_ENV 44 | echo "- ${{github.event.inputs.changes_1}}" >> $GITHUB_ENV 45 | if [[ -n "${{github.event.inputs.changes_2}}" ]]; then echo "- ${{github.event.inputs.changes_2}}" >> $GITHUB_ENV; fi 46 | if [[ -n "${{github.event.inputs.changes_3}}" ]]; then echo "- ${{github.event.inputs.changes_3}}" >> $GITHUB_ENV; fi 47 | if [[ -n "${{github.event.inputs.changes_4}}" ]]; then echo "- ${{github.event.inputs.changes_4}}" >> $GITHUB_ENV; fi 48 | if [[ -n "${{github.event.inputs.changes_5}}" ]]; then echo "- ${{github.event.inputs.changes_5}}" >> $GITHUB_ENV; fi 49 | if [[ -n "${{github.event.inputs.changes_6}}" ]]; then echo "- ${{github.event.inputs.changes_6}}" >> $GITHUB_ENV; fi 50 | if [[ -n "${{github.event.inputs.changes_7}}" ]]; then echo "- ${{github.event.inputs.changes_7}}" >> $GITHUB_ENV; fi 51 | if [[ -n "${{github.event.inputs.changes_8}}" ]]; then echo "- ${{github.event.inputs.changes_8}}" >> $GITHUB_ENV; fi 52 | echo "" >> $GITHUB_ENV 53 | echo 'EOF' >> $GITHUB_ENV 54 | - name: Convert all files to Unix format 55 | run: | 56 | sudo apt-get install dos2unix 57 | find . -type f -name "*.py" -print0 | xargs -0 dos2unix 58 | find . -type f -name "*.md" -print0 | xargs -0 dos2unix 59 | find . -type f -name "*.txt" -print0 | xargs -0 dos2unix 60 | - name: Update version numbers 61 | run: | 62 | sed -i -E 's/ version=.+,/ version="'${{github.event.inputs.version}}'",/' setup.py 63 | - name: Update the changelog 64 | run: | 65 | ex CHANGELOG.md <" 79 | git add * 80 | git commit -s -m "${{github.event.inputs.version}} versioning" 81 | git push origin main 82 | - name: Set up Python 83 | uses: actions/setup-python@v2 84 | with: 85 | python-version: '3.x' 86 | - name: Install dependencies 87 | run: | 88 | python -m pip install --upgrade pip 89 | pip install setuptools wheel twine 90 | - name: Build the distribution 91 | run: | 92 | python setup.py sdist bdist_wheel bdist_rpm 93 | mv dist/*.rpm . 94 | - name: Upload to pypi 95 | uses: pypa/gh-action-pypi-publish@release/v1 96 | with: 97 | password: ${{ secrets.PYPI_API_TOKEN }} 98 | - name: Make the release 99 | env: 100 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 101 | run: | 102 | gh release create ${{github.event.inputs.version}} -t ${{github.event.inputs.version}} -n "Changes since last release:"$'\n\n'"$CHANGES" *.rpm 103 | - name: Set up QEMU 104 | uses: docker/setup-qemu-action@v3 105 | - name: Set up Docker Buildx 106 | uses: docker/setup-buildx-action@v3 107 | - name: Login to Docker Hub 108 | uses: docker/login-action@v3 109 | with: 110 | username: ${{secrets.DOCKER_USERNAME}} 111 | password: ${{secrets.DOCKER_PASSWORD}} 112 | - name: Build Docker image and push 113 | id: docker_build 114 | uses: docker/build-push-action@v5 115 | with: 116 | context: . 117 | push: true 118 | tags: dmtf/redfish-tacklebox:${{github.event.inputs.version}},dmtf/redfish-tacklebox:latest 119 | platforms: linux/arm64,linux/amd64 120 | - name: Image digest 121 | run: echo ${{steps.docker_build.outputs.digest}} 122 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | dist/ 3 | *.egg-info/ 4 | *.xlsx 5 | launch.json 6 | uv.lock -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Original Contribution: 2 | 3 | * [Mike Raineri](https://github.com/mraineri) - Dell Inc. 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Overview 4 | 5 | This repository is maintained by the [DMTF](https://www.dmtf.org/ "https://www.dmtf.org/"). All contributions are reviewed and approved by members of the organization. 6 | 7 | ## Submitting Issues 8 | 9 | Bugs, feature requests, and questions are all submitted in the "Issues" section for the project. DMTF members are responsible for triaging and addressing issues. 10 | 11 | ## Contribution Process 12 | 13 | 1. Fork the repository. 14 | 2. Make and commit changes. 15 | 3. Make a pull request. 16 | 17 | All contributions must adhere to the BSD 3-Clause License described in the LICENSE.md file, and the [Developer Certificate of Origin](#developer-certificate-of-origin). 18 | 19 | Pull requests are reviewed and approved by DMTF members. 20 | 21 | ## Developer Certificate of Origin 22 | 23 | All contributions must adhere to the [Developer Certificate of Origin (DCO)](http://developercertificate.org "http://developercertificate.org"). 24 | 25 | The DCO is an attestation attached to every contribution made by every developer. In the commit message of the contribution, the developer adds a "Signed-off-by" statement and thereby agrees to the DCO. This can be added by using the `--signoff` parameter with `git commit`. 26 | 27 | Full text of the DCO: 28 | 29 | ``` 30 | Developer Certificate of Origin 31 | Version 1.1 32 | 33 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 34 | 35 | Everyone is permitted to copy and distribute verbatim copies of this 36 | license document, but changing it is not allowed. 37 | 38 | 39 | Developer's Certificate of Origin 1.1 40 | 41 | By making a contribution to this project, I certify that: 42 | 43 | (a) The contribution was created in whole or in part by me and I 44 | have the right to submit it under the open source license 45 | indicated in the file; or 46 | 47 | (b) The contribution is based upon previous work that, to the best 48 | of my knowledge, is covered under an appropriate open source 49 | license and I have the right under that license to submit that 50 | work with modifications, whether created in whole or in part 51 | by me, under the same open source license (unless I am 52 | permitted to submit under a different license), as indicated 53 | in the file; or 54 | 55 | (c) The contribution was provided directly to me by some other 56 | person who certified (a), (b) or (c) and I have not modified 57 | it. 58 | 59 | (d) I understand and agree that this project and the contribution 60 | are public and that a record of the contribution (including all 61 | personal information I submit with it, including my sign-off) is 62 | maintained indefinitely and may be redistributed consistent with 63 | this project or the open source license(s) involved. 64 | ``` 65 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2019-2025 DMTF. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 4 | FROM python:3.12.0-alpine 5 | 6 | WORKDIR /src 7 | COPY . /src/ 8 | RUN python3 -m pip install --no-cache-dir /src 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019-2025, Contributing Member(s) of Distributed Management Task 4 | Force, Inc.. All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation and/or 14 | other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its contributors 17 | may be used to endorse or promote products derived from this software without 18 | specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := 3.3.8 2 | UNAME := $(shell uname -s) 3 | 4 | ##@ 5 | ##@ Help 6 | ##@ 7 | 8 | .PHONY: help 9 | help: ##@ (Default) Print listing of key targets with their descriptions 10 | @printf "\nUsage: make \n" 11 | ifeq ($(UNAME), Linux) 12 | @grep -F -h "##@" $(MAKEFILE_LIST) | grep -F -v grep -F | sed -e 's/\\$$//' | awk 'BEGIN {FS = ":*[[:space:]]*##@[[:space:]]*"}; \ 13 | { \ 14 | if($$2 == "") \ 15 | pass; \ 16 | else if($$0 ~ /^#/) \ 17 | printf "\n\n%s\n", $$2; \ 18 | else if($$1 == "") \ 19 | printf " %-20s%s\n", "", $$2; \ 20 | else \ 21 | printf "\n \033[34m%-20s\033[0m %s", $$1, $$2; \ 22 | }' 23 | @printf "\n\n" 24 | else ifeq ($(UNAME), Darwin) 25 | @grep -F -h "##@" $(MAKEFILE_LIST) | grep -F -v grep -F | sed -e 's/\\$$//' | awk 'BEGIN {FS = ":*[[:space:]]*##@[[:space:]]*"}; \ 26 | { \ 27 | if($$2 == "") \ 28 | next; \ 29 | else if($$0 ~ /^#/) \ 30 | printf "\n\n%s\n", $$2; \ 31 | else if($$1 == "") \ 32 | printf " %-20s%s\n", "", $$2; \ 33 | else \ 34 | printf "\n \033[34m%-20s\033[0m %s", $$1, $$2; \ 35 | }' 36 | @printf "\n\n" 37 | else 38 | @printf "\nmake help not supported on $(uname)\n" 39 | endif 40 | .DEFAULT_GOAL := help 41 | 42 | ##@ 43 | ##@ Commands 44 | ##@ 45 | 46 | build: ##@ Build the python package 47 | python setup.py sdist 48 | 49 | install: ##@ Install with pip 50 | pip install dist/redfish_utilities-${VERSION}.tar.gz 51 | 52 | install-uv: ##@ Install with uv 53 | uv pip install dist/redfish_utilities-${VERSION}.tar.gz 54 | 55 | lint: ##@ Run linting 56 | black . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redfish Tacklebox 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md) 6 | [![PyPI](https://img.shields.io/pypi/v/redfish-utilities)](https://pypi.org/project/redfish-utilities/) 7 | [![Pulls](https://img.shields.io/docker/pulls/dmtf/redfish-tacklebox?style=flat&logo=docker&label=Pulls)](https://hub.docker.com/r/dmtf/redfish-tacklebox) 8 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat)](https://github.com/psf/black) 9 | [![Linters](https://github.com/DMTF/Redfish-Tacklebox/actions/workflows/linters.yml/badge.svg)](https://github.com/DMTF/Redfish-Tacklebox/actions/workflows/linters.yml) 10 | [![Docker](https://github.com/DMTF/Redfish-Tacklebox/actions/workflows/docker.yml/badge.svg)](https://github.com/DMTF/Redfish-Tacklebox/actions/workflows/docker.yml) 11 | [![GitHub stars](https://img.shields.io/github/stars/DMTF/Redfish-Tacklebox.svg?style=flat-square&label=github%20stars)](https://github.com/DMTF/Redfish-Tacklebox) 12 | [![GitHub Contributors](https://img.shields.io/github/contributors/DMTF/Redfish-Tacklebox.svg?style=flat-square)](https://github.com/DMTF/Redfish-Tacklebox/graphs/contributors) 13 | 14 | ## About 15 | 16 | Redfish Tacklebox contains a set of Python3 utilities to perform common management operations with a Redfish service. 17 | The utilities can be used as part of larger management applications, or be used as standalone command line tools. 18 | 19 | ## Installation 20 | 21 | Only one installation method is required to use Redfish Tacklebox. 22 | 23 | ### From PyPI 24 | 25 | pip install redfish_utilities 26 | 27 | ### Building from Source 28 | 29 | git clone https://github.com/DMTF/Redfish-Tacklebox.git 30 | cd Redfish-Tacklebox 31 | python setup.py sdist 32 | pip install dist/redfish_utilities-x.x.x.tar.gz 33 | 34 | ### Building Docker 35 | 36 | * Pull the container from Docker Hub: 37 | 38 | ```bash 39 | docker pull dmtf/redfish-tacklebox:latest 40 | ``` 41 | * Build a container from local source: 42 | 43 | ```bash 44 | docker build -t dmtf/redfish-tacklebox:latest . 45 | ``` 46 | * Build a container from GitHub: 47 | 48 | ```bash 49 | docker build -t dmtf/redfish-tacklebox:latest https://github.com/DMTF/Redfish-Tacklebox.git 50 | ``` 51 | 52 | ## Requirements 53 | 54 | External modules: 55 | * redfish: https://pypi.python.org/pypi/redfish 56 | * XlsxWriter: https://pypi.org/project/XlsxWriter 57 | 58 | You may install the external modules by running: 59 | 60 | `pip install -r requirements.txt` 61 | 62 | ## Utilities 63 | 64 | * [Discover (rf_discover.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_discover.md) 65 | * [Sensor List (rf_sensor_list.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_sensor_list.md) 66 | * [System Inventory (rf_sys_inventory.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_sys_inventory.md) 67 | * [Logs (rf_logs.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_logs.md) 68 | * [SEL (rf_sel.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_sel.md) 69 | * [Power/Reset (rf_power_reset.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_power_reset.md) 70 | * [Boot Override (rf_boot_override.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_boot_override.md) 71 | * [Virtual Media (rf_virtual_media.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_virtual_media.md) 72 | * [BIOS Settings (rf_bios_settings.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_bios_settings.md) 73 | * [Manager Configuration (rf_manager_config.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_manager_config.md) 74 | * [Accounts (rf_accounts.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_accounts.md) 75 | * [Update (rf_update.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_update.md) 76 | * [Firmware Inventory (rf_firmware_inventory.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_firmware_inventory.md) 77 | * [Event Service (rf_event_service.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_event_service.md) 78 | * [Licenses (rf_licenses.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_licenses.md) 79 | * [Certificates (rf_certificates.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_certificates.md) 80 | * [Diagnostic Data (rf_diagnostic_data.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_diagnostic_data.md) 81 | * [Assembly (rf_assembly.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_assembly.md) 82 | * [Power Equipment (rf_power_equipment.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_power_equipment.md) 83 | * [Thermal Equipment (rf_thermal_equipment.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_thermal_equipment.md) 84 | * [Raw Request (rf_raw_request.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_raw_request.md) 85 | * [Test Event Listener (rf_test_event_listener.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_test_event_listener.md) 86 | 87 | ## Release Process 88 | 89 | 1. Go to the "Actions" page 90 | 2. Select the "Release and Publish" workflow 91 | 3. Click "Run workflow" 92 | 4. Fill out the form 93 | 5. Click "Run workflow" 94 | -------------------------------------------------------------------------------- /docs/rf_accounts.md: -------------------------------------------------------------------------------- 1 | # Accounts (rf_accounts.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to manage user accounts on a Redfish service. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_accounts.py [-h] --user USER --password PASSWORD --rhost RHOST 13 | [--add name password role] [--delete DELETE] 14 | [--setname old_name new_name] 15 | [--setpassword name new_password] 16 | [--setrole name new_role] [--enable ENABLE] 17 | [--disable DISABLE] [--unlock UNLOCK] [--debug] 18 | 19 | A tool to manage user accounts on a Redfish service 20 | 21 | required arguments: 22 | --user USER, -u USER The user name for authentication 23 | --password PASSWORD, -p PASSWORD 24 | The password for authentication 25 | --rhost RHOST, -r RHOST 26 | The address of the Redfish service (with scheme) 27 | 28 | optional arguments: 29 | -h, --help show this help message and exit 30 | --add name password role, -add name password role 31 | Adds a new user account 32 | --delete DELETE, -delete DELETE 33 | Deletes a user account with the given name 34 | --setname old_name new_name, -setname old_name new_name 35 | Sets a user account to a new name 36 | --setpassword name new_password, -setpassword name new_password 37 | Sets a user account to a new password 38 | --setrole name new_role, -setrole name new_role 39 | Sets a user account to a new role 40 | --enable ENABLE, -enable ENABLE 41 | Enables a user account with the given name 42 | --disable DISABLE, -disable DISABLE 43 | Disabled a user account with the given name 44 | --unlock UNLOCK, -unlock UNLOCK 45 | Unlocks a user account with the given name 46 | --debug Creates debug file showing HTTP traces and exceptions 47 | ``` 48 | 49 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 50 | Based on the parameters, it will display, add, delete, or modify user accounts. 51 | 52 | * The *add* argument is used to create a new user account 53 | * The *delete* argument is used to delete a user account based on the given user name 54 | * The *setname* argument is used to change the name of a user account 55 | * The *setpassword* argument is used to change the password of a user account 56 | * The *setrole* argument is used to change the role of a user account 57 | * The *enable* argument is used to enable a user account 58 | * The *disable* argument is used to disable a user account 59 | * The *unlock* argument is used to unlock a user account 60 | * If none of the above arguments are given, a table of the user accounts is provided 61 | 62 | Example; display existing user accounts: 63 | 64 | ``` 65 | $ rf_accounts.py -u root -p root -r https://192.168.1.100 66 | 67 | Name | Role | Locked | Enabled 68 | Administrator | Administrator | False | True 69 | 70 | ``` 71 | 72 | Example; add a new account: 73 | 74 | ``` 75 | $ rf_accounts.py -u root -p root -r https://192.168.1.100 -add new_name new_password new_role 76 | Adding new user 'new_name' 77 | ``` 78 | 79 | Example; delete an account: 80 | 81 | ``` 82 | $ rf_accounts.py -u root -p root -r https://192.168.1.100 -delete user_to_delete 83 | Deleting user 'user_to_delete' 84 | ``` 85 | 86 | Example; change the username for an account: 87 | 88 | ``` 89 | $ rf_accounts.py -u root -p root -r https://192.168.1.100 -setname user_to_change new_name 90 | Changing name of user 'user_to_change' to 'new_name' 91 | ``` 92 | 93 | Example; change the password for an account: 94 | 95 | ``` 96 | $ rf_accounts.py -u root -p root -r https://192.168.1.100 -setpassword user_to_change new_password 97 | Changing password of user 'user_to_change' 98 | ``` 99 | 100 | Example; change the role for an account: 101 | 102 | ``` 103 | $ rf_accounts.py -u root -p root -r https://192.168.1.100 -setrole user_to_change Operator 104 | Changing role of user 'user_to_change' to 'Operator' 105 | ``` 106 | 107 | Example; enable a user account: 108 | 109 | ``` 110 | $ rf_accounts.py -u root -p root -r https://192.168.1.100 -enable user_to_change 111 | Enabling user 'user_to_change' 112 | ``` 113 | 114 | Example; disable a user account: 115 | 116 | ``` 117 | $ rf_accounts.py -u root -p root -r https://192.168.1.100 -disable user_to_change 118 | Disabling user 'user_to_change' 119 | ``` 120 | 121 | Example; unlock a user account: 122 | 123 | ``` 124 | $ rf_accounts.py -u root -p root -r https://192.168.1.100 -unlock user_to_change 125 | Unlocking user 'user_to_change' 126 | ``` 127 | -------------------------------------------------------------------------------- /docs/rf_assembly.md: -------------------------------------------------------------------------------- 1 | # Assembly (rf_assembly.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to manage assemblies on a Redfish service. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_assembly.py [-h] --user USER --password PASSWORD --rhost RHOST 13 | --assembly ASSEMBLY [--index INDEX] [--debug] 14 | {info,download,upload} ... 15 | 16 | A tool to manage assemblies on a Redfish service 17 | 18 | positional arguments: 19 | {info,download,upload} 20 | info Displays information about the an assembly 21 | download Downloads assembly data to a file 22 | upload Uploads assembly data from a file 23 | 24 | required arguments: 25 | --user USER, -u USER The user name for authentication 26 | --password PASSWORD, -p PASSWORD 27 | The password for authentication 28 | --rhost RHOST, -r RHOST 29 | The address of the Redfish service (with scheme) 30 | --assembly ASSEMBLY, -a ASSEMBLY 31 | The URI of the target assembly 32 | 33 | optional arguments: 34 | -h, --help show this help message and exit 35 | --index INDEX, -i INDEX 36 | The target assembly index 37 | --debug Creates debug file showing HTTP traces and exceptions 38 | ``` 39 | 40 | ### Info 41 | 42 | Displays information about the an assembly. 43 | 44 | ``` 45 | usage: rf_assembly.py info [-h] 46 | 47 | optional arguments: 48 | -h, --help show this help message and exit 49 | ``` 50 | 51 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 52 | It will then get the assembly information from the URI specified by the *assembly* argument and displays its information. 53 | 54 | Example: 55 | 56 | ``` 57 | $ rf_assembly.py -u root -p root -r https://192.168.1.100 -a /redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay1/Assembly info 58 | 0 | Contoso Power Supply 59 | | Model: 345TTT 60 | | PartNumber: 923943 61 | | SerialNumber: 345394834 62 | | Producer: Contoso Supply Co. 63 | | Vendor: Contoso 64 | | ProductionDate: 2017-04-01T14:55:33+03:00 65 | ``` 66 | 67 | ### Download 68 | 69 | Downloads assembly data to a file. 70 | 71 | ``` 72 | usage: rf_assembly.py download [-h] --file FILE 73 | 74 | required arguments: 75 | --file FILE, -f FILE The file, and optional path, to save the assembly data 76 | 77 | optional arguments: 78 | -h, --help show this help message and exit 79 | ``` 80 | 81 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 82 | It will then get the assembly information from the URI specified by the *assembly* argument and download the binary data contents to the file specified by the *file* argument. 83 | 84 | ``` 85 | $ rf_assembly.py -u root -p root -r https://192.168.1.100 -a /redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay1/Assembly download -f data.bin 86 | Saving data to 'data.bin'... 87 | ``` 88 | 89 | ### Upload 90 | 91 | Uploads assembly data from a file. 92 | 93 | ``` 94 | usage: rf_assembly.py upload [-h] --file FILE 95 | 96 | required arguments: 97 | --file FILE, -f FILE The file, and optional path, containing the assembly 98 | data to upload 99 | 100 | optional arguments: 101 | -h, --help show this help message and exit 102 | ``` 103 | 104 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 105 | It will then get the assembly information from the URI specified by the *assembly* argument and upload the contents of the file specified by the *file* argument to the binary data. 106 | 107 | ``` 108 | $ rf_assembly.py -u root -p root -r https://192.168.1.100 -a /redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay1/Assembly upload -f data.bin 109 | Writing data from 'data.bin'... 110 | ``` 111 | -------------------------------------------------------------------------------- /docs/rf_bios_settings.md: -------------------------------------------------------------------------------- 1 | # BIOS Settings (rf_bios_settings.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to manager BIOS settings for a system. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_bios_settings.py [-h] --user USER --password PASSWORD --rhost RHOST 13 | [--system SYSTEM] [--attribute name value] 14 | [--reset] [--workaround] [--debug] 15 | 16 | A tool to manager BIOS settings for a system 17 | 18 | required arguments: 19 | --user USER, -u USER The user name for authentication 20 | --password PASSWORD, -p PASSWORD 21 | The password for authentication 22 | --rhost RHOST, -r RHOST 23 | The address of the Redfish service (with scheme) 24 | 25 | optional arguments: 26 | -h, --help show this help message and exit 27 | --system SYSTEM, -s SYSTEM 28 | The ID of the system to manage 29 | --attribute name value, -a name value 30 | Sets a BIOS attribute to a new value; can be supplied 31 | multiple times to set multiple attributes 32 | --reset, -reset Resets BIOS to the default settings 33 | --workaround, -workaround 34 | Indicates if workarounds should be attempted for non- 35 | conformant services 36 | --debug Creates debug file showing HTTP traces and exception 37 | ``` 38 | 39 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 40 | It then traverses the system collection for the service to find the matching system specified by the *system* argument. 41 | 42 | * If *system* is not specified, and if the service has exactly one system, it will perform the operation on the one system. 43 | 44 | The tool will then get the BIOS resource for the matching system. 45 | 46 | * If *reset* is specified, it will perform a request to set BIOS to the default settings. 47 | * If *attribute* is specified, it will update the BIOS resource with the new attribute value. 48 | * Otherwise, it will display the BIOS settings. 49 | 50 | Example; display attributes: 51 | 52 | ``` 53 | $ rf_bios_settings.py -u root -p root -r https://192.168.1.100 54 | 55 | BIOS Settings: 56 | Attribute Name | Current Setting | Future Setting 57 | AdminPhone | | (404) 555-1212 58 | BootMode | Uefi | Uefi 59 | EmbeddedSata | Raid | Ahci 60 | NicBoot1 | NetworkBoot | NetworkBoot 61 | NicBoot2 | Disabled | NetworkBoot 62 | PowerProfile | MaxPerf | MaxPerf 63 | ProcCoreDisable | 0 | 0 64 | ProcHyperthreading | Enabled | Enabled 65 | ProcTurboMode | Enabled | Disabled 66 | UsbControl | UsbEnabled | UsbEnabled 67 | 68 | ``` 69 | 70 | Example; set an attribute: 71 | 72 | ``` 73 | $ rf_bios_settings.py -u root -p root -r https://192.168.1.100 -a BiosMode Legacy 74 | Setting BiosMode to Legacy... 75 | ``` 76 | 77 | Example; reset BIOS to the default settings: 78 | 79 | ``` 80 | $ rf_bios_settings.py -u root -p root -r https://192.168.1.100 -reset 81 | Resetting the BIOS settings... 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/rf_boot_override.md: -------------------------------------------------------------------------------- 1 | # Boot Override (rf_boot_override.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to perform a one time boot override of a system. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_boot_override.py [-h] --user USER --password PASSWORD --rhost RHOST 13 | [--system SYSTEM] [--info] [--target TARGET] 14 | [--uefi UEFI] [--mode MODE] [--reset] 15 | [--workaround] [--debug] 16 | 17 | A tool to perform a one time boot override of a system 18 | 19 | required arguments: 20 | --user USER, -u USER The user name for authentication 21 | --password PASSWORD, -p PASSWORD 22 | The password for authentication 23 | --rhost RHOST, -r RHOST 24 | The address of the Redfish service (with scheme) 25 | 26 | optional arguments: 27 | -h, --help show this help message and exit 28 | --system SYSTEM, -s SYSTEM 29 | The ID of the system to set 30 | --info, -info Indicates if boot information should be reported 31 | --target TARGET, -t TARGET 32 | The target boot device; if this argument is omitted 33 | the tool will display the current boot settings 34 | --uefi UEFI, -uefi UEFI 35 | If target is 'UefiTarget', the UEFI Device Path of the 36 | device to boot. If target is 'UefiBootNext', the UEFI 37 | Boot Option string of the device to boot. 38 | --mode MODE, -m MODE The requested boot mode ('UEFI' or 'Legacy') 39 | --reset, -reset Signifies that the system is reset after the boot 40 | override is set 41 | --workaround, -workaround 42 | Indicates if workarounds should be attempted for non- 43 | conformant services 44 | --debug Creates debug file showing HTTP traces and exceptions 45 | ``` 46 | 47 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 48 | It then traverses the system collection for the service to find the matching system specified by the *system* argument. 49 | 50 | * If *system* is not specified, and if the service has exactly one system, it will perform the operation on the one system. 51 | 52 | The tool will then perform an operation on the `Boot` object within the matching system. 53 | 54 | * If *target* is specified, it will update the `Boot` object to set the boot override to be *target*. 55 | * If *reset* is provided, it will reset the system after updating the `Boot` object. 56 | * If *target* is not specified, it will display the current boot override settings for the system. 57 | 58 | Example: 59 | 60 | ``` 61 | $ rf_boot_override.py -u root -p root -r https://192.168.1.100 -t Pxe -reset 62 | Setting a one time boot for Pxe... 63 | Resetting the system... 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/rf_diagnostic_data.md: -------------------------------------------------------------------------------- 1 | # Diagnostic Data (rf_diagnostic_data.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to manage licenses on a Redfish service. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_diagnostic_data.py [-h] --user USER --password PASSWORD --rhost 13 | RHOST [--manager [MANAGER]] [--system [SYSTEM]] 14 | [--chassis [CHASSIS]] [--log LOG] 15 | [--type {Manager,PreOS,OS,OEM}] 16 | [--oemtype OEMTYPE] [--directory DIRECTORY] 17 | [--debug] 18 | 19 | A tool to collect diagnostic data from a log service on a Redfish service 20 | 21 | required arguments: 22 | --user USER, -u USER The user name for authentication 23 | --password PASSWORD, -p PASSWORD 24 | The password for authentication 25 | --rhost RHOST, -r RHOST 26 | The address of the Redfish service (with scheme) 27 | 28 | optional arguments: 29 | -h, --help show this help message and exit 30 | --manager [MANAGER], -m [MANAGER] 31 | The ID of the manager containing the log service 32 | --system [SYSTEM], -s [SYSTEM] 33 | The ID of the system containing the log service 34 | --chassis [CHASSIS], -c [CHASSIS] 35 | The ID of the chassis containing the log service 36 | --log LOG, -l LOG The ID of the log service 37 | --type {Manager,PreOS,OS,OEM}, -type {Manager,PreOS,OS,OEM} 38 | The type of diagnostic data to collect; defaults to 39 | 'Manager' if not specified 40 | --oemtype OEMTYPE, -oemtype OEMTYPE 41 | The OEM-specific type of diagnostic data to collect; 42 | this option should only be used if the requested type 43 | is 'OEM' 44 | --directory DIRECTORY, -d DIRECTORY 45 | The directory to save the diagnostic data; defaults to 46 | the current directory if not specified 47 | --debug Creates debug file showing HTTP traces and exceptions 48 | ``` 49 | 50 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 51 | It will then attempt to locate the appropriate log service via the following logic: 52 | 53 | * If the *manager* argument is provided, it will traverse the manager collection for the matching manager. 54 | * If the *system* argument is provided, it will traverse the system collection for the matching system. 55 | * If the *chassis* argument is provided, it will traverse the chassis collection for the matching chassis. 56 | * If any of the above arguments are provided without a specified Id, but the collection contains exactly one member, then that member is used. 57 | * If none of the above arguments are provided, then the tool will try to use a manager in the manager collection if there is only one member present. 58 | * Within the member, the tool will find the matching log service based on the *log* argument. 59 | * If *log* is not specified, and there is exactly one log service in the member, then the tool will use that one log service. 60 | 61 | Once the desired log service is found, the tool perform the `GetDiagnosticData` action and specify the type of diagnostic data to collect based on the *type* and *oemtype* arguments. 62 | Once the action is complete, it will download the diagnostic data from the service and save it on the local system. 63 | 64 | Example: 65 | 66 | ``` 67 | $ rf_diagnostic_data.py -u root -p root -r https://192.168.1.100 -m BMC 68 | Collecting diagnostic data... 69 | Task is Done! 70 | Saved diagnostic data to './debug-data.tar.gz' 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/rf_discover.md: -------------------------------------------------------------------------------- 1 | # Discover (rf_discover.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to discover Redfish services. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_discover.py [-h] 13 | 14 | A tool to discover Redfish services 15 | 16 | optional arguments: 17 | -h, --help show this help message and exit 18 | ``` 19 | 20 | The tool will perform an SSDP request to find all available Redfish services. 21 | Once all the responses are collected, it will print each service with its UUID and service root. 22 | 23 | Example: 24 | 25 | ``` 26 | $ rf_discover.py 27 | Redfish services: 28 | 5822e6cd-35e1-45ab-99de-797bc78edf48: https://192.168.1.17/redfish/v1/ 29 | 03fff361-3520-421f-9511-71b97093cb79: https://192.168.1.22/redfish/v1/ 30 | b010f703-7c4e-441b-bb77-aad212d897d2: https://192.168.1.70/redfish/v1/ 31 | a3dd7e36-c96c-473d-858a-bdfa7fa5cdd7: https://192.168.1.18/redfish/v1/ 32 | 5d23dd1c-dfd8-4b83-963f-ec3e7c1c8b27: https://192.168.1.96/redfish/v1/ 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/rf_event_service.md: -------------------------------------------------------------------------------- 1 | # Event Service (rf_event_service.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to manage the event service on a Redfish service. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_event_service.py [-h] --user USER --password PASSWORD --rhost RHOST 13 | [--debug] 14 | {info,subscribe,unsubscribe} ... 15 | 16 | A tool to manage the event service on a Redfish service 17 | 18 | positional arguments: 19 | {info,subscribe,unsubscribe} 20 | info Displays information about the event service and 21 | subscriptions 22 | subscribe Creates an event subscription to a specified URL 23 | unsubscribe Deletes an event subscription 24 | 25 | required arguments: 26 | --user USER, -u USER The user name for authentication 27 | --password PASSWORD, -p PASSWORD 28 | The password for authentication 29 | --rhost RHOST, -r RHOST 30 | The address of the Redfish service (with scheme) 31 | 32 | optional arguments: 33 | -h, --help show this help message and exit 34 | --debug Creates debug file showing HTTP traces and exceptions 35 | ``` 36 | 37 | ### Info 38 | 39 | Displays information about the event service and subscriptions. 40 | 41 | ``` 42 | usage: rf_event_service.py info [-h] 43 | 44 | optional arguments: 45 | -h, --help show this help message and exit 46 | ``` 47 | 48 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 49 | It will then locate the event service and event subscriptions and display their information. 50 | 51 | Example: 52 | 53 | ``` 54 | $ rf_event_service.py -u root -p root -r https://192.168.1.100 info 55 | Service Info 56 | Status: Enabled 57 | Delivery Retry Policy: 3 attempts, 60 second intervals 58 | Event Types: StatusChange, ResourceUpdated, ResourceAdded, ResourceRemoved, Alert 59 | Event Formats: N/A 60 | Registries: N/A 61 | Resource Types: N/A 62 | Include Origin of Condition Supported: False 63 | SSE URI: Not supported 64 | SSE Filter Parameters: Unknown/Unspecified 65 | 66 | Subscription Info 67 | 1 | Destination: http://www.dnsname.com/Destination1 68 | | State: Enabled 69 | | Context: WebUser3 70 | | Event Format: Event 71 | | Event Types: Alert 72 | 2 | Destination: contoso_user@snmp_server.contoso.com 73 | | State: Enabled 74 | | Context: My_SNMPv3_Events 75 | | Event Format: Event 76 | | Resource Types: Certificate, Volume, Thermal, VirtualMedia, Power 77 | 3 | Destination: mailto:spam@contoso.com 78 | | State: Enabled 79 | | Context: EmailUser3 80 | | Event Format: Event 81 | | Resource Types: Certificates, Systems 82 | 4 | Destination: syslog://123.45.10:514 83 | | State: Enabled 84 | | Context: Syslog-Mockup 85 | | Event Format: Event 86 | ``` 87 | 88 | ### Subscribe 89 | 90 | Creates an event subscription to a specified URL. 91 | 92 | ``` 93 | usage: rf_event_service.py subscribe [-h] --destination DESTINATION 94 | [--context CONTEXT] [--expand] 95 | [--format FORMAT] 96 | [--resourcetypes RESOURCETYPES [RESOURCETYPES ...]] 97 | [--registries REGISTRIES [REGISTRIES ...]] 98 | [--eventtypes EVENTTYPES [EVENTTYPES ...]] 99 | 100 | required arguments: 101 | --destination DESTINATION, -dest DESTINATION 102 | The URL where events are sent for the subscription 103 | 104 | optional arguments: 105 | -h, --help show this help message and exit 106 | --context CONTEXT, -c CONTEXT 107 | The context string for the subscription that is 108 | supplied back in the event payload 109 | --expand, -e Indicates if the origin of condition in the event is 110 | to be expanded 111 | --format FORMAT, -f FORMAT 112 | The format of the event payloads 113 | --resourcetypes RESOURCETYPES [RESOURCETYPES ...], -rt RESOURCETYPES [RESOURCETYPES ...] 114 | A list of resource types for the subscription 115 | --registries REGISTRIES [REGISTRIES ...], -reg REGISTRIES [REGISTRIES ...] 116 | A list of registries for the subscription 117 | --eventtypes EVENTTYPES [EVENTTYPES ...], -et EVENTTYPES [EVENTTYPES ...] 118 | A list of event types for the subscription; this 119 | option has been deprecated in favor of other methods 120 | such as 'resource types' and 'registries' 121 | ``` 122 | 123 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 124 | It will then locate the event service and perform a POST operation on the event destination collection to create a new subscription. 125 | The subscription will specify the destination to be the *destination* argument, and other optional arguments are provided as additional settings on the subscription. 126 | 127 | Example: 128 | 129 | ``` 130 | $ rf_event_service.py -u root -p root -r https://192.168.1.100 subscribe -dest http://someremotelistener/redfish_event_handler 131 | Created subscription '/redfish/v1/EventService/Subscriptions/5' 132 | ``` 133 | 134 | ### Unsubscribe 135 | 136 | Deletes an event subscription. 137 | 138 | ``` 139 | usage: rf_event_service.py unsubscribe [-h] --id ID 140 | 141 | required arguments: 142 | --id ID, -i ID The identifier of the event subscription to be deleted 143 | 144 | optional arguments: 145 | -h, --help show this help message and exit 146 | ``` 147 | 148 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 149 | It will then locate the event service and traverse the members of the event destination collection to find a member with the `Id` property matching the *id* argument. 150 | If a match is found, it will perform a DELETE on the member. 151 | 152 | Example: 153 | 154 | ``` 155 | $ rf_event_service.py -u root -p root -r https://192.168.1.100 unsubscribe --id 5 156 | Deleting subscription '5' 157 | ``` 158 | -------------------------------------------------------------------------------- /docs/rf_firmware_inventory.md: -------------------------------------------------------------------------------- 1 | # Firmware Inventory (rf_firmware_inventory.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to collect firmware inventory from a Redfish service. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_firmware_inventory.py [-h] --user USER --password PASSWORD --rhost 13 | RHOST [--details] [--id] [--debug] 14 | 15 | A tool to collect firmware inventory from a Redfish service 16 | 17 | required arguments: 18 | --user USER, -u USER The user name for authentication 19 | --password PASSWORD, -p PASSWORD 20 | The password for authentication 21 | --rhost RHOST, -r RHOST 22 | The address of the Redfish service (with scheme) 23 | 24 | optional arguments: 25 | -h, --help show this help message and exit 26 | --details, -details Indicates details to be shown for each firmware entry 27 | --id, -i Construct inventory names using 'Id' values 28 | --debug Creates debug file showing HTTP traces and exceptions 29 | ``` 30 | 31 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 32 | It then retrieves the firmware inventory collection under the update service and prints its contents. 33 | 34 | Example: 35 | 36 | ``` 37 | $ rf_firmware_inventory.py -u root -p root -r https://192.168.1.100 -details 38 | Contoso BMC Firmware | Version: 1.45.455b66-rev4 39 | | Manufacturer: Contoso 40 | | SoftwareId: 1624A9DF-5E13-47FC-874A-DF3AFF143089 41 | | ReleaseDate: 2017-08-22T12:00:00Z 42 | Contoso Simple Storage Firmware | Version: 2.50 43 | | Manufacturer: Contoso 44 | | ReleaseDate: 2021-10-18T12:00:00Z 45 | Contoso BIOS Firmware | Version: P79 v1.45 46 | | Manufacturer: Contoso 47 | | SoftwareId: FEE82A67-6CE2-4625-9F44-237AD2402C28 48 | | ReleaseDate: 2017-12-06T12:00:00Z 49 | 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/rf_licenses.md: -------------------------------------------------------------------------------- 1 | # Licenses (rf_licenses.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to manage licenses on a Redfish service. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_licenses.py [-h] --user USER --password PASSWORD --rhost RHOST 13 | [--debug] 14 | {info,install,delete} ... 15 | 16 | A tool to manage licenses on a Redfish service 17 | 18 | positional arguments: 19 | {info,install,delete} 20 | info Displays information about the licenses installed on 21 | the service 22 | install Installs a new license 23 | delete Deletes a license 24 | 25 | required arguments: 26 | --user USER, -u USER The user name for authentication 27 | --password PASSWORD, -p PASSWORD 28 | The password for authentication 29 | --rhost RHOST, -r RHOST 30 | The address of the Redfish service (with scheme) 31 | 32 | optional arguments: 33 | -h, --help show this help message and exit 34 | --debug Creates debug file showing HTTP traces and exceptions 35 | ``` 36 | 37 | ### Info 38 | 39 | Displays information about the licenses installed on the service. 40 | 41 | ``` 42 | usage: rf_licenses.py info [-h] [--details] 43 | 44 | optional arguments: 45 | -h, --help show this help message and exit 46 | --details, -details Indicates if the full details of each license should be 47 | shown 48 | ``` 49 | 50 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 51 | It will then locate the license service, find its license collection, and display the licenses. 52 | 53 | Example: 54 | 55 | ``` 56 | $ rf_licenses.py -u root -p root -r https://192.168.1.100 info 57 | 58 | License | Details 59 | RemotePresence | LIC023923978, Installed on 2023-08-20T20:13:44Z, Expires on 2026-08-20T20:13:43Z 60 | RedfishTelemetry | LIC892345871, Installed on 2023-04-21T08:44:02Z, Installed on 2026-04-21T08:44:02Z 61 | 62 | ``` 63 | 64 | ### Install 65 | 66 | Installs a new license. 67 | 68 | ``` 69 | usage: rf_licenses.py install [-h] --license LICENSE 70 | 71 | required arguments: 72 | --license LICENSE, -l LICENSE 73 | The filepath or URI to the license to install 74 | 75 | optional arguments: 76 | -h, --help show this help message and exit 77 | ``` 78 | 79 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 80 | It will then locate the license service. 81 | If the license referenced by the *license* argument is local file, it will insert the contents of the license file in the license collection. 82 | Otherwise, it will install the new license with the `Install` action found on the license service. 83 | 84 | Example: 85 | 86 | ``` 87 | $ rf_licenses.py -u root -p root -r https://192.168.1.100 install --license /home/user/my_license.xml 88 | Installing license '/home/user/my_license.xml'... 89 | ``` 90 | 91 | ### Delete 92 | 93 | Deletes a license. 94 | 95 | ``` 96 | usage: rf_licenses.py delete [-h] --license LICENSE 97 | 98 | required arguments: 99 | --license LICENSE, -l LICENSE 100 | The identifier of the license to delete 101 | 102 | optional arguments: 103 | -h, --help show this help message and exit 104 | ``` 105 | 106 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 107 | It will then locate the license service and find the license requested by the *license* argument. 108 | If the matching license is found, it will delete the license. 109 | 110 | Example: 111 | 112 | ``` 113 | $ rf_licenses.py -u root -p root -r https://192.168.1.100 delete --license 1 114 | Deleting license '1'... 115 | ``` 116 | -------------------------------------------------------------------------------- /docs/rf_logs.md: -------------------------------------------------------------------------------- 1 | # Logs (rf_logs.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to manage logs on a Redfish service. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_logs.py [-h] --user USER --password PASSWORD --rhost RHOST 13 | [--manager [MANAGER]] [--system [SYSTEM]] 14 | [--chassis [CHASSIS]] [--log LOG] [--details] [--clear] 15 | [--debug] 16 | 17 | A tool to manage logs on a Redfish service 18 | 19 | required arguments: 20 | --user USER, -u USER The user name for authentication 21 | --password PASSWORD, -p PASSWORD 22 | The password for authentication 23 | --rhost RHOST, -r RHOST 24 | The address of the Redfish service (with scheme) 25 | 26 | optional arguments: 27 | -h, --help show this help message and exit 28 | --manager [MANAGER], -m [MANAGER] 29 | The ID of the manager containing the log service 30 | --system [SYSTEM], -s [SYSTEM] 31 | The ID of the system containing the log service 32 | --chassis [CHASSIS], -c [CHASSIS] 33 | The ID of the chassis containing the log service 34 | --log LOG, -l LOG The ID of the resource containing the log service 35 | --details, -details Indicates details to be shown for each log entry 36 | --clear, -clear Indicates if the log should be cleared 37 | --debug Creates debug file showing HTTP traces and exceptions 38 | ``` 39 | 40 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 41 | It will then attempt to locate the appropriate log service via the following logic: 42 | 43 | * If the *manager* argument is provided, it will traverse the manager collection for the matching manager. 44 | * If the *system* argument is provided, it will traverse the system collection for the matching system. 45 | * If the *chassis* argument is provided, it will traverse the chassis collection for the matching chassis. 46 | * If any of the above arguments are provided without a specified Id, but the collection contains exactly one member, then that member is used. 47 | * If none of the above arguments are provided, then the tool will try to use a manager in the manager collection if there is only one member present. 48 | * Within the member, the tool will find the matching log service based on the *log* argument. 49 | * If *log* is not specified, and there is exactly one log service in the member, then the tool will use that one log service. 50 | 51 | Once the desired log service is found, the tool will either perform the `ClearLog` action if *clear* is provided, or read and display the log entries. 52 | 53 | Example: 54 | 55 | ``` 56 | $ rf_logs.py -u root -p root -r https://192.168.1.100 -m BMC 57 | Id | Timestamp | Message 58 | 1 | 2012-03-07T14:44:00Z | System May be Melting 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/rf_power_reset.md: -------------------------------------------------------------------------------- 1 | # Power/Reset (rf_power_reset.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to perform a power/reset operation of a system. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_power_reset.py [-h] --user USER --password PASSWORD --rhost RHOST 13 | [--system SYSTEM] 14 | [--type {On,ForceOff,GracefulShutdown,GracefulRestart,ForceRestart,Nmi,ForceOn,PushPowerButton,PowerCycle,Suspend,Pause,Resume}] 15 | [--info] [--debug] 16 | 17 | A tool to perform a power/reset operation of a system 18 | 19 | required arguments: 20 | --user USER, -u USER The user name for authentication 21 | --password PASSWORD, -p PASSWORD 22 | The password for authentication 23 | --rhost RHOST, -r RHOST 24 | The address of the Redfish service (with scheme) 25 | 26 | optional arguments: 27 | -h, --help show this help message and exit 28 | --system SYSTEM, -s SYSTEM 29 | The ID of the system to reset 30 | --type {On,ForceOff,GracefulShutdown,GracefulRestart,ForceRestart,Nmi,ForceOn,PushPowerButton,PowerCycle,Suspend,Pause,Resume}, -t {On,ForceOff,GracefulShutdown,GracefulRestart,ForceRestart,Nmi,ForceOn,PushPowerButton,PowerCycle,Suspend,Pause,Resume} 31 | The type of power/reset operation to perform 32 | --info, -info Indicates if reset and power information should be 33 | reported 34 | --debug Creates debug file showing HTTP traces and exceptions 35 | ``` 36 | 37 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 38 | It then traverses the system collection for the service to find the matching system specified by the *system* argument. 39 | It will perform the `Reset` action with the specified reset type from the *type* argument. 40 | 41 | * If *system* is not specified, and if the service has exactly one system, it will perform the operation on the one system. 42 | * If *type* is not specified, it will attempt a `GracefulRestart`. 43 | 44 | Example: 45 | 46 | ``` 47 | $ rf_power_reset.py -u root -p root -r https://192.168.1.100 -t GracefulRestart` 48 | Resetting the system... 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/rf_raw_request.md: -------------------------------------------------------------------------------- 1 | # Raw Request (rf_raw_request.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool perform a raw request to a Redfish service. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_raw_request.py [-h] --user USER --password PASSWORD --rhost RHOST 13 | [--method {GET,HEAD,POST,PATCH,PUT,DELETE}] --request 14 | REQUEST [--body BODY] [--verbose] 15 | 16 | A tool perform a raw request to a Redfish service 17 | 18 | required arguments: 19 | --user USER, -u USER The user name for authentication 20 | --password PASSWORD, -p PASSWORD 21 | The password for authentication 22 | --rhost RHOST, -r RHOST 23 | The address of the Redfish service (with scheme) 24 | --request REQUEST, -req REQUEST 25 | The URI for the request 26 | 27 | optional arguments: 28 | -h, --help show this help message and exit 29 | --method {GET,HEAD,POST,PATCH,PUT,DELETE}, -m {GET,HEAD,POST,PATCH,PUT,DELETE} 30 | The HTTP method to perform; performs GET if not 31 | specified 32 | --body BODY, -b BODY The body to provide with the request; can be a JSON 33 | string for a JSON request, a filename to send binary 34 | data, or an unstructured string 35 | --verbose, -v Indicates if HTTP response codes and headers are 36 | displayed 37 | ``` 38 | 39 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 40 | It will then perform the requested method on the specified URI with an optional body, specified by the *method*, *request*, and *body* arguments. 41 | It will then display the response of the operation from the service. 42 | 43 | Example; GET operation: 44 | 45 | ``` 46 | $ rf_raw_request.py -u root -p root -r https://192.168.1.100 -req /redfish/v1/SessionService 47 | { 48 | "@odata.id": "/redfish/v1/SessionService", 49 | "@odata.type": "#SessionService.v1_1_8.SessionService", 50 | "Description": "Session Service", 51 | "Id": "SessionService", 52 | "Name": "Session Service", 53 | "ServiceEnabled": true, 54 | "SessionTimeout": 30, 55 | "Sessions": { 56 | "@odata.id": "/redfish/v1/SessionService/Sessions" 57 | }, 58 | "Status": { 59 | "Health": "OK", 60 | "State": "Enabled" 61 | } 62 | } 63 | ``` 64 | 65 | Example; PATCH operation: 66 | 67 | ``` 68 | $ rf_raw_request.py -u root -p root -r https://192.168.1.100 -req /redfish/v1/Systems/1 -m PATCH -b '{"AssetTag": "New tag"}' 69 | No response body 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/rf_sel.md: -------------------------------------------------------------------------------- 1 | # SEL (rf_sel.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to manage the SEL on a Redfish service. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_sel.py [-h] --user USER --password PASSWORD --rhost RHOST 13 | [--details] [--clear] [--debug] 14 | 15 | A tool to manage the SEL on a Redfish service 16 | 17 | required arguments: 18 | --user USER, -u USER The user name for authentication 19 | --password PASSWORD, -p PASSWORD 20 | The password for authentication 21 | --rhost RHOST, -r RHOST 22 | The address of the Redfish service (with scheme) 23 | 24 | optional arguments: 25 | -h, --help show this help message and exit 26 | --details, -details Indicates details to be shown for each log entry 27 | --clear, -clear Indicates if the log should be cleared 28 | --debug Creates debug file showing HTTP traces and exceptions 29 | ``` 30 | 31 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 32 | It will then attempt to locate the SEL via the following logic: 33 | 34 | * It will iterate through each log service found in each manager and each system. 35 | * The first log service found where `LogEntryType` contains `SEL` is considered the SEL for the service. 36 | 37 | Once the SEL is found, the tool will either perform the `ClearLog` action if *clear* is provided, or read and display the log entries. 38 | 39 | Example: 40 | 41 | ``` 42 | $ rf_sel.py -u root -p root -r https://192.168.1.100 43 | Id | Timestamp | Message 44 | 1 | 2012-03-07T14:44:00Z | System May be Melting 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/rf_sys_inventory.md: -------------------------------------------------------------------------------- 1 | # System Inventory (rf_sys_inventory.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to walk a Redfish service and list component information. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_sys_inventory.py [-h] --user USER --password PASSWORD --rhost RHOST 13 | [--details] [--noabsent] [--write [WRITE]] 14 | [--workaround] [--debug] 15 | 16 | A tool to walk a Redfish service and list component information 17 | 18 | required arguments: 19 | --user USER, -u USER The user name for authentication 20 | --password PASSWORD, -p PASSWORD 21 | The password for authentication 22 | --rhost RHOST, -r RHOST 23 | The address of the Redfish service (with scheme) 24 | 25 | optional arguments: 26 | -h, --help show this help message and exit 27 | --details, -details Indicates if the full details of each component should 28 | be shown 29 | --noabsent, -noabsent 30 | Indicates if absent devices should be skipped 31 | --write [WRITE], -w [WRITE] 32 | Indicates if the inventory should be written to a 33 | spreadsheet and what the file name should be if given 34 | --workaround, -workaround 35 | Indicates if workarounds should be attempted for non- 36 | conformant services 37 | --debug Creates debug file showing HTTP traces and exceptions 38 | ``` 39 | 40 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 41 | It then traverses the chassis collection for the service, and collects component information for processors, memory, drives, PCIe devices, network adapters, and storage controllers. 42 | Using the information collected, it will build an inventory table and print the information. 43 | 44 | Example: 45 | 46 | ``` 47 | $ rf_sys_inventory.py -u root -p root -r https://192.168.1.100 -details 48 | '1U' Inventory 49 | Name | Description 50 | Chassis: 1U | 3500RX 51 | | Manufacturer: Contoso 52 | | Model: 3500RX 53 | | SKU: 8675309 54 | | PartNumber: 224071-J23 55 | | SerialNumber: 437XR1138R2 56 | | AssetTag: Chicago-45Z-2381 57 | Processor: CPU1 | Multi-Core Intel(R) Xeon(R) processor 7xxx Series 58 | | Manufacturer: Intel(R) Corporation 59 | | Model: Multi-Core Intel(R) Xeon(R) processor 7xxx Series 60 | Processor: CPU2 | Not Present 61 | Processor: FPGA1 | Stratix 10 62 | | Manufacturer: Intel(R) Corporation 63 | | Model: Stratix 10 64 | Memory: DIMM1 | 32768MB DDR4 DRAM 65 | Memory: DIMM2 | 32768MB DDR4 DRAM 66 | Memory: DIMM3 | 32768MB DDR4 DRAM 67 | Memory: DIMM4 | Not Present 68 | Drive: SATA Bay 1 | Contoso 7450GB Drive 69 | | Manufacturer: Contoso 70 | | Model: 3000GT8 71 | Drive: SATA Bay 2 | Contoso 3725GB Drive 72 | | Manufacturer: Contoso 73 | | Model: 3000GT7 74 | Drive: SATA Bay 3 | Not Present 75 | Drive: SATA Bay 4 | Not Present 76 | 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/rf_test_event_listener.md: -------------------------------------------------------------------------------- 1 | # Test Event Listener (rf_test_event_listener.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to help verify a Redfish event listener. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_test_event_listener.py [-h] --listener LISTENER [--file FILE] 13 | [--id ID] [--name NAME] [--context CONTEXT] 14 | [--eventtype EVENTTYPE] [--eventid EVENTID] 15 | [--severity SEVERITY] [--message MESSAGE] 16 | [--messageid MESSAGEID] 17 | [--timestamp TIMESTAMP] [--header name value] 18 | 19 | A tool to help verify a Redfish event listener 20 | 21 | required arguments: 22 | --listener LISTENER, -l LISTENER 23 | The absolute URI of the Redfish event listener (with 24 | scheme) 25 | 26 | optional arguments: 27 | -h, --help show this help message and exit 28 | --file FILE, -file FILE 29 | The filepath to a JSON file containing the event 30 | payload; if this argument is specified, all other 31 | arguments controlling the event data is ignored 32 | --id ID, -id ID The value to specify in the Id property of the event 33 | --name NAME, -name NAME 34 | The value to specify in the Name property of the event 35 | --context CONTEXT, -context CONTEXT 36 | The value to specify in the Context property of the 37 | event 38 | --eventtype EVENTTYPE, -eventtype EVENTTYPE 39 | The value to specify in the EventType property of the 40 | event 41 | --eventid EVENTID, -eventid EVENTID 42 | The value to specify in the EventId property of the 43 | event 44 | --severity SEVERITY, -severity SEVERITY 45 | The value to specify in the Severity property of the 46 | event 47 | --message MESSAGE, -message MESSAGE 48 | The value to specify in the Message property of the 49 | event 50 | --messageid MESSAGEID, -messageid MESSAGEID 51 | The value to specify in the MessageId property of the 52 | event 53 | --timestamp TIMESTAMP, -timestamp TIMESTAMP 54 | The value to specify in the EventTimestamp property of 55 | the event 56 | --header name value, -header name value 57 | Name-value pairs of HTTP headers to provide with the 58 | request 59 | ``` 60 | 61 | If the *file* argument is present, the payload is constructed entirely from the contents of the referenced file. 62 | If the *file* argument is not present, all other arguments are used to build the event payload. 63 | Once the event payload is constructed, the tool will perform a `POST` operations to the URI specified by the *listener* argument with additional HTTP headers specified by the *header* argument. 64 | 65 | Example: 66 | 67 | ``` 68 | $ rf_test_event_listener.py -l https://redfishlistener.contoso.org 69 | Listener responded with 204 No Content 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/rf_update.md: -------------------------------------------------------------------------------- 1 | # Update (rf_update.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to perform an update with a Redfish service. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_update.py [-h] --user USER --password PASSWORD --rhost RHOST --image 13 | IMAGE [--target TARGET] 14 | [--applytime {Immediate,OnReset,AtMaintenanceWindowStart,InMaintenanceWindowOnReset,OnStartUpdateRequest}] 15 | [--timeout TIMEOUT] [--debug] 16 | 17 | A tool to perform an update with a Redfish service 18 | 19 | required arguments: 20 | --user USER, -u USER The user name for authentication 21 | --password PASSWORD, -p PASSWORD 22 | The password for authentication 23 | --rhost RHOST, -r RHOST 24 | The address of the Redfish service (with scheme) 25 | --image IMAGE, -i IMAGE 26 | The URI or filepath of the image 27 | 28 | optional arguments: 29 | -h, --help show this help message and exit 30 | --target TARGET, -t TARGET 31 | The target resource to apply the image 32 | --applytime {Immediate,OnReset,AtMaintenanceWindowStart,InMaintenanceWindowOnReset,OnStartUpdateRequest}, -at {Immediate,OnReset,AtMaintenanceWindowStart,InMaintenanceWindowOnReset,OnStartUpdateRequest} 33 | The apply time for the update 34 | --timeout TIMEOUT, -timeout TIMEOUT 35 | The timeout, in seconds, to transfer the image; by 36 | default this is 2 seconds per MB 37 | --debug Creates debug file showing HTTP traces and exceptions 38 | ``` 39 | 40 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 41 | It then builds a request payload to perform a `SimpleUpdate` action against the update service using the image specified by the *image* argument. 42 | The optional *target* argument is used in the request if attempting to update a particular system, device, manager, or other resource. 43 | Once the `SimpleUpdate` is requested, it monitors the progress of the update, and displays response messages reported by the service about the update once complete. 44 | 45 | Example: 46 | 47 | ``` 48 | $ rf_update.py -u root -p root -r https://192.168.1.100 -i image.bin 49 | 50 | Pushing the image to the service directly; depending on the size of the image, this can take a few minutes... 51 | Update initiated... 52 | Task is Done! 53 | 54 | Success 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/rf_virtual_media.md: -------------------------------------------------------------------------------- 1 | # Virtual Media (rf_virtual_media.py) 2 | 3 | Copyright 2019-2025 DMTF. All rights reserved. 4 | 5 | ## About 6 | 7 | A tool to manage virtual media of a system. 8 | 9 | ## Usage 10 | 11 | ``` 12 | usage: rf_virtual_media.py [-h] --user USER --password PASSWORD --rhost RHOST 13 | [--system SYSTEM] [--debug] 14 | {info,insert,eject} ... 15 | 16 | A tool to manage virtual media of a system 17 | 18 | positional arguments: 19 | {info,insert,eject} 20 | info Displays information about the virtual media for a 21 | system 22 | insert Inserts virtual media for a system 23 | eject Ejects virtual media from a system 24 | 25 | required arguments: 26 | --user USER, -u USER The user name for authentication 27 | --password PASSWORD, -p PASSWORD 28 | The password for authentication 29 | --rhost RHOST, -r RHOST 30 | The address of the Redfish service (with scheme) 31 | 32 | optional arguments: 33 | -h, --help show this help message and exit 34 | --system SYSTEM, -s SYSTEM 35 | The ID of the system containing the virtual media 36 | --debug Creates debug file showing HTTP traces and exceptions 37 | ``` 38 | 39 | ### Info 40 | 41 | Displays information about the virtual media for a system. 42 | 43 | ``` 44 | usage: rf_virtual_media.py info [-h] 45 | 46 | optional arguments: 47 | -h, --help show this help message and exit 48 | ``` 49 | 50 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 51 | It will then locate the system specified by the *system* argument, find its virtual media collection, and display the virtual media instances. 52 | 53 | Example: 54 | 55 | ``` 56 | $ rf_virtual_media.py -u root -p root -r https://192.168.1.100 info 57 | 58 | Floppy1 | ImageName: Sardine2.1.43.35.6a 59 | | Image: https://www.dmtf.org/freeImages/Sardine.img 60 | | MediaTypes: Floppy, USBStick 61 | | ConnectedVia: URI 62 | | Inserted: True 63 | | WriteProtected: False 64 | 65 | CD1 | ImageName: mymedia-read-only 66 | | Image: redfish.dmtf.org/freeImages/freeOS.1.1.iso 67 | | MediaTypes: CD, DVD 68 | | ConnectedVia: Applet 69 | | Inserted: True 70 | | WriteProtected: False 71 | 72 | ``` 73 | 74 | ### Insert 75 | 76 | Inserts virtual media for a system. 77 | 78 | ``` 79 | usage: rf_virtual_media.py insert [-h] --image IMAGE [--id ID] [--notinserted] 80 | [--writable] 81 | [--mediatypes MEDIATYPES [MEDIATYPES ...]] 82 | 83 | required arguments: 84 | --image IMAGE, -image IMAGE 85 | The URI of the image to insert 86 | 87 | optional arguments: 88 | -h, --help show this help message and exit 89 | --id ID, -i ID The identifier of the virtual media instance to insert 90 | --notinserted, -notinserted 91 | Indicates if the media is to be marked as not inserted 92 | for the system 93 | --writable, -writable 94 | Indicates if the media is to be marked as writable for 95 | the system 96 | --mediatypes MEDIATYPES [MEDIATYPES ...], -mt MEDIATYPES [MEDIATYPES ...] 97 | A list of acceptable media types for the virtual media 98 | ``` 99 | 100 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 101 | It will then locate the system specified by the *system* argument, find its virtual media collection. 102 | It will then iterate through the virtual media collection, and insert the virtual media specified by the *image* argument in an appropriate slot. 103 | 104 | Example: 105 | 106 | ``` 107 | $ rf_virtual_media.py -u root -p root -r https://192.168.1.100 insert -image http://somefileserver/my_media.iso 108 | Inserting 'http://somefileserver/my_media.iso' 109 | ``` 110 | 111 | ### Eject 112 | 113 | Ejects virtual media from a system. 114 | 115 | ``` 116 | usage: rf_virtual_media.py eject [-h] --id ID 117 | 118 | required arguments: 119 | --id ID, -i ID The identifier of the virtual media instance to eject 120 | 121 | optional arguments: 122 | -h, --help show this help message and exit 123 | ``` 124 | 125 | The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments. 126 | It will then locate the system specified by the *system* argument, find its virtual media collection. 127 | It will then locate the virtual media instance with matching `Id` property with the *id* argument, and then eject the media. 128 | 129 | Example: 130 | 131 | ``` 132 | $ rf_virtual_media.py -u root -p root -r https://192.168.1.100 eject --id CD1 133 | Ejecting 'CD1' 134 | ``` 135 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 120 3 | 4 | # uv package manager 5 | [tool.uv] 6 | native-tls = true 7 | -------------------------------------------------------------------------------- /redfish_utilities/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | from .accounts import get_users 7 | from .accounts import print_users 8 | from .accounts import add_user 9 | from .accounts import delete_user 10 | from .accounts import modify_user 11 | from .assembly import get_assembly 12 | from .assembly import print_assembly 13 | from .assembly import download_assembly 14 | from .assembly import upload_assembly 15 | from .certificates import get_all_certificates 16 | from .certificates import print_certificates 17 | from .certificates import get_generate_csr_info 18 | from .certificates import generate_csr 19 | from .certificates import install_certificate 20 | from .certificates import delete_certificate 21 | from .event_service import get_event_service 22 | from .event_service import print_event_service 23 | from .event_service import get_event_subscriptions 24 | from .event_service import print_event_subscriptions 25 | from .event_service import create_event_subscription 26 | from .event_service import delete_event_subscription 27 | from .inventory import get_system_inventory 28 | from .inventory import print_system_inventory 29 | from .inventory import write_system_inventory 30 | from .licenses import get_licenses 31 | from .licenses import print_licenses 32 | from .licenses import install_license 33 | from .licenses import delete_license 34 | from .logs import log_container 35 | from .logs import diagnostic_data_types 36 | from .logs import get_log_service_ids 37 | from .logs import get_log_service 38 | from .logs import get_log_entries 39 | from .logs import print_log_entries 40 | from .logs import clear_log_entries 41 | from .logs import collect_diagnostic_data 42 | from .logs import download_diagnostic_data 43 | from .managers import get_manager_ids 44 | from .managers import get_manager 45 | from .managers import set_manager 46 | from .managers import print_manager 47 | from .managers import get_manager_reset_info 48 | from .managers import manager_reset 49 | from .managers import get_manager_reset_to_defaults_info 50 | from .managers import manager_reset_to_defaults 51 | from .managers import get_manager_network_protocol 52 | from .managers import set_manager_network_protocol 53 | from .managers import print_manager_network_protocol 54 | from .managers import get_manager_ethernet_interface_ids 55 | from .managers import get_manager_ethernet_interface 56 | from .managers import set_manager_ethernet_interface 57 | from .managers import print_manager_ethernet_interface 58 | from .messages import print_error_payload 59 | from .messages import verify_response 60 | from .power_equipment import power_equipment_types 61 | from .power_equipment import power_equipment_electrical_types 62 | from .power_equipment import get_power_equipment_ids 63 | from .power_equipment import get_power_equipment 64 | from .power_equipment import print_power_equipment 65 | from .power_equipment import get_power_equipment_summary 66 | from .power_equipment import print_power_equipment_summary 67 | from .power_equipment import get_power_equipment_electrical 68 | from .power_equipment import print_power_equipment_electrical 69 | from .power_equipment import print_power_equipment_electrical_summary 70 | from .resets import reset_types 71 | from .resets import reset_to_defaults_types 72 | from .sensors import get_sensors 73 | from .sensors import print_sensors 74 | from .systems import get_system_ids 75 | from .systems import get_system 76 | from .systems import get_system_boot 77 | from .systems import set_system_boot 78 | from .systems import print_system_boot 79 | from .systems import get_system_reset_info 80 | from .systems import system_reset 81 | from .systems import get_virtual_media 82 | from .systems import print_virtual_media 83 | from .systems import insert_virtual_media 84 | from .systems import eject_virtual_media 85 | from .systems import get_system_bios 86 | from .systems import set_system_bios 87 | from .systems import print_system_bios 88 | from .systems import reset_system_bios 89 | from .tasks import poll_task_monitor 90 | from .thermal_equipment import thermal_equipment_types 91 | from .thermal_equipment import thermal_equipment_component_types 92 | from .thermal_equipment import get_thermal_equipment_ids 93 | from .thermal_equipment import get_thermal_equipment 94 | from .thermal_equipment import print_thermal_equipment 95 | from .thermal_equipment import get_thermal_equipment_summary 96 | from .thermal_equipment import print_thermal_equipment_summary 97 | from .thermal_equipment import get_thermal_equipment_component 98 | from .thermal_equipment import print_thermal_equipment_component 99 | from .thermal_equipment import print_thermal_equipment_component_summary 100 | from .thermal_equipment import print_thermal_equipment_leak_detector_summary 101 | from .update import operation_apply_times 102 | from .update import get_update_service 103 | from .update import get_simple_update_info 104 | from .update import simple_update 105 | from .update import multipart_push_update 106 | from .update import get_firmware_inventory 107 | from .update import print_software_inventory 108 | from .misc import logout, print_password_change_required_and_logout 109 | 110 | from . import config 111 | 112 | __all__ = [ 113 | "get_users", 114 | "print_users", 115 | "add_user", 116 | "delete_user", 117 | "modify_user", 118 | "get_assembly", 119 | "print_assembly", 120 | "download_assembly", 121 | "upload_assembly", 122 | "get_all_certificates", 123 | "print_certificates", 124 | "get_generate_csr_info", 125 | "generate_csr", 126 | "install_certificate", 127 | "delete_certificate", 128 | "get_event_service", 129 | "print_event_service", 130 | "get_event_subscriptions", 131 | "print_event_subscriptions", 132 | "create_event_subscription", 133 | "delete_event_subscription", 134 | "get_system_inventory", 135 | "print_system_inventory", 136 | "write_system_inventory", 137 | "get_licenses", 138 | "print_licenses", 139 | "install_license", 140 | "delete_license", 141 | "log_container", 142 | "diagnostic_data_types", 143 | "get_log_service_ids", 144 | "get_log_service", 145 | "get_log_entries", 146 | "print_log_entries", 147 | "clear_log_entries", 148 | "collect_diagnostic_data", 149 | "download_diagnostic_data", 150 | "get_manager_ids", 151 | "get_manager", 152 | "set_manager", 153 | "print_manager", 154 | "get_manager_reset_info", 155 | "manager_reset", 156 | "get_manager_reset_to_defaults_info", 157 | "manager_reset_to_defaults", 158 | "get_manager_network_protocol", 159 | "set_manager_network_protocol", 160 | "print_manager_network_protocol", 161 | "get_manager_ethernet_interface_ids", 162 | "get_manager_ethernet_interface", 163 | "set_manager_ethernet_interface", 164 | "print_manager_ethernet_interface", 165 | "print_error_payload", 166 | "verify_response", 167 | "power_equipment_types", 168 | "power_equipment_electrical_types", 169 | "get_power_equipment_ids", 170 | "get_power_equipment", 171 | "print_power_equipment", 172 | "get_power_equipment_summary", 173 | "print_power_equipment_summary", 174 | "get_power_equipment_electrical", 175 | "print_power_equipment_electrical", 176 | "print_power_equipment_electrical_summary", 177 | "reset_types", 178 | "reset_to_defaults_types", 179 | "get_sensors", 180 | "print_sensors", 181 | "get_system_ids", 182 | "get_system", 183 | "get_system_boot", 184 | "set_system_boot", 185 | "print_system_boot", 186 | "get_system_reset_info", 187 | "system_reset", 188 | "get_virtual_media", 189 | "print_virtual_media", 190 | "insert_virtual_media", 191 | "eject_virtual_media", 192 | "get_system_bios", 193 | "set_system_bios", 194 | "print_system_bios", 195 | "reset_system_bios", 196 | "poll_task_monitor", 197 | "thermal_equipment_types", 198 | "thermal_equipment_component_types", 199 | "get_thermal_equipment_ids", 200 | "get_thermal_equipment", 201 | "print_thermal_equipment", 202 | "get_thermal_equipment_summary", 203 | "print_thermal_equipment_summary", 204 | "get_thermal_equipment_component", 205 | "print_thermal_equipment_component", 206 | "print_thermal_equipment_component_summary", 207 | "print_thermal_equipment_leak_detector_summary", 208 | "operation_apply_times", 209 | "get_update_service", 210 | "get_simple_update_info", 211 | "simple_update", 212 | "multipart_push_update", 213 | "get_firmware_inventory", 214 | "print_software_inventory", 215 | "logout", 216 | "print_password_change_required_and_logout", 217 | "config", 218 | ] 219 | -------------------------------------------------------------------------------- /redfish_utilities/assembly.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Assembly Module 8 | 9 | File : assembly.py 10 | 11 | Brief : This file contains the definitions and functionalities for managing 12 | assemblies on a Redfish service 13 | """ 14 | 15 | from .messages import verify_response 16 | 17 | 18 | class RedfishAssemblyNotFoundError(Exception): 19 | """ 20 | Raised when an assembly index cannot be found 21 | """ 22 | 23 | pass 24 | 25 | 26 | class RedfishAssemblyNoBinaryDataError(Exception): 27 | """ 28 | Raised when an assembly index does not contain binary data 29 | """ 30 | 31 | pass 32 | 33 | 34 | def get_assembly(context, uri): 35 | """ 36 | Collects assembly information from a Redfish service 37 | 38 | Args: 39 | context: The Redfish client object with an open session 40 | uri: The URI of the assembly to get 41 | 42 | Returns: 43 | A list containing all assemblies from the URI 44 | """ 45 | 46 | # Get the assembly 47 | assembly = context.get(uri) 48 | verify_response(assembly) 49 | return assembly.dict.get("Assemblies", []) 50 | 51 | 52 | def print_assembly(assemblies, index=None): 53 | """ 54 | Prints assembly information into a table 55 | 56 | Args: 57 | assemblies: An array of assembly information to print 58 | index: If specified, prints only the desired index 59 | """ 60 | 61 | assembly_format_header = " {:5s} | {} {}" 62 | assembly_format = " {:5s} | {}: {}" 63 | assembly_properties = [ 64 | "Model", 65 | "PartNumber", 66 | "SparePartNumber", 67 | "SKU", 68 | "SerialNumber", 69 | "Producer", 70 | "Vendor", 71 | "ProductionDate", 72 | "Version", 73 | "EngineeringChangeLevel", 74 | ] 75 | 76 | # If an index is specified, isolate to the one index 77 | if index is not None: 78 | if index < 0 or index >= len(assemblies): 79 | raise RedfishAssemblyNotFoundError( 80 | "Assembly contains {} entries; index {} is not valid".format(len(assemblies), index) 81 | ) 82 | assemblies = [assemblies[index]] 83 | 84 | # Go through each assembly 85 | for assembly in assemblies: 86 | # Print the heading 87 | heading_details = [] 88 | state = assembly.get("Status", {}).get("State") 89 | if state: 90 | heading_details.append(state) 91 | health = assembly.get("Status", {}).get("Health") 92 | if health: 93 | heading_details.append(health) 94 | heading_details = ", ".join(heading_details) 95 | if len(heading_details) != 0: 96 | heading_details = "(" + heading_details + ")" 97 | print(assembly_format_header.format(assembly["MemberId"], assembly["Name"], heading_details)) 98 | 99 | # Print any of the found properties 100 | for property in assembly_properties: 101 | if property in assembly: 102 | print(assembly_format.format("", property, assembly[property])) 103 | 104 | 105 | def download_assembly(context, assemblies, filepath, index=None): 106 | """ 107 | Downloads the binary data of an assembly to a file 108 | 109 | Args: 110 | context: The Redfish client object with an open session 111 | assemblies: An array of assembly information 112 | filepath: The filepath to download the binary data 113 | index: The index into the assemblies array to download; if None, perform on index 0 if there's only 1 assembly 114 | """ 115 | 116 | # Get the binary data URI 117 | binary_data_uri = get_assembly_binary_data_uri(assemblies, index) 118 | 119 | # Download the data and save it 120 | response = context.get(binary_data_uri) 121 | verify_response(response) 122 | with open(filepath, "wb") as binary_file: 123 | binary_file.write(response.read) 124 | 125 | 126 | def upload_assembly(context, assemblies, filepath, index=None): 127 | """ 128 | Uploads the binary data of a file to an assembly 129 | 130 | Args: 131 | context: The Redfish client object with an open session 132 | assemblies: An array of assembly information 133 | filepath: The filepath of the binary data to upload 134 | index: The index into the assemblies array to upload; if None, perform on index 0 if there's only 1 assembly 135 | """ 136 | 137 | # Get the binary data URI 138 | binary_data_uri = get_assembly_binary_data_uri(assemblies, index) 139 | 140 | # Upload the binary data 141 | with open(filepath, "rb") as binary_file: 142 | data = binary_file.read() 143 | response = context.put(binary_data_uri, body=data) 144 | verify_response(response) 145 | 146 | 147 | def get_assembly_binary_data_uri(assemblies, index=None): 148 | """ 149 | Locates the binary data URI for a target assembly 150 | 151 | Args: 152 | assemblies: An array of assembly information 153 | index: The index into the assemblies array to download; if None, perform on index 0 if there's only 1 assembly 154 | 155 | Returns: 156 | A string containing the binary data URI 157 | """ 158 | 159 | # If an index is specified, isolate to the one index 160 | if index is None: 161 | index = 0 162 | if len(assemblies) != 1: 163 | raise RedfishAssemblyNotFoundError( 164 | "Assembly contains {} entries; an index needs to be specified".format(len(assemblies)) 165 | ) 166 | else: 167 | if index < 0 or index >= len(assemblies): 168 | raise RedfishAssemblyNotFoundError( 169 | "Assembly contains {} entries; index {} is not valid".format(len(assemblies), index) 170 | ) 171 | 172 | # Get the binary data URI 173 | binary_data_uri = assemblies[index].get("BinaryDataURI") 174 | if binary_data_uri is None: 175 | # No binary data 176 | raise RedfishAssemblyNoBinaryDataError("Assembly index {} does not contain binary data".format(index)) 177 | return binary_data_uri 178 | -------------------------------------------------------------------------------- /redfish_utilities/collections.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Collections Module 8 | 9 | File : collections.py 10 | 11 | Brief : This file contains the definitions and functionalities for performing 12 | operations with resource collections 13 | """ 14 | 15 | from .messages import verify_response 16 | 17 | 18 | class RedfishCollectionNotFoundError(Exception): 19 | """ 20 | Raised when the specified collection is not found (HTTP Status = 404) 21 | """ 22 | 23 | pass 24 | 25 | 26 | class RedfishCollectionMemberNotFoundError(Exception): 27 | """ 28 | Raised when the specified member is not found (HTTP Status = 404) 29 | """ 30 | 31 | pass 32 | 33 | 34 | def get_collection_ids(context, collection_uri): 35 | """ 36 | Iterates over a collection and returns the identifiers of all members 37 | 38 | Args: 39 | context: The Redfish client object with an open session 40 | collection_uri: The URI of the collection to process 41 | 42 | Returns: 43 | A list of identifiers of the members of the collection 44 | """ 45 | 46 | # Get the collection and iterate through its collection 47 | avail_members = [] 48 | collection = context.get(collection_uri) 49 | if collection.status == 404: 50 | raise RedfishCollectionNotFoundError("Service does not contain a collection at URI {}".format(collection_uri)) 51 | verify_response(collection) 52 | while True: 53 | for member in collection.dict["Members"]: 54 | avail_members.append(member["@odata.id"].strip("/").split("/")[-1]) 55 | if "Members@odata.nextLink" not in collection.dict: 56 | break 57 | collection = context.get(collection.dict["Members@odata.nextLink"]) 58 | verify_response(collection) 59 | 60 | return avail_members 61 | 62 | 63 | def get_collection_members(context, collection_uri): 64 | """ 65 | Iterates over a collection and returns all members 66 | 67 | Args: 68 | context: The Redfish client object with an open session 69 | collection_uri: The URI of the collection to process 70 | 71 | Returns: 72 | A list of the members of the collection 73 | """ 74 | 75 | # Get the collection and iterate through its collection 76 | members = [] 77 | collection = context.get(collection_uri) 78 | if collection.status == 404: 79 | raise RedfishCollectionNotFoundError("Service does not contain a collection at URI {}".format(collection_uri)) 80 | verify_response(collection) 81 | while True: 82 | for member in collection.dict["Members"]: 83 | member_response = context.get(member["@odata.id"]) 84 | verify_response(member_response) 85 | members.append(member_response.dict) 86 | if "Members@odata.nextLink" not in collection.dict: 87 | break 88 | collection = context.get(collection.dict["Members@odata.nextLink"]) 89 | verify_response(collection) 90 | 91 | return members 92 | -------------------------------------------------------------------------------- /redfish_utilities/config.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Configuration Settings 8 | 9 | File : config.py 10 | 11 | Brief : This file contains configuration settings for the module; useful for 12 | debugging issues encountered on live systems or configuring global 13 | options. 14 | """ 15 | 16 | # Leverage known workarounds for non-conformant services, such as bypassing 17 | # unexpected 4XX responses, missing properties, and malformed URIs 18 | __workarounds__ = False 19 | 20 | # Automate task handling for POST/PATCH/PUT/DELETE operations that should 21 | # always be "fast" 22 | __auto_task_handling__ = False 23 | -------------------------------------------------------------------------------- /redfish_utilities/event_service.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Event Service Module 8 | 9 | File : event_service.py 10 | 11 | Brief : This file contains the definitions and functionalities for managing 12 | accounts on a Redfish Service 13 | """ 14 | 15 | from .messages import verify_response 16 | 17 | 18 | class RedfishEventServiceNotFoundError(Exception): 19 | """ 20 | Raised when the event service cannot be found 21 | """ 22 | 23 | pass 24 | 25 | 26 | class RedfishEventSubscriptionNotFoundError(Exception): 27 | """ 28 | Raised when the specified event subscription cannot be found 29 | """ 30 | 31 | pass 32 | 33 | 34 | def get_event_service(context): 35 | """ 36 | Collects the event service information from a Redfish service 37 | 38 | Args: 39 | context: The Redfish client object with an open session 40 | 41 | Returns: 42 | An object containing information about the event service 43 | """ 44 | 45 | # Get the service root to find the event service 46 | service_root = context.get("/redfish/v1/") 47 | if "EventService" not in service_root.dict: 48 | # No event service 49 | raise RedfishEventServiceNotFoundError("Service does not contain an event service") 50 | 51 | # Get the event service 52 | event_service = context.get(service_root.dict["EventService"]["@odata.id"]) 53 | return event_service.dict 54 | 55 | 56 | def print_event_service(service): 57 | """ 58 | Prints the event service information from a Redfish service 59 | 60 | Args: 61 | service: The event service object 62 | """ 63 | 64 | print("Service Info") 65 | 66 | # General status 67 | status = "Enabled" 68 | if "ServiceEnabled" in service: 69 | if not service["ServiceEnabled"]: 70 | status = "Disabled" 71 | if "Status" in service: 72 | if "State" in service["Status"]: 73 | status = service["Status"]["State"] 74 | print(" Status: {}".format(status)) 75 | 76 | # Delivery retry policy 77 | retry_policy = "" 78 | if "DeliveryRetryAttempts" in service: 79 | retry_policy += "{} attempts, ".format(service["DeliveryRetryAttempts"]) 80 | if "DeliveryRetryIntervalSeconds" in service: 81 | retry_policy += "{} second intervals, ".format(service["DeliveryRetryIntervalSeconds"]) 82 | if len(retry_policy) > 2: 83 | retry_policy = retry_policy[:-2] 84 | else: 85 | retry_policy = "Unknown/Unspecified" 86 | print(" Delivery Retry Policy: {}".format(retry_policy)) 87 | 88 | # Subscription methods 89 | print(" Event Types: {}".format(", ".join(service.get("EventTypesForSubscription", ["N/A"])))) 90 | print(" Event Formats: {}".format(", ".join(service.get("EventFormatTypes", ["N/A"])))) 91 | print(" Registries: {}".format(", ".join(service.get("RegistryPrefixes", ["N/A"])))) 92 | print(" Resource Types: {}".format(", ".join(service.get("ResourceTypes", ["N/A"])))) 93 | print(" Include Origin of Condition Supported: {}".format(service.get("IncludeOriginOfConditionSupported", False))) 94 | 95 | # SSE info 96 | print(" SSE URI: {}".format(service.get("ServerSentEventUri", "Not supported"))) 97 | sse_filters = "" 98 | for filter in service.get("SSEFilterPropertiesSupported", {}): 99 | if filter == "EventType": 100 | # This style is deprecated 101 | continue 102 | if service["SSEFilterPropertiesSupported"][filter]: 103 | sse_filters += "{}, ".format(filter) 104 | if len(sse_filters) > 2: 105 | sse_filters = sse_filters[:-2] 106 | else: 107 | sse_filters = "Unknown/Unspecified" 108 | print(" SSE Filter Parameters: {}".format(sse_filters)) 109 | 110 | 111 | def get_event_subscriptions(context): 112 | """ 113 | Collects the event subscription information from a Redfish service 114 | 115 | Args: 116 | context: The Redfish client object with an open session 117 | 118 | Returns: 119 | A list containing all event subscriptions 120 | """ 121 | 122 | # Get the event service 123 | event_service = get_event_service(context) 124 | if "Subscriptions" not in event_service: 125 | # No subscriptions 126 | raise RedfishEventServiceNotFoundError("Service does not contain a subscription collection") 127 | 128 | # Get each of the event subscriptions 129 | subscriptions = [] 130 | subscription_col = context.get(event_service["Subscriptions"]["@odata.id"]) 131 | for subscription_member in subscription_col.dict["Members"]: 132 | subscription = context.get(subscription_member["@odata.id"]) 133 | subscriptions.append(subscription.dict) 134 | return subscriptions 135 | 136 | 137 | def print_event_subscriptions(subscriptions): 138 | """ 139 | Prints the event subscription information from a Redfish service 140 | 141 | Args: 142 | subscriptions: An array of event subscription objects 143 | """ 144 | 145 | subscription_line_format = " {:36s} | {}: {}" 146 | 147 | print("Subscription Info") 148 | 149 | if len(subscriptions) == 0: 150 | print(" No subscriptions") 151 | return 152 | 153 | for subscription in subscriptions: 154 | print( 155 | subscription_line_format.format(subscription["Id"], "Destination", subscription.get("Destination", "N/A")) 156 | ) 157 | print(subscription_line_format.format("", "State", subscription.get("Status", {}).get("State", "Enabled"))) 158 | if "Context" in subscription: 159 | print(subscription_line_format.format("", "Context", subscription["Context"])) 160 | print(subscription_line_format.format("", "Event Format", subscription.get("EventFormatType", "Event"))) 161 | if "EventTypes" in subscription: 162 | print(subscription_line_format.format("", "Event Types", ", ".join(subscription["EventTypes"]))) 163 | if "RegistryPrefixes" in subscription: 164 | print(subscription_line_format.format("", "Registries", ", ".join(subscription["RegistryPrefixes"]))) 165 | if "ResourceTypes" in subscription: 166 | print(subscription_line_format.format("", "Resource Types", ", ".join(subscription["ResourceTypes"]))) 167 | 168 | 169 | def create_event_subscription( 170 | context, 171 | destination, 172 | format=None, 173 | client_context=None, 174 | expand=None, 175 | resource_types=None, 176 | registries=None, 177 | message_ids=None, 178 | origins=None, 179 | subordinate_resources=None, 180 | event_types=None, 181 | ): 182 | """ 183 | Creates an event subscription 184 | 185 | Args: 186 | context: The Redfish client object with an open session 187 | destination: The event subscription destination 188 | format: The format of the event payloads 189 | client_context: The client-provided context string 190 | expand: Indicates if the OriginOfCondition is to be expanded in event payloads 191 | resource_types: The resource types for the subscription 192 | registries: The registries for the subscription 193 | message_ids: The message IDs for the subscription 194 | origins: The origins for the subscription 195 | subordinate_resources: Indicates if subordinate resources to those referenced by 'origins' will also be monitored 196 | event_types: The event types for the subscription; this method for subscriptions has been deprecated for other controls 197 | 198 | Returns: 199 | The response of the POST 200 | """ 201 | 202 | # Get the event service 203 | event_service = get_event_service(context) 204 | if "Subscriptions" not in event_service: 205 | # No subscriptions 206 | raise RedfishEventServiceNotFoundError("Service does not contain a subscription collection") 207 | 208 | # Form the POST request 209 | payload = {"Destination": destination, "Protocol": "Redfish"} 210 | if format is not None: 211 | payload["EventFormatType"] = format 212 | if client_context is not None: 213 | payload["Context"] = client_context 214 | if expand is not None: 215 | payload["IncludeOriginOfCondition"] = expand 216 | if resource_types is not None: 217 | payload["ResourceTypes"] = resource_types 218 | if registries is not None: 219 | payload["RegistryPrefixes"] = registries 220 | if message_ids is not None: 221 | payload["MessageIds"] = message_ids 222 | if origins is not None: 223 | payload["OriginResources"] = origins 224 | if subordinate_resources is not None: 225 | payload["SubordinateResources"] = subordinate_resources 226 | if event_types is not None: 227 | payload["EventTypes"] = event_types 228 | 229 | # Create the subscription 230 | response = context.post(event_service["Subscriptions"]["@odata.id"], body=payload) 231 | verify_response(response) 232 | return response 233 | 234 | 235 | def delete_event_subscription(context, id): 236 | """ 237 | Deletes an event subscription 238 | 239 | Args: 240 | context: The Redfish client object with an open session 241 | id: The identifier for the subscription 242 | 243 | Returns: 244 | The response of the DELETE 245 | """ 246 | 247 | # Get the current subscriptions 248 | subscriptions = get_event_subscriptions(context) 249 | 250 | # Find the matching subscription and delete it 251 | for subscription in subscriptions: 252 | if subscription["Id"] == id: 253 | response = context.delete(subscription["@odata.id"]) 254 | verify_response(response) 255 | return response 256 | 257 | # No matches found 258 | raise RedfishEventSubscriptionNotFoundError("Service does not contain an event subscription called {}".format(id)) 259 | -------------------------------------------------------------------------------- /redfish_utilities/licenses.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Licenses Module 8 | 9 | File : licenses.py 10 | 11 | Brief : This file contains the definitions and functionalities for managing 12 | licenses on a Redfish service 13 | """ 14 | 15 | import base64 16 | import os 17 | from .collections import get_collection_ids 18 | from .messages import verify_response 19 | 20 | 21 | class RedfishLicenseServiceNotFoundError(Exception): 22 | """ 23 | Raised when the license service cannot be found 24 | """ 25 | 26 | pass 27 | 28 | 29 | class RedfishLicenseCollectionNotFoundError(Exception): 30 | """ 31 | Raised when the license collection cannot be found 32 | """ 33 | 34 | pass 35 | 36 | 37 | class RedfishLicenseNotFoundError(Exception): 38 | """ 39 | Raised when a specific license cannot be found 40 | """ 41 | 42 | pass 43 | 44 | 45 | class RedfishInstallLicenseNotFoundError(Exception): 46 | """ 47 | Raised when the license service does not contain the Install action 48 | """ 49 | 50 | pass 51 | 52 | 53 | def get_licenses(context): 54 | """ 55 | Collects license information from a Redfish service 56 | 57 | Args: 58 | context: The Redfish client object with an open session 59 | 60 | Returns: 61 | A list containing all licenses 62 | """ 63 | 64 | license_list = [] 65 | license_collection = get_license_collection(context) 66 | 67 | # Get the identifiers of the collection 68 | license_col = get_collection_ids(context, license_collection) 69 | 70 | # Get each member and add it to the response list 71 | for license_id in license_col: 72 | license = context.get(license_collection + "/" + license_id) 73 | verify_response(license) 74 | license_list.append(license.dict) 75 | 76 | return license_list 77 | 78 | 79 | def print_licenses(license_list, details=False): 80 | """ 81 | Prints the license list into a table 82 | 83 | Args: 84 | license_list: The license list to print 85 | details: True to print all the detailed info 86 | """ 87 | 88 | license_format = " {:30s} | {}" 89 | license_format_detail = " {:30s} | {}: {}" 90 | info_properties = [ 91 | {"Format": "{}", "Property": "EntitlementId"}, 92 | {"Format": "Installed on {}", "Property": "InstallDate"}, 93 | {"Format": "Expires on {}", "Property": "ExpirationDate"}, 94 | ] 95 | detail_list = [ 96 | "Description", 97 | "LicenseType", 98 | "LicenseOrigin", 99 | "Removable", 100 | "Manufacturer", 101 | "SKU", 102 | "PartNumber", 103 | "SerialNumber", 104 | "AuthorizationScope", 105 | "MaxAuthorizedDevices", 106 | "RemainingUseCount", 107 | ] 108 | 109 | print("") 110 | print(license_format.format("License", "Details")) 111 | 112 | # Go through each license 113 | for license in license_list: 114 | info_list = [] 115 | # Build the general info string 116 | for info in info_properties: 117 | if info["Property"] in license: 118 | info_string = info["Format"].format(license[info["Property"]]) 119 | else: 120 | if info["Property"] == "EntitlementId": 121 | # Fallback to ensure there is always something to show 122 | info_string = "License " + license["Id"] 123 | info_list.append(info_string) 124 | 125 | # Print the license info 126 | print(license_format.format(license["Id"], ", ".join(info_list))) 127 | 128 | # Print details if requested 129 | if details: 130 | for detail in detail_list: 131 | if detail in license: 132 | print(license_format_detail.format("", detail, license[detail])) 133 | 134 | print("") 135 | 136 | 137 | def install_license(context, license_path): 138 | """ 139 | Installs a new license 140 | 141 | Args: 142 | context: The Redfish client object with an open session 143 | license_path: The filepath or URI of the license to install 144 | 145 | Returns: 146 | The response of the operation 147 | """ 148 | 149 | # Get the license service 150 | license_service = get_license_service(context) 151 | 152 | # Determine which installation method to use based on the provided license path 153 | if os.path.isfile(license_path): 154 | # Local file; perform via a POST to the license collection 155 | if "Licenses" not in license_service: 156 | raise RedfishLicenseCollectionNotFoundError("The license service does not contain a license collection") 157 | install_uri = license_service["Licenses"]["@odata.id"] 158 | 159 | # Read in the file and convert it to a Base64-encoded string 160 | with open(license_path, "rb") as file: 161 | license_file = file.read() 162 | payload = {"LicenseString": base64.b64encode(license_file).decode("utf-8")} 163 | else: 164 | # Remote file; perform via a POST to the Install action 165 | if "Actions" not in license_service: 166 | raise RedfishInstallLicenseNotFoundError("The license service does not contain actions") 167 | if "#LicenseService.Install" not in license_service["Actions"]: 168 | raise RedfishInstallLicenseNotFoundError("The license service does not contain the Install action") 169 | install_uri = license_service["Actions"]["#LicenseService.Install"]["target"] 170 | 171 | payload = {"LicenseFileURI": license_path} 172 | 173 | # Install the license 174 | response = context.post(install_uri, body=payload) 175 | verify_response(response) 176 | return response 177 | 178 | 179 | def delete_license(context, license_id): 180 | """ 181 | Deletes a license 182 | 183 | Args: 184 | context: The Redfish client object with an open session 185 | license_id: The identifier of the license to delete 186 | 187 | Returns: 188 | The response of the operation 189 | """ 190 | 191 | # Get the identifiers of the collection 192 | license_collection = get_license_collection(context) 193 | avail_licenses = get_collection_ids(context, license_collection) 194 | if license_id not in avail_licenses: 195 | raise RedfishLicenseNotFoundError( 196 | "License service does not contain the license '{}'; available licenses: {}".format( 197 | license_id, ", ".join(avail_licenses) 198 | ) 199 | ) 200 | 201 | # Delete the requested license 202 | response = context.delete(license_collection + "/" + license_id) 203 | verify_response(response) 204 | return response 205 | 206 | 207 | def get_license_service(context): 208 | """ 209 | Collects the license service information from a Redfish service 210 | 211 | Args: 212 | context: The Redfish client object with an open session 213 | 214 | Returns: 215 | An object containing information about the license service 216 | """ 217 | 218 | # Get the service root to find the license service 219 | service_root = context.get("/redfish/v1/") 220 | if "LicenseService" not in service_root.dict: 221 | # No event service 222 | raise RedfishLicenseServiceNotFoundError("Service does not contain a license service") 223 | 224 | # Get the license service 225 | license_service = context.get(service_root.dict["LicenseService"]["@odata.id"]) 226 | verify_response(license_service) 227 | return license_service.dict 228 | 229 | 230 | def get_license_collection(context): 231 | """ 232 | Finds the license collection for the Redfish service 233 | 234 | Args: 235 | context: The Redfish client object with an open session 236 | 237 | Returns: 238 | The URI for the license collection 239 | """ 240 | 241 | # Get the license service to find the license collection 242 | license_service = get_license_service(context) 243 | if "Licenses" not in license_service: 244 | # No license collection 245 | raise RedfishLicenseCollectionNotFoundError("Service does not contain a license collection") 246 | 247 | return license_service["Licenses"]["@odata.id"] 248 | -------------------------------------------------------------------------------- /redfish_utilities/messages.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Messages Module 8 | 9 | File : messages.py 10 | 11 | Brief : This file contains the definitions and functionalities for interacting 12 | with Messages for a given Redfish service 13 | """ 14 | 15 | from redfish.messages import ( 16 | get_messages_detail, 17 | get_error_messages, 18 | search_message, 19 | RedfishPasswordChangeRequiredError, 20 | RedfishOperationFailedError, 21 | ) 22 | 23 | 24 | def verify_response(response): 25 | """ 26 | Verifies a response and raises an exception if there was a failure 27 | 28 | Args: 29 | response: The response to verify 30 | """ 31 | 32 | if response.status >= 400: 33 | messages_detail = get_messages_detail(response) 34 | exception_string = get_error_messages(messages_detail) 35 | message_item = search_message(messages_detail, "Base", "PasswordChangeRequired") 36 | if message_item is not None: 37 | raise RedfishPasswordChangeRequiredError( 38 | "Operation failed: HTTP {}\n{}".format(response.status, exception_string), 39 | message_item["MessageArgs"][0], 40 | ) 41 | else: 42 | raise RedfishOperationFailedError("Operation failed: HTTP {}\n{}".format(response.status, exception_string)) 43 | 44 | return 45 | 46 | 47 | def print_error_payload(response): 48 | """ 49 | Prints an error payload, which can also be used for action responses 50 | 51 | Args: 52 | response: The response to print 53 | """ 54 | 55 | try: 56 | print(get_error_messages(response)) 57 | except Exception: 58 | # No response body 59 | if response.status >= 400: 60 | print("Failed") 61 | else: 62 | print("Success") 63 | -------------------------------------------------------------------------------- /redfish_utilities/misc.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Miscellaneous 8 | 9 | File : misc.py 10 | 11 | Brief : Miscellaneous functions with common script logic 12 | """ 13 | 14 | 15 | def logout(context, ignore_error=False): 16 | """ 17 | Performs a logout of the service and allows for exceptions to be ignored 18 | 19 | Args: 20 | context: The Redfish client object with an open session 21 | ignore_error: Indicates if exceptions during logout are ignored 22 | """ 23 | 24 | if context is not None: 25 | try: 26 | context.logout() 27 | except Exception: 28 | if ignore_error: 29 | pass 30 | else: 31 | raise 32 | return 33 | 34 | 35 | def print_password_change_required_and_logout(context, args): 36 | """ 37 | Common help text when handling password change required conditions 38 | 39 | Args: 40 | context: The Redfish client object with an open session 41 | args: The argparse object from the calling script 42 | """ 43 | 44 | print("Password change required. To set a new password, run the following:") 45 | print( 46 | "rf_accounts.py -r {} -u {} -p --setpassword {} ".format( 47 | args.rhost, args.user, args.user 48 | ) 49 | ) 50 | logout(context, ignore_error=True) # Some services do not allow session logout in this condition 51 | return 52 | -------------------------------------------------------------------------------- /redfish_utilities/resets.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Resets Module 8 | 9 | File : resets.py 10 | 11 | Brief : This file contains the common definitions and functionalities for 12 | reset operations 13 | """ 14 | 15 | reset_types = [ 16 | "On", 17 | "ForceOff", 18 | "GracefulShutdown", 19 | "GracefulRestart", 20 | "ForceRestart", 21 | "Nmi", 22 | "ForceOn", 23 | "PushPowerButton", 24 | "PowerCycle", 25 | "Suspend", 26 | "Pause", 27 | "Resume", 28 | ] 29 | 30 | reset_to_defaults_types = ["ResetAll", "PreserveNetworkAndUsers", "PreserveNetwork"] 31 | -------------------------------------------------------------------------------- /redfish_utilities/tasks.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Tasks Module 8 | 9 | File : tasks.py 10 | 11 | Brief : This file contains the definitions and functionalities for interacting 12 | with Tasks for a given Redfish service 13 | """ 14 | 15 | import sys 16 | import time 17 | 18 | 19 | def poll_task_monitor(context, response, silent=False): 20 | """ 21 | Monitors a task monitor until it's complete and prints out progress 22 | NOTE: This call will block until the task is complete 23 | 24 | Args: 25 | context: The Redfish client object with an open session 26 | response: The initial response from the operation that produced the task 27 | silent: Indicates if the task progress is to be hidden 28 | 29 | Returns: 30 | The final response of the request 31 | """ 32 | 33 | # No task was produced; just return the existing response 34 | if not response.is_processing: 35 | return response 36 | 37 | # Poll the task until completion 38 | task_monitor = response 39 | while task_monitor.is_processing: 40 | # Print the progress 41 | if silent is False: 42 | task_state = None 43 | task_percent = None 44 | try: 45 | task_state = task_monitor.dict.get("TaskState", None) 46 | task_percent = task_monitor.dict.get("PercentComplete", None) 47 | except Exception: 48 | # 202 responses are allowed to not have a response body 49 | pass 50 | if task_state is None: 51 | task_state = "Running" 52 | if task_percent is None: 53 | progress_str = "Task is {}\r".format(task_state) 54 | else: 55 | progress_str = "Task is {}: {}% complete\r".format(task_state, task_percent) 56 | sys.stdout.write("\x1b[2K") 57 | sys.stdout.write(progress_str) 58 | sys.stdout.flush() 59 | 60 | # Sleep for the requested time 61 | retry_time = response.retry_after 62 | if retry_time is None: 63 | retry_time = 1 64 | time.sleep(retry_time) 65 | 66 | # Check the monitor for an update 67 | task_monitor = response.monitor(context) 68 | if silent is False: 69 | sys.stdout.write("\x1b[2K") 70 | print("Task is Done!") 71 | 72 | return task_monitor 73 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | redfish>=3.2.1 2 | XlsxWriter>=1.2.7 3 | requests -------------------------------------------------------------------------------- /scripts/rf_accounts.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Accounts 8 | 9 | File : rf_accounts.py 10 | 11 | Brief : This script uses the redfish_utilities module to manage user accounts 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import redfish 18 | import redfish_utilities 19 | import traceback 20 | import sys 21 | from redfish.messages import RedfishPasswordChangeRequiredError 22 | 23 | # Get the input arguments 24 | argget = argparse.ArgumentParser(description="A tool to manage user accounts on a Redfish service") 25 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 26 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 27 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 28 | argget.add_argument( 29 | "--add", "-add", type=str, nargs=3, metavar=("name", "password", "role"), help="Adds a new user account" 30 | ) 31 | argget.add_argument("--delete", "-delete", type=str, help="Deletes a user account with the given name") 32 | argget.add_argument( 33 | "--setname", 34 | "-setname", 35 | type=str, 36 | nargs=2, 37 | metavar=("old_name", "new_name"), 38 | help="Sets a user account to a new name", 39 | ) 40 | argget.add_argument( 41 | "--setpassword", 42 | "-setpassword", 43 | type=str, 44 | nargs=2, 45 | metavar=("name", "new_password"), 46 | help="Sets a user account to a new password", 47 | ) 48 | argget.add_argument( 49 | "--setrole", "-setrole", type=str, nargs=2, metavar=("name", "new_role"), help="Sets a user account to a new role" 50 | ) 51 | argget.add_argument("--enable", "-enable", type=str, help="Enables a user account with the given name") 52 | argget.add_argument("--disable", "-disable", type=str, help="Disabled a user account with the given name") 53 | argget.add_argument("--unlock", "-unlock", type=str, help="Unlocks a user account with the given name") 54 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 55 | args = argget.parse_args() 56 | 57 | # Always let the calls for account management block until the underlying task is complete 58 | redfish_utilities.config.__auto_task_handling__ = True 59 | 60 | if args.debug: 61 | log_file = "rf_accounts-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 62 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 63 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 64 | logger.info("rf_accounts Trace") 65 | 66 | # Set up the Redfish object 67 | user_uri = None 68 | redfish_obj = None 69 | try: 70 | redfish_obj = redfish.redfish_client( 71 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 72 | ) 73 | redfish_obj.login(auth="session") 74 | except RedfishPasswordChangeRequiredError as e: 75 | if args.setpassword is None: 76 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 77 | sys.exit(1) 78 | else: 79 | user_uri = e.args[1] 80 | if args.setpassword[0] == args.user: 81 | # The user is requesting to change their own password 82 | # Allow the request to go through directly, and log out 83 | exit_code = 0 84 | try: 85 | print("Changing password of user '{}'".format(args.setpassword[0])) 86 | redfish_utilities.modify_user( 87 | redfish_obj, args.setpassword[0], new_password=args.setpassword[1], user_uri=user_uri 88 | ) 89 | except Exception as e: 90 | if args.debug: 91 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 92 | exit_code = 1 93 | print(e) 94 | finally: 95 | # Log out 96 | redfish_utilities.logout( 97 | redfish_obj, ignore_error=True 98 | ) # Some services do not allow session logout in this condition 99 | sys.exit(exit_code) 100 | else: 101 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 102 | sys.exit(1) 103 | except Exception: 104 | raise 105 | 106 | exit_code = 0 107 | try: 108 | print_accounts = True 109 | if args.add is not None: 110 | print("Adding new user '{}'".format(args.add[0])) 111 | redfish_utilities.add_user(redfish_obj, args.add[0], args.add[1], args.add[2]) 112 | print_accounts = False 113 | if args.delete is not None: 114 | print("Deleting user '{}'".format(args.delete)) 115 | redfish_utilities.delete_user(redfish_obj, args.delete) 116 | print_accounts = False 117 | if args.setname is not None: 118 | print("Changing name of user '{}' to '{}'".format(args.setname[0], args.setname[1])) 119 | redfish_utilities.modify_user(redfish_obj, args.setname[0], new_name=args.setname[1]) 120 | print_accounts = False 121 | if args.setpassword is not None: 122 | print("Changing password of user '{}'".format(args.setpassword[0])) 123 | redfish_utilities.modify_user(redfish_obj, args.setpassword[0], new_password=args.setpassword[1]) 124 | print_accounts = False 125 | if args.setrole is not None: 126 | print("Changing role of user '{}' to '{}'".format(args.setrole[0], args.setrole[1])) 127 | redfish_utilities.modify_user(redfish_obj, args.setrole[0], new_role=args.setrole[1]) 128 | print_accounts = False 129 | if args.enable is not None: 130 | print("Enabling user '{}'".format(args.enable)) 131 | redfish_utilities.modify_user(redfish_obj, args.enable, new_enabled=True) 132 | print_accounts = False 133 | if args.disable is not None: 134 | print("Disabling user '{}'".format(args.disable)) 135 | redfish_utilities.modify_user(redfish_obj, args.disable, new_enabled=False) 136 | print_accounts = False 137 | if args.unlock is not None: 138 | print("Unlocking user '{}'".format(args.unlock)) 139 | redfish_utilities.modify_user(redfish_obj, args.unlock, new_locked=False) 140 | print_accounts = False 141 | if print_accounts: 142 | user_list = redfish_utilities.get_users(redfish_obj) 143 | redfish_utilities.print_users(user_list) 144 | except Exception as e: 145 | if args.debug: 146 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 147 | exit_code = 1 148 | print(e) 149 | finally: 150 | # Log out 151 | redfish_utilities.logout(redfish_obj) 152 | sys.exit(exit_code) 153 | -------------------------------------------------------------------------------- /scripts/rf_assembly.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Assembly 8 | 9 | File : rf_assembly.py 10 | 11 | Brief : This script uses the redfish_utilities module to manage assemblies 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import redfish 18 | import redfish_utilities 19 | import traceback 20 | import sys 21 | from redfish.messages import RedfishPasswordChangeRequiredError 22 | 23 | # Get the input arguments 24 | argget = argparse.ArgumentParser(description="A tool to manage assemblies on a Redfish service") 25 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 26 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 27 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 28 | argget.add_argument("--assembly", "-a", type=str, required=True, help="The URI of the target assembly") 29 | argget.add_argument("--index", "-i", type=int, help="The target assembly index") 30 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 31 | subparsers = argget.add_subparsers(dest="command") 32 | info_argget = subparsers.add_parser("info", help="Displays information about the an assembly") 33 | download_argget = subparsers.add_parser("download", help="Downloads assembly data to a file") 34 | download_argget.add_argument( 35 | "--file", "-f", type=str, required=True, help="The file, and optional path, to save the assembly data" 36 | ) 37 | upload_argget = subparsers.add_parser("upload", help="Uploads assembly data from a file") 38 | upload_argget.add_argument( 39 | "--file", "-f", type=str, required=True, help="The file, and optional path, containing the assembly data to upload" 40 | ) 41 | args = argget.parse_args() 42 | 43 | if args.index and args.index < 0: 44 | print("rf_assembly.py: error: the assembly index cannot be negative") 45 | sys.exit(1) 46 | 47 | if args.debug: 48 | log_file = "rf_assembly-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 49 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 50 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 51 | logger.info("rf_assembly Trace") 52 | 53 | # Set up the Redfish object 54 | redfish_obj = None 55 | try: 56 | redfish_obj = redfish.redfish_client( 57 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 58 | ) 59 | redfish_obj.login(auth="session") 60 | except RedfishPasswordChangeRequiredError: 61 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 62 | sys.exit(1) 63 | except Exception: 64 | raise 65 | 66 | exit_code = 0 67 | try: 68 | assembly_info = redfish_utilities.get_assembly(redfish_obj, args.assembly) 69 | if args.command == "download": 70 | print("Saving data to '{}'...".format(args.file)) 71 | redfish_utilities.download_assembly(redfish_obj, assembly_info, args.file, args.index) 72 | elif args.command == "upload": 73 | print("Writing data from '{}'...".format(args.file)) 74 | redfish_utilities.upload_assembly(redfish_obj, assembly_info, args.file, args.index) 75 | else: 76 | redfish_utilities.print_assembly(assembly_info, args.index) 77 | except Exception as e: 78 | if args.debug: 79 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 80 | exit_code = 1 81 | print(e) 82 | finally: 83 | # Log out 84 | redfish_utilities.logout(redfish_obj) 85 | sys.exit(exit_code) 86 | -------------------------------------------------------------------------------- /scripts/rf_bios_settings.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish BIOS Settings 8 | 9 | File : rf_bios_settings.py 10 | 11 | Brief : This script uses the redfish_utilities module to manager BIOS settings of a system 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import redfish 18 | import redfish_utilities 19 | import traceback 20 | import sys 21 | from redfish.messages import RedfishPasswordChangeRequiredError 22 | 23 | # Get the input arguments 24 | argget = argparse.ArgumentParser(description="A tool to manager BIOS settings for a system") 25 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 26 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 27 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 28 | argget.add_argument("--system", "-s", type=str, help="The ID of the system to manage") 29 | argget.add_argument( 30 | "--attribute", 31 | "-a", 32 | type=str, 33 | nargs=2, 34 | metavar=("name", "value"), 35 | action="append", 36 | help="Sets a BIOS attribute to a new value; can be supplied multiple times to set multiple attributes", 37 | ) 38 | argget.add_argument("--reset", "-reset", action="store_true", help="Resets BIOS to the default settings") 39 | argget.add_argument( 40 | "--workaround", 41 | "-workaround", 42 | action="store_true", 43 | help="Indicates if workarounds should be attempted for non-conformant services", 44 | default=False, 45 | ) 46 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 47 | args = argget.parse_args() 48 | 49 | if args.workaround: 50 | redfish_utilities.config.__workarounds__ = True 51 | 52 | if args.debug: 53 | log_file = "rf_bios_settings-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 54 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 55 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 56 | logger.info("rf_bios_settings Trace") 57 | 58 | # Set up the Redfish object 59 | redfish_obj = None 60 | try: 61 | redfish_obj = redfish.redfish_client( 62 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 63 | ) 64 | redfish_obj.login(auth="session") 65 | except RedfishPasswordChangeRequiredError: 66 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 67 | sys.exit(1) 68 | except Exception: 69 | raise 70 | 71 | exit_code = 0 72 | try: 73 | if args.reset: 74 | # Reset BIOS to the default settings 75 | print("Resetting the BIOS settings...") 76 | redfish_utilities.reset_system_bios(redfish_obj, args.system) 77 | else: 78 | # Get the BIOS settings 79 | current_settings, future_settings = redfish_utilities.get_system_bios(redfish_obj, args.system) 80 | 81 | if args.attribute is not None: 82 | new_settings = {} 83 | for attribute in args.attribute: 84 | # Based on the current settings, determine the appropriate data type for the new setting 85 | new_value = attribute[1] 86 | if attribute[0] in current_settings: 87 | if isinstance(current_settings[attribute[0]], bool): 88 | # Boolean; convert from a string 89 | if new_value.lower() == "true": 90 | new_value = True 91 | else: 92 | new_value = False 93 | elif isinstance(current_settings[attribute[0]], (int, float)): 94 | # Integer or float; go by the user input to determine how to convert since the current value may be truncated 95 | try: 96 | new_value = int(new_value) 97 | except Exception: 98 | new_value = float(new_value) 99 | 100 | # Set the specified attribute to the new value 101 | new_settings[attribute[0]] = new_value 102 | print("Setting {} to {}...".format(attribute[0], attribute[1])) 103 | redfish_utilities.set_system_bios(redfish_obj, new_settings, args.system) 104 | else: 105 | # Print the BIOS settings 106 | redfish_utilities.print_system_bios(current_settings, future_settings) 107 | except Exception as e: 108 | if args.debug: 109 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 110 | exit_code = 1 111 | print(e) 112 | finally: 113 | # Log out 114 | redfish_utilities.logout(redfish_obj) 115 | sys.exit(exit_code) 116 | -------------------------------------------------------------------------------- /scripts/rf_boot_override.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Boot Override 8 | 9 | File : rf_boot_override.py 10 | 11 | Brief : This script uses the redfish_utilities module to manage a one time boot 12 | override of a system 13 | """ 14 | 15 | import argparse 16 | import datetime 17 | import logging 18 | import redfish 19 | import redfish_utilities 20 | import traceback 21 | import sys 22 | from redfish.messages import RedfishPasswordChangeRequiredError 23 | 24 | # Get the input arguments 25 | argget = argparse.ArgumentParser(description="A tool to perform a one time boot override of a system") 26 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 27 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 28 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 29 | argget.add_argument("--system", "-s", type=str, help="The ID of the system to set") 30 | argget.add_argument("--info", "-info", action="store_true", help="Indicates if boot information should be reported") 31 | argget.add_argument( 32 | "--target", 33 | "-t", 34 | type=str, 35 | help="The target boot device; if this argument is omitted the tool will display the current boot settings", 36 | ) 37 | argget.add_argument( 38 | "--uefi", 39 | "-uefi", 40 | type=str, 41 | help="If target is 'UefiTarget', the UEFI Device Path of the device to boot. If target is 'UefiBootNext', the UEFI Boot Option string of the device to boot.", 42 | ) 43 | argget.add_argument("--mode", "-m", type=str, help="The requested boot mode ('UEFI' or 'Legacy')") 44 | argget.add_argument( 45 | "--reset", "-reset", action="store_true", help="Signifies that the system is reset after the boot override is set" 46 | ) 47 | argget.add_argument( 48 | "--workaround", 49 | "-workaround", 50 | action="store_true", 51 | help="Indicates if workarounds should be attempted for non-conformant services", 52 | default=False, 53 | ) 54 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 55 | args = argget.parse_args() 56 | 57 | # Verify the combination of arguments is correct 58 | if args.target is None: 59 | args.info = True 60 | if args.uefi or args.mode or args.reset: 61 | argget.error("Cannot use '--uefi', '--mode', or '--reset' without '--target'") 62 | 63 | if args.workaround: 64 | redfish_utilities.config.__workarounds__ = True 65 | 66 | if args.debug: 67 | log_file = "rf_boot_override-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 68 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 69 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 70 | logger.info("rf_boot_override Trace") 71 | 72 | # Set up the Redfish object 73 | redfish_obj = None 74 | try: 75 | redfish_obj = redfish.redfish_client( 76 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 77 | ) 78 | redfish_obj.login(auth="session") 79 | except RedfishPasswordChangeRequiredError: 80 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 81 | sys.exit(1) 82 | except Exception: 83 | raise 84 | 85 | exit_code = 0 86 | try: 87 | if args.info: 88 | boot = redfish_utilities.get_system_boot(redfish_obj, args.system) 89 | redfish_utilities.print_system_boot(boot) 90 | else: 91 | # Build and send the boot request based on the arguments given 92 | uefi_target = None 93 | boot_next = None 94 | boot_enable = "Once" 95 | if args.target == "UefiTarget": 96 | uefi_target = args.uefi 97 | if args.target == "UefiBootNext": 98 | boot_next = args.uefi 99 | if args.target == "None": 100 | print("Disabling one time boot...") 101 | boot_enable = "Disabled" 102 | else: 103 | print("Setting a one time boot for {}...".format(args.target)) 104 | redfish_utilities.set_system_boot( 105 | redfish_obj, args.system, args.target, boot_enable, args.mode, uefi_target, boot_next 106 | ) 107 | 108 | # Reset the system if requested 109 | if args.reset: 110 | print("Resetting the system...") 111 | response = redfish_utilities.system_reset(redfish_obj, args.system) 112 | response = redfish_utilities.poll_task_monitor(redfish_obj, response) 113 | redfish_utilities.verify_response(response) 114 | except Exception as e: 115 | if args.debug: 116 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 117 | exit_code = 1 118 | print(e) 119 | finally: 120 | # Log out 121 | redfish_utilities.logout(redfish_obj) 122 | sys.exit(exit_code) 123 | -------------------------------------------------------------------------------- /scripts/rf_certificates.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Certificates 8 | 9 | File : rf_certificates.py 10 | 11 | Brief : This script uses the redfish_utilities module to manage certificates 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import redfish 18 | import redfish_utilities 19 | import traceback 20 | import sys 21 | from redfish.messages import RedfishPasswordChangeRequiredError 22 | 23 | # Get the input arguments 24 | argget = argparse.ArgumentParser(description="A tool to manage certificates on a Redfish service") 25 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 26 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 27 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 28 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 29 | subparsers = argget.add_subparsers(dest="command") 30 | info_argget = subparsers.add_parser("info", help="Displays information about the certificates installed on the service") 31 | info_argget.add_argument( 32 | "--details", 33 | "-details", 34 | action="store_true", 35 | help="Indicates if the full details of each certificate should be shown", 36 | ) 37 | csrinfo_argget = subparsers.add_parser( 38 | "csrinfo", help="Displays information about options supported for generating certificate signing requests" 39 | ) 40 | csr_argget = subparsers.add_parser("csr", help="Generates a certificate signing request") 41 | csr_argget.add_argument( 42 | "--certificatecollection", 43 | "-col", 44 | type=str, 45 | required=True, 46 | help="The URI of the certificate collection where the signed certificate will be installed", 47 | ) 48 | csr_argget.add_argument( 49 | "--commonname", "-cn", type=str, required=True, help="The common name of the component to secure" 50 | ) 51 | csr_argget.add_argument( 52 | "--organization", "-o", type=str, required=True, help="The name of the unit in the organization making the request" 53 | ) 54 | csr_argget.add_argument( 55 | "--organizationalunit", 56 | "-ou", 57 | type=str, 58 | required=True, 59 | help="The name of the unit in the organization making the request", 60 | ) 61 | csr_argget.add_argument( 62 | "--city", "-l", type=str, required=True, help="The city or locality of the organization making the request" 63 | ) 64 | csr_argget.add_argument( 65 | "--state", 66 | "-st", 67 | type=str, 68 | required=True, 69 | help="The state, province, or region of the organization making the request", 70 | ) 71 | csr_argget.add_argument( 72 | "--country", 73 | "-c", 74 | type=str, 75 | required=True, 76 | help="The two-letter country code of the organization making the request", 77 | ) 78 | csr_argget.add_argument( 79 | "--email", "-email", type=str, help="The email address of the contact within the organization making the request" 80 | ) 81 | csr_argget.add_argument("--keyalg", "-alg", type=str, help="The type of key-pair for use with signing algorithms") 82 | csr_argget.add_argument( 83 | "--keylen", "-len", type=int, help="The length of the key, in bits, if the key pair algorithm supports key size" 84 | ) 85 | csr_argget.add_argument( 86 | "--keycurve", "-curve", type=str, help="The curve ID to use with the key if the key pair algorithm supports curves" 87 | ) 88 | csr_argget.add_argument( 89 | "--out", "-out", type=str, help="The file, with optional path, to save the certificate signing request" 90 | ) 91 | install_argget = subparsers.add_parser("install", help="Installs a certificate on the service") 92 | install_argget.add_argument( 93 | "--destination", 94 | "-dest", 95 | type=str, 96 | required=True, 97 | help="The installation URI of the certificate; either a certificate collection to insert, or an existing certificate to replace", 98 | ) 99 | install_argget.add_argument( 100 | "--certificate", "-cert", type=str, required=True, help="The file, and optional path, of the certificate to install" 101 | ) 102 | install_argget.add_argument( 103 | "--key", "-key", type=str, help="The file, and optional path, of the private key for the certificate to install" 104 | ) 105 | delete_argget = subparsers.add_parser("delete", help="Deletes a certificate on the service") 106 | delete_argget.add_argument( 107 | "--certificate", "-cert", type=str, required=True, help="The URI of the certificate to delete" 108 | ) 109 | args = argget.parse_args() 110 | 111 | if args.debug: 112 | log_file = "rf_certificates-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 113 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 114 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 115 | logger.info("rf_certificates Trace") 116 | 117 | # Set up the Redfish object 118 | redfish_obj = None 119 | try: 120 | redfish_obj = redfish.redfish_client( 121 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 122 | ) 123 | redfish_obj.login(auth="session") 124 | except RedfishPasswordChangeRequiredError: 125 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 126 | sys.exit(1) 127 | except Exception: 128 | raise 129 | 130 | exit_code = 0 131 | try: 132 | if args.command == "csrinfo": 133 | csr_uri, csr_parameters = redfish_utilities.get_generate_csr_info(redfish_obj) 134 | if csr_parameters is None: 135 | print("No generate CSR parameter information found") 136 | else: 137 | print("Generate CSR parameters:") 138 | for param in csr_parameters: 139 | required = "" 140 | if param.get("Required"): 141 | required = " (required)" 142 | allowable_values = "" 143 | if param.get("AllowableValues"): 144 | allowable_values = ": " + ", ".join(param["AllowableValues"]) 145 | if param.get("AllowableNumbers"): 146 | allowable_values = ": " + ", ".join(param["AllowableNumbers"]) 147 | if param.get("AllowablePattern"): 148 | allowable_values = ": " + param["AllowablePattern"] 149 | print(" {}{}{}".format(param["Name"], required, allowable_values)) 150 | elif args.command == "csr": 151 | print("Generating cerficiate signing request...") 152 | response = redfish_utilities.generate_csr( 153 | redfish_obj, 154 | args.commonname, 155 | args.organization, 156 | args.organizationalunit, 157 | args.city, 158 | args.state, 159 | args.country, 160 | args.certificatecollection, 161 | args.email, 162 | args.keyalg, 163 | args.keylen, 164 | args.keycurve, 165 | ) 166 | response = redfish_utilities.poll_task_monitor(redfish_obj, response) 167 | redfish_utilities.verify_response(response) 168 | print("") 169 | print(response.dict["CSRString"]) 170 | print("") 171 | if args.out: 172 | with open(args.out, "w") as file: 173 | file.write(response.dict["CSRString"]) 174 | elif args.command == "install": 175 | print("Installing {}...".format(args.certificate)) 176 | response = redfish_utilities.install_certificate(redfish_obj, args.destination, args.certificate, args.key) 177 | response = redfish_utilities.poll_task_monitor(redfish_obj, response) 178 | redfish_utilities.verify_response(response) 179 | elif args.command == "delete": 180 | print("Deleting {}...".format(args.certificate)) 181 | response = redfish_utilities.delete_certificate(redfish_obj, args.certificate) 182 | redfish_utilities.verify_response(response) 183 | else: 184 | certificates = redfish_utilities.get_all_certificates(redfish_obj) 185 | if args.command == "info": 186 | redfish_utilities.print_certificates(certificates, details=args.details) 187 | else: 188 | redfish_utilities.print_certificates(certificates) 189 | except Exception as e: 190 | if args.debug: 191 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 192 | exit_code = 1 193 | print(e) 194 | finally: 195 | # Log out 196 | redfish_utilities.logout(redfish_obj) 197 | sys.exit(exit_code) 198 | -------------------------------------------------------------------------------- /scripts/rf_diagnostic_data.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Diagnostic Data 8 | 9 | File : rf_diagnostic_data.py 10 | 11 | Brief : This script uses the redfish_utilities module to collect diagnostic data from a log service 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import os 18 | import redfish 19 | import redfish_utilities 20 | import traceback 21 | import sys 22 | from redfish.messages import RedfishPasswordChangeRequiredError 23 | 24 | # Get the input arguments 25 | argget = argparse.ArgumentParser( 26 | description="A tool to collect diagnostic data from a log service on a Redfish service" 27 | ) 28 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 29 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 30 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 31 | argget.add_argument( 32 | "--manager", "-m", type=str, nargs="?", default=False, help="The ID of the manager containing the log service" 33 | ) 34 | argget.add_argument( 35 | "--system", "-s", type=str, nargs="?", default=False, help="The ID of the system containing the log service" 36 | ) 37 | argget.add_argument( 38 | "--chassis", "-c", type=str, nargs="?", default=False, help="The ID of the chassis containing the log service" 39 | ) 40 | argget.add_argument("--log", "-l", type=str, help="The ID of the log service") 41 | argget.add_argument( 42 | "--type", 43 | "-type", 44 | type=redfish_utilities.diagnostic_data_types, 45 | help="The type of diagnostic data to collect; defaults to 'Manager' if not specified", 46 | choices=redfish_utilities.diagnostic_data_types, 47 | default=redfish_utilities.diagnostic_data_types.MANAGER, 48 | ) 49 | argget.add_argument( 50 | "--oemtype", 51 | "-oemtype", 52 | type=str, 53 | help="The OEM-specific type of diagnostic data to collect; this option should only be used if the requested type is 'OEM'", 54 | ) 55 | argget.add_argument( 56 | "--directory", 57 | "-d", 58 | type=str, 59 | help="The directory to save the diagnostic data; defaults to the current directory if not specified", 60 | default=".", 61 | ) 62 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 63 | args = argget.parse_args() 64 | 65 | # Determine the target log service based on the inputs 66 | # Effectively if the user gives multiple targets, some will be ignored 67 | container_type = redfish_utilities.log_container.MANAGER 68 | container_id = None 69 | if args.manager is not False: 70 | container_type = redfish_utilities.log_container.MANAGER 71 | container_id = args.manager 72 | elif args.system is not False: 73 | container_type = redfish_utilities.log_container.SYSTEM 74 | container_id = args.system 75 | elif args.chassis is not False: 76 | container_type = redfish_utilities.log_container.CHASSIS 77 | container_id = args.chassis 78 | 79 | if args.debug: 80 | log_file = "rf_diagnostic_data-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 81 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 82 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 83 | logger.info("rf_diagnostic_data Trace") 84 | 85 | # Set up the Redfish object 86 | redfish_obj = None 87 | try: 88 | redfish_obj = redfish.redfish_client( 89 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 90 | ) 91 | redfish_obj.login(auth="session") 92 | except RedfishPasswordChangeRequiredError: 93 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 94 | sys.exit(1) 95 | except Exception: 96 | raise 97 | 98 | exit_code = 0 99 | try: 100 | print("Collecting diagnostic data...") 101 | response = redfish_utilities.collect_diagnostic_data( 102 | redfish_obj, container_type, container_id, args.log, args.type, args.oemtype 103 | ) 104 | response = redfish_utilities.poll_task_monitor(redfish_obj, response) 105 | filename, data = redfish_utilities.download_diagnostic_data(redfish_obj, response) 106 | 107 | # Save the file 108 | if not os.path.isdir(args.directory): 109 | os.makedirs(args.directory) 110 | path = os.path.join(args.directory, filename) 111 | name_parts = filename.split(".", 1) 112 | file_check = 0 113 | if len(name_parts) == 1: 114 | name_parts.append("") 115 | while os.path.isfile(path): 116 | # If the file already exists, build a new file name with a counter 117 | file_check = file_check + 1 118 | filename = "{}({}).{}".format(name_parts[0], file_check, name_parts[1]) 119 | path = os.path.join(args.directory, filename) 120 | with open(path, "wb") as file: 121 | file.write(data) 122 | print("Saved diagnostic data to '{}'".format(path)) 123 | except Exception as e: 124 | if args.debug: 125 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 126 | exit_code = 1 127 | print(e) 128 | finally: 129 | # Log out 130 | redfish_utilities.logout(redfish_obj) 131 | sys.exit(exit_code) 132 | -------------------------------------------------------------------------------- /scripts/rf_discover.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Discover 8 | 9 | File : rf_discover.py 10 | 11 | Brief : This script uses the redfish module to discover Redfish services 12 | """ 13 | 14 | import argparse 15 | import re 16 | import redfish 17 | import sys 18 | 19 | # No arguments, but having help text is useful 20 | argget = argparse.ArgumentParser(description="A tool to discover Redfish services") 21 | args = argget.parse_args() 22 | 23 | # Invoke the discovery routine for SSDP and print the responses 24 | services = redfish.discover_ssdp() 25 | if len(services) == 0: 26 | print("No Redfish services discovered") 27 | sys.exit(1) 28 | else: 29 | print("Redfish services:") 30 | 31 | # Go through each discovered service and print out basic info 32 | for service in services: 33 | # Try to get the Product property from the service root for each service 34 | # If found, print the UUID, service root pointer, and the Product 35 | # If not, just print the UUID and service root pointer 36 | try: 37 | # Need to strip off /redfish/v1 from the SSDP response to use the URL with the library 38 | groups = re.search(r"^(.+)\/redfish\/v1\/?$", services[service]) 39 | url = groups.group(1) 40 | redfish_obj = redfish.redfish_client(base_url=url, timeout=15, max_retry=3) 41 | print("{}: {} ({})".format(service, services[service], redfish_obj.root["Product"])) 42 | except Exception: 43 | print("{}: {}".format(service, services[service])) 44 | 45 | sys.exit(0) 46 | -------------------------------------------------------------------------------- /scripts/rf_event_service.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Event Service 8 | 9 | File : rf_event_service.py 10 | 11 | Brief : This script uses the redfish_utilities module to manage the event service and event subscriptions 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import redfish 18 | import redfish_utilities 19 | import traceback 20 | import sys 21 | from redfish.messages import RedfishPasswordChangeRequiredError 22 | 23 | # Get the input arguments 24 | argget = argparse.ArgumentParser(description="A tool to manage the event service on a Redfish service") 25 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 26 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 27 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 28 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 29 | subparsers = argget.add_subparsers(dest="command") 30 | info_argget = subparsers.add_parser("info", help="Displays information about the event service and subscriptions") 31 | sub_argget = subparsers.add_parser("subscribe", help="Creates an event subscription to a specified URL") 32 | sub_argget.add_argument( 33 | "--destination", "-dest", type=str, required=True, help="The URL where events are sent for the subscription" 34 | ) 35 | sub_argget.add_argument( 36 | "--context", 37 | "-c", 38 | type=str, 39 | help="The context string for the subscription that is supplied back in the event payload", 40 | ) 41 | sub_argget.add_argument( 42 | "--expand", 43 | "-e", 44 | action="store_true", 45 | help="Indicates if the origin of condition in the event is to be expanded", 46 | default=None, 47 | ) 48 | sub_argget.add_argument("--format", "-f", type=str, help="The format of the event payloads") 49 | sub_argget.add_argument( 50 | "--resourcetypes", "-rt", type=str, nargs="+", help="A list of resource types for the subscription" 51 | ) 52 | sub_argget.add_argument("--registries", "-reg", type=str, nargs="+", help="A list of registries for the subscription") 53 | sub_argget.add_argument( 54 | "--eventtypes", 55 | "-et", 56 | type=str, 57 | nargs="+", 58 | help="A list of event types for the subscription; this option has been deprecated in favor of other methods such as 'resource types' and 'registries'", 59 | ) 60 | unsub_argget = subparsers.add_parser("unsubscribe", help="Deletes an event subscription") 61 | unsub_argget.add_argument( 62 | "--id", "-i", type=str, required=True, help="The identifier of the event subscription to be deleted" 63 | ) 64 | args = argget.parse_args() 65 | 66 | if args.debug: 67 | log_file = "rf_event_service-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 68 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 69 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 70 | logger.info("rf_event_service Trace") 71 | 72 | # Set up the Redfish object 73 | redfish_obj = None 74 | try: 75 | redfish_obj = redfish.redfish_client( 76 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 77 | ) 78 | redfish_obj.login(auth="session") 79 | except RedfishPasswordChangeRequiredError: 80 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 81 | sys.exit(1) 82 | except Exception: 83 | raise 84 | 85 | exit_code = 0 86 | try: 87 | if args.command == "subscribe": 88 | response = redfish_utilities.create_event_subscription( 89 | redfish_obj, 90 | args.destination, 91 | format=args.format, 92 | client_context=args.context, 93 | expand=args.expand, 94 | resource_types=args.resourcetypes, 95 | registries=args.registries, 96 | event_types=args.eventtypes, 97 | ) 98 | print("Created subscription '{}'".format(response.getheader("Location"))) 99 | elif args.command == "unsubscribe": 100 | print("Deleting subscription '{}'".format(args.id)) 101 | redfish_utilities.delete_event_subscription(redfish_obj, args.id) 102 | else: 103 | event_service = redfish_utilities.get_event_service(redfish_obj) 104 | redfish_utilities.print_event_service(event_service) 105 | print("") 106 | event_subscriptions = redfish_utilities.get_event_subscriptions(redfish_obj) 107 | redfish_utilities.print_event_subscriptions(event_subscriptions) 108 | except Exception as e: 109 | if args.debug: 110 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 111 | exit_code = 1 112 | print(e) 113 | finally: 114 | # Log out 115 | redfish_utilities.logout(redfish_obj) 116 | sys.exit(exit_code) 117 | -------------------------------------------------------------------------------- /scripts/rf_firmware_inventory.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Firmware Inventory 8 | 9 | File : rf_firmware_inventory.py 10 | 11 | Brief : This script uses the redfish_utilities module to manage firmware inventory 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import redfish 18 | import redfish_utilities 19 | import traceback 20 | import sys 21 | from redfish.messages import RedfishPasswordChangeRequiredError 22 | 23 | # Get the input arguments 24 | argget = argparse.ArgumentParser(description="A tool to collect firmware inventory from a Redfish service") 25 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 26 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 27 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 28 | argget.add_argument( 29 | "--details", "-details", action="store_true", help="Indicates details to be shown for each firmware entry" 30 | ) 31 | argget.add_argument("--id", "-i", action="store_true", help="Construct inventory names using 'Id' values") 32 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 33 | args = argget.parse_args() 34 | 35 | if args.debug: 36 | log_file = "rf_firmware_inventory-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 37 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 38 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 39 | logger.info("rf_firmware_inventory Trace") 40 | 41 | # Set up the Redfish object 42 | redfish_obj = None 43 | try: 44 | redfish_obj = redfish.redfish_client( 45 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 46 | ) 47 | redfish_obj.login(auth="session") 48 | except RedfishPasswordChangeRequiredError: 49 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 50 | sys.exit(1) 51 | except Exception: 52 | raise 53 | 54 | exit_code = 0 55 | try: 56 | firmware_inventory = redfish_utilities.get_firmware_inventory(redfish_obj) 57 | redfish_utilities.print_software_inventory(firmware_inventory, details=args.details, use_id=args.id) 58 | except Exception as e: 59 | if args.debug: 60 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 61 | exit_code = 1 62 | print(e) 63 | finally: 64 | # Log out 65 | redfish_utilities.logout(redfish_obj) 66 | sys.exit(exit_code) 67 | -------------------------------------------------------------------------------- /scripts/rf_licenses.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Licenses 8 | 9 | File : rf_licenses.py 10 | 11 | Brief : This script uses the redfish_utilities module to manage licenses 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import redfish 18 | import redfish_utilities 19 | import traceback 20 | import sys 21 | from redfish.messages import RedfishPasswordChangeRequiredError 22 | 23 | # Get the input arguments 24 | argget = argparse.ArgumentParser(description="A tool to manage licenses on a Redfish service") 25 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 26 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 27 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 28 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 29 | subparsers = argget.add_subparsers(dest="command") 30 | info_argget = subparsers.add_parser("info", help="Displays information about the licenses installed on the service") 31 | info_argget.add_argument( 32 | "--details", "-details", action="store_true", help="Indicates if the full details of each license should be shown" 33 | ) 34 | install_argget = subparsers.add_parser("install", help="Installs a new license") 35 | install_argget.add_argument( 36 | "--license", "-l", type=str, required=True, help="The filepath or URI to the license to install" 37 | ) 38 | delete_argget = subparsers.add_parser("delete", help="Deletes a license") 39 | delete_argget.add_argument("--license", "-l", type=str, required=True, help="The identifier of the license to delete") 40 | args = argget.parse_args() 41 | 42 | if args.debug: 43 | log_file = "rf_licenses-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 44 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 45 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 46 | logger.info("rf_licenses Trace") 47 | 48 | # Set up the Redfish object 49 | redfish_obj = None 50 | try: 51 | redfish_obj = redfish.redfish_client( 52 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 53 | ) 54 | redfish_obj.login(auth="session") 55 | except RedfishPasswordChangeRequiredError: 56 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 57 | sys.exit(1) 58 | except Exception: 59 | raise 60 | 61 | exit_code = 0 62 | try: 63 | if args.command == "install": 64 | print("Installing license '{}'...".format(args.license)) 65 | response = redfish_utilities.install_license(redfish_obj, args.license) 66 | response = redfish_utilities.poll_task_monitor(redfish_obj, response) 67 | redfish_utilities.verify_response(response) 68 | elif args.command == "delete": 69 | print("Deleting license '{}'...".format(args.license)) 70 | response = redfish_utilities.delete_license(redfish_obj, args.license) 71 | response = redfish_utilities.poll_task_monitor(redfish_obj, response) 72 | redfish_utilities.verify_response(response) 73 | else: 74 | licenses = redfish_utilities.get_licenses(redfish_obj) 75 | if args.command == "info": 76 | redfish_utilities.print_licenses(licenses, details=args.details) 77 | else: 78 | redfish_utilities.print_licenses(licenses) 79 | except Exception as e: 80 | if args.debug: 81 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 82 | exit_code = 1 83 | print(e) 84 | finally: 85 | # Log out 86 | redfish_utilities.logout(redfish_obj) 87 | sys.exit(exit_code) 88 | -------------------------------------------------------------------------------- /scripts/rf_logs.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Logs 8 | 9 | File : rf_logs.py 10 | 11 | Brief : This script uses the redfish_utilities module to manage logs 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import redfish 18 | import redfish_utilities 19 | import traceback 20 | import sys 21 | from redfish.messages import RedfishPasswordChangeRequiredError 22 | 23 | # Get the input arguments 24 | argget = argparse.ArgumentParser(description="A tool to manage logs on a Redfish service") 25 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 26 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 27 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 28 | argget.add_argument( 29 | "--manager", "-m", type=str, nargs="?", default=False, help="The ID of the manager containing the log service" 30 | ) 31 | argget.add_argument( 32 | "--system", "-s", type=str, nargs="?", default=False, help="The ID of the system containing the log service" 33 | ) 34 | argget.add_argument( 35 | "--chassis", "-c", type=str, nargs="?", default=False, help="The ID of the chassis containing the log service" 36 | ) 37 | argget.add_argument("--log", "-l", type=str, help="The ID of the resource containing the log service") 38 | argget.add_argument( 39 | "--details", "-details", action="store_true", help="Indicates details to be shown for each log entry" 40 | ) 41 | argget.add_argument("--clear", "-clear", action="store_true", help="Indicates if the log should be cleared") 42 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 43 | args = argget.parse_args() 44 | 45 | # Determine the target log service based on the inputs 46 | # Effectively if the user gives multiple targets, some will be ignored 47 | container_type = redfish_utilities.log_container.MANAGER 48 | container_id = None 49 | if args.manager is not False: 50 | container_type = redfish_utilities.log_container.MANAGER 51 | container_id = args.manager 52 | elif args.system is not False: 53 | container_type = redfish_utilities.log_container.SYSTEM 54 | container_id = args.system 55 | elif args.chassis is not False: 56 | container_type = redfish_utilities.log_container.CHASSIS 57 | container_id = args.chassis 58 | 59 | if args.debug: 60 | log_file = "rf_logs-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 61 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 62 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 63 | logger.info("rf_logs Trace") 64 | 65 | # Set up the Redfish object 66 | redfish_obj = None 67 | try: 68 | redfish_obj = redfish.redfish_client( 69 | base_url=args.rhost, username=args.user, password=args.password, timeout=30, max_retry=3 70 | ) 71 | redfish_obj.login(auth="session") 72 | except RedfishPasswordChangeRequiredError: 73 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 74 | sys.exit(1) 75 | except Exception: 76 | raise 77 | 78 | exit_code = 0 79 | try: 80 | # Either clear the logs or get/print the logs 81 | if args.clear: 82 | # Clear log was requested 83 | print("Clearing the log...") 84 | response = redfish_utilities.clear_log_entries(redfish_obj, container_type, container_id, args.log) 85 | response = redfish_utilities.poll_task_monitor(redfish_obj, response) 86 | redfish_utilities.verify_response(response) 87 | else: 88 | # Print log was requested 89 | log_entries = redfish_utilities.get_log_entries(redfish_obj, container_type, container_id, args.log) 90 | try: 91 | from signal import signal, SIGPIPE, SIG_DFL 92 | 93 | signal(SIGPIPE, SIG_DFL) 94 | except Exception: 95 | # Windows does not support SIGPIPE; no need to modify the handling 96 | pass 97 | redfish_utilities.print_log_entries(log_entries, args.details) 98 | except Exception as e: 99 | if args.debug: 100 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 101 | exit_code = 1 102 | print(e) 103 | finally: 104 | # Log out 105 | redfish_utilities.logout(redfish_obj) 106 | sys.exit(exit_code) 107 | -------------------------------------------------------------------------------- /scripts/rf_power_equipment.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Power Equipment 8 | 9 | File : rf_power_equipment.py 10 | 11 | Brief : This script uses the redfish_utilities module to manage power equipment 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import redfish 18 | import redfish_utilities 19 | import traceback 20 | import sys 21 | from redfish.messages import RedfishPasswordChangeRequiredError 22 | 23 | # Get the input arguments 24 | argget = argparse.ArgumentParser(description="A tool to manage power equipment") 25 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 26 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 27 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 28 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 29 | subparsers = argget.add_subparsers(dest="command") 30 | list_argget = subparsers.add_parser("list", help="Displays a list of the available power equipment") 31 | status_argget = subparsers.add_parser("status", help="Displays the status of an instance of power equipment") 32 | status_argget.add_argument( 33 | "--type", 34 | "-t", 35 | type=redfish_utilities.power_equipment_types, 36 | required=True, 37 | help="The type of power equipment to get", 38 | choices=redfish_utilities.power_equipment_types, 39 | ) 40 | status_argget.add_argument("--equipment", "-pe", type=str, help="The identifier of the power equipment to get") 41 | outletsummary_argget = subparsers.add_parser( 42 | "outlets", help="Displays the outlet summary of an instance of power equipment" 43 | ) 44 | outletsummary_argget.add_argument( 45 | "--type", 46 | "-t", 47 | type=redfish_utilities.power_equipment_types, 48 | required=True, 49 | help="The type of power equipment to get", 50 | choices=redfish_utilities.power_equipment_types, 51 | ) 52 | outletsummary_argget.add_argument("--equipment", "-pe", type=str, help="The identifier of the power equipment to get") 53 | outlet_info_argget = subparsers.add_parser( 54 | "outletinfo", help="Displays the status of an outlet for an instance of power equipment" 55 | ) 56 | outlet_info_argget.add_argument( 57 | "--type", 58 | "-t", 59 | type=redfish_utilities.power_equipment_types, 60 | required=True, 61 | help="The type of power equipment to get", 62 | choices=redfish_utilities.power_equipment_types, 63 | ) 64 | outlet_info_argget.add_argument("--equipment", "-pe", type=str, help="The identifier of the power equipment to get") 65 | outlet_info_argget.add_argument("--outlet", "-o", type=str, help="The identifier of the outlet to get") 66 | mains_info_argget = subparsers.add_parser( 67 | "mainsinfo", help="Displays the status of a mains circuit for an instance of power equipment" 68 | ) 69 | mains_info_argget.add_argument( 70 | "--type", 71 | "-t", 72 | type=redfish_utilities.power_equipment_types, 73 | required=True, 74 | help="The type of power equipment to get", 75 | choices=redfish_utilities.power_equipment_types, 76 | ) 77 | mains_info_argget.add_argument("--equipment", "-pe", type=str, help="The identifier of the power equipment to get") 78 | mains_info_argget.add_argument("--mains", "-m", type=str, help="The identifier of the mains circuit to get") 79 | branch_info_argget = subparsers.add_parser( 80 | "branchinfo", help="Displays the status of a branch circuit for an instance of power equipment" 81 | ) 82 | branch_info_argget.add_argument( 83 | "--type", 84 | "-t", 85 | type=redfish_utilities.power_equipment_types, 86 | required=True, 87 | help="The type of power equipment to get", 88 | choices=redfish_utilities.power_equipment_types, 89 | ) 90 | branch_info_argget.add_argument("--equipment", "-pe", type=str, help="The identifier of the power equipment to get") 91 | branch_info_argget.add_argument("--branch", "-b", type=str, help="The identifier of the branch circuit to get") 92 | args = argget.parse_args() 93 | 94 | if args.debug: 95 | log_file = "rf_power_equipment-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 96 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 97 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 98 | logger.info("rf_power_equipment Trace") 99 | 100 | # Set up the Redfish object 101 | redfish_obj = None 102 | try: 103 | redfish_obj = redfish.redfish_client( 104 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 105 | ) 106 | redfish_obj.login(auth="session") 107 | except RedfishPasswordChangeRequiredError: 108 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 109 | sys.exit(1) 110 | except Exception: 111 | raise 112 | 113 | exit_code = 0 114 | try: 115 | if args.command == "status": 116 | power_equipment = redfish_utilities.get_power_equipment( 117 | redfish_obj, 118 | args.type, 119 | args.equipment, 120 | get_metrics=True, 121 | get_mains=True, 122 | get_branches=True, 123 | get_feeders=True, 124 | get_subfeeds=True, 125 | ) 126 | redfish_utilities.print_power_equipment(power_equipment) 127 | circuit_types = [ 128 | redfish_utilities.power_equipment_electrical_types.MAINS, 129 | redfish_utilities.power_equipment_electrical_types.BRANCH, 130 | redfish_utilities.power_equipment_electrical_types.FEEDER, 131 | redfish_utilities.power_equipment_electrical_types.SUBFEED, 132 | ] 133 | for circuit_type in circuit_types: 134 | redfish_utilities.print_power_equipment_electrical_summary(power_equipment, circuit_type, False) 135 | elif args.command == "outlets": 136 | power_equipment = redfish_utilities.get_power_equipment( 137 | redfish_obj, args.type, args.equipment, get_outlets=True 138 | ) 139 | redfish_utilities.print_power_equipment_electrical_summary( 140 | power_equipment, redfish_utilities.power_equipment_electrical_types.OUTLET, True 141 | ) 142 | elif args.command == "outletinfo": 143 | outlet = redfish_utilities.get_power_equipment_electrical( 144 | redfish_obj, 145 | args.type, 146 | redfish_utilities.power_equipment_electrical_types.OUTLET, 147 | args.equipment, 148 | args.outlet, 149 | ) 150 | redfish_utilities.print_power_equipment_electrical(outlet) 151 | elif args.command == "mainsinfo": 152 | mains = redfish_utilities.get_power_equipment_electrical( 153 | redfish_obj, args.type, redfish_utilities.power_equipment_electrical_types.MAINS, args.equipment, args.mains 154 | ) 155 | redfish_utilities.print_power_equipment_electrical(mains) 156 | elif args.command == "branchinfo": 157 | mains = redfish_utilities.get_power_equipment_electrical( 158 | redfish_obj, 159 | args.type, 160 | redfish_utilities.power_equipment_electrical_types.BRANCH, 161 | args.equipment, 162 | args.branch, 163 | ) 164 | redfish_utilities.print_power_equipment_electrical(mains) 165 | else: 166 | power_equipment = redfish_utilities.get_power_equipment_summary(redfish_obj) 167 | redfish_utilities.print_power_equipment_summary(power_equipment) 168 | except Exception as e: 169 | if args.debug: 170 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 171 | exit_code = 1 172 | print(e) 173 | finally: 174 | # Log out 175 | redfish_utilities.logout(redfish_obj) 176 | sys.exit(exit_code) 177 | -------------------------------------------------------------------------------- /scripts/rf_power_reset.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Power Reset 8 | 9 | File : rf_power_reset.py 10 | 11 | Brief : This script uses the redfish_utilities module to perform a reset of the system 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import redfish 18 | import redfish_utilities 19 | import traceback 20 | import sys 21 | from redfish.messages import RedfishPasswordChangeRequiredError 22 | 23 | # Get the input arguments 24 | argget = argparse.ArgumentParser(description="A tool to perform a power/reset operation of a system") 25 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 26 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 27 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 28 | argget.add_argument("--system", "-s", type=str, help="The ID of the system to reset") 29 | argget.add_argument( 30 | "--type", "-t", type=str, help="The type of power/reset operation to perform", choices=redfish_utilities.reset_types 31 | ) 32 | argget.add_argument( 33 | "--info", "-info", action="store_true", help="Indicates if reset and power information should be reported" 34 | ) 35 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 36 | args = argget.parse_args() 37 | 38 | if args.debug: 39 | log_file = "rf_power_reset-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 40 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 41 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 42 | logger.info("rf_power_reset Trace") 43 | 44 | # Set up the Redfish object 45 | redfish_obj = None 46 | try: 47 | redfish_obj = redfish.redfish_client( 48 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 49 | ) 50 | redfish_obj.login(auth="session") 51 | except RedfishPasswordChangeRequiredError: 52 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 53 | sys.exit(1) 54 | except Exception: 55 | raise 56 | 57 | exit_code = 0 58 | try: 59 | if args.info: 60 | system_info = redfish_utilities.get_system(redfish_obj, args.system) 61 | reset_uri, reset_parameters = redfish_utilities.get_system_reset_info(redfish_obj, args.system, system_info) 62 | printed_reset_types = False 63 | for param in reset_parameters: 64 | if param["Name"] == "ResetType" and "AllowableValues" in param: 65 | print("Supported reset types: {}".format(", ".join(param["AllowableValues"]))) 66 | printed_reset_types = True 67 | if not printed_reset_types: 68 | print("No reset information found") 69 | if "PowerState" in system_info.dict: 70 | print("Current power state: {}".format(system_info.dict["PowerState"])) 71 | else: 72 | print("No power state information found") 73 | else: 74 | print("Resetting the system...") 75 | response = redfish_utilities.system_reset(redfish_obj, args.system, args.type) 76 | response = redfish_utilities.poll_task_monitor(redfish_obj, response) 77 | redfish_utilities.verify_response(response) 78 | except Exception as e: 79 | if args.debug: 80 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 81 | exit_code = 1 82 | print(e) 83 | finally: 84 | # Log out 85 | redfish_utilities.logout(redfish_obj) 86 | sys.exit(exit_code) 87 | -------------------------------------------------------------------------------- /scripts/rf_raw_request.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Raw Request 8 | 9 | File : rf_raw_request.py 10 | 11 | Brief : This script performs a raw request specified by the user 12 | """ 13 | 14 | import argparse 15 | import json 16 | import os 17 | import redfish 18 | import redfish_utilities 19 | import sys 20 | from redfish.messages import RedfishPasswordChangeRequiredError 21 | 22 | 23 | def ifmatch_header(redfish_obj, path, headers=None): 24 | """ 25 | Generates If-Match header for PATCH and PUT operations 26 | 27 | Args: 28 | redfish_obj: The Redfish client object with an open session 29 | path: The URI of the resource 30 | headers: Dictionary of HTTP headers to provide in the request 31 | 32 | Returns: 33 | Updated dictionary of HTTP headers with If-Match, if an ETag was found 34 | """ 35 | 36 | if headers is None: 37 | headers = {} 38 | try: 39 | response = redfish_obj.get(path) 40 | etag = response.getheader("ETag") 41 | if etag is not None: 42 | headers["If-Match"] = etag 43 | except Exception: 44 | pass 45 | return headers 46 | 47 | 48 | # Get the input arguments 49 | argget = argparse.ArgumentParser(description="A tool perform a raw request to a Redfish service") 50 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 51 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 52 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 53 | argget.add_argument( 54 | "--method", 55 | "-m", 56 | type=str, 57 | required=False, 58 | help="The HTTP method to perform; performs GET if not specified", 59 | default="GET", 60 | choices=["GET", "HEAD", "POST", "PATCH", "PUT", "DELETE"], 61 | ) 62 | argget.add_argument("--request", "-req", type=str, required=True, help="The URI for the request") 63 | argget.add_argument( 64 | "--body", 65 | "-b", 66 | type=str, 67 | required=False, 68 | help="The body to provide with the request; can be a JSON string for a JSON request, a filename to send binary data, or an unstructured string", 69 | ) 70 | argget.add_argument( 71 | "--verbose", 72 | "-v", 73 | action="store_true", 74 | help="Indicates if HTTP response codes and headers are displayed", 75 | default=False, 76 | ) 77 | args = argget.parse_args() 78 | 79 | # Connect to the service 80 | redfish_obj = None 81 | try: 82 | redfish_obj = redfish.redfish_client( 83 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 84 | ) 85 | redfish_obj.login(auth="session") 86 | except RedfishPasswordChangeRequiredError: 87 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 88 | sys.exit(1) 89 | except Exception: 90 | raise 91 | 92 | # Encode the body 93 | # If the body argument points to a file, load the file 94 | if args.body is not None and os.path.isfile(args.body): 95 | with open(args.body, mode="rb") as file: 96 | body = file.read() 97 | else: 98 | # Not a file; either JSON or a raw string 99 | try: 100 | body = json.loads(args.body) 101 | except Exception: 102 | body = args.body 103 | if body is None: 104 | # Default case if nothing resolves (empty JSON object) 105 | body = {} 106 | 107 | headers = {} 108 | # Perform the requested operation 109 | if args.method == "HEAD": 110 | resp = redfish_obj.head(args.request) 111 | elif args.method == "POST": 112 | resp = redfish_obj.post(args.request, body=body) 113 | elif args.method == "PATCH": 114 | headers = ifmatch_header(redfish_obj, args.request, headers=headers) 115 | resp = redfish_obj.patch(args.request, body=body, headers=headers) 116 | elif args.method == "PUT": 117 | headers = ifmatch_header(redfish_obj, args.request, headers=headers) 118 | resp = redfish_obj.put(args.request, body=body, headers=headers) 119 | elif args.method == "DELETE": 120 | resp = redfish_obj.delete(args.request) 121 | else: 122 | resp = redfish_obj.get(args.request) 123 | 124 | # Print HTTP status and headers 125 | if args.verbose: 126 | print("HTTP {}".format(resp.status)) 127 | for header in resp.getheaders(): 128 | print("{}: {}".format(header[0], header[1])) 129 | print() 130 | 131 | # Print the response 132 | if resp.status != 204: 133 | try: 134 | print(json.dumps(resp.dict, sort_keys=True, indent=4, separators=(",", ": "))) 135 | except Exception: 136 | # The response is either malformed JSON or not JSON at all 137 | print(resp.text) 138 | else: 139 | print("No response body") 140 | 141 | # Log out 142 | redfish_utilities.logout(redfish_obj) 143 | sys.exit(0) 144 | -------------------------------------------------------------------------------- /scripts/rf_sel.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish SEL 8 | 9 | File : rf_sel.py 10 | 11 | Brief : This script uses the redfish_utilities module to manage the SEL 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import redfish 18 | import redfish_utilities 19 | import traceback 20 | import sys 21 | from redfish.messages import RedfishPasswordChangeRequiredError 22 | 23 | # Get the input arguments 24 | argget = argparse.ArgumentParser(description="A tool to manage the SEL on a Redfish service") 25 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 26 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 27 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 28 | argget.add_argument( 29 | "--details", "-details", action="store_true", help="Indicates details to be shown for each log entry" 30 | ) 31 | argget.add_argument("--clear", "-clear", action="store_true", help="Indicates if the log should be cleared") 32 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 33 | args = argget.parse_args() 34 | 35 | if args.debug: 36 | log_file = "rf_sel-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 37 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 38 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 39 | logger.info("rf_sel Trace") 40 | 41 | # Set up the Redfish object 42 | redfish_obj = None 43 | try: 44 | redfish_obj = redfish.redfish_client( 45 | base_url=args.rhost, username=args.user, password=args.password, timeout=30, max_retry=3 46 | ) 47 | redfish_obj.login(auth="session") 48 | except RedfishPasswordChangeRequiredError: 49 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 50 | sys.exit(1) 51 | except Exception: 52 | raise 53 | 54 | exit_code = 0 55 | try: 56 | # Find the SEL 57 | match = False 58 | for container_type in [redfish_utilities.log_container.MANAGER, redfish_utilities.log_container.SYSTEM]: 59 | if container_type == redfish_utilities.log_container.MANAGER: 60 | container_ids = redfish_utilities.get_manager_ids(redfish_obj) 61 | else: 62 | container_ids = redfish_utilities.get_system_ids(redfish_obj) 63 | for container in container_ids: 64 | try: 65 | container, log_service_ids = redfish_utilities.get_log_service_ids( 66 | redfish_obj, container_type=container_type, container_id=container 67 | ) 68 | for log_service in log_service_ids: 69 | log_service_resp = redfish_utilities.get_log_service( 70 | redfish_obj, container_type=container_type, container_id=container, log_service_id=log_service 71 | ) 72 | if ( 73 | log_service_resp.dict.get("LogEntryType") == "SEL" 74 | or log_service_resp.dict["Id"].upper() == "SEL" 75 | ): 76 | match = True 77 | break 78 | except Exception: 79 | pass 80 | if match: 81 | break 82 | if match: 83 | break 84 | if match: 85 | # Either clear the logs or get/print the logs 86 | if args.clear: 87 | # Clear log was requested 88 | print("Clearing the SEL...") 89 | response = redfish_utilities.clear_log_entries(redfish_obj, log_service=log_service_resp) 90 | response = redfish_utilities.poll_task_monitor(redfish_obj, response) 91 | redfish_utilities.verify_response(response) 92 | else: 93 | # Print log was requested 94 | log_entries = redfish_utilities.get_log_entries(redfish_obj, log_service=log_service_resp) 95 | try: 96 | from signal import signal, SIGPIPE, SIG_DFL 97 | 98 | signal(SIGPIPE, SIG_DFL) 99 | except Exception: 100 | # Windows does not support SIGPIPE; no need to modify the handling 101 | pass 102 | redfish_utilities.print_log_entries(log_entries, args.details) 103 | else: 104 | print("No SEL found") 105 | except Exception as e: 106 | if args.debug: 107 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 108 | exit_code = 1 109 | print(e) 110 | finally: 111 | # Log out 112 | redfish_utilities.logout(redfish_obj) 113 | sys.exit(exit_code) 114 | -------------------------------------------------------------------------------- /scripts/rf_sensor_list.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Sensor List 8 | 9 | File : rf_sensor_list.py 10 | 11 | Brief : This script uses the redfish_utilities module to dump sensor info 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import redfish 18 | import redfish_utilities 19 | import traceback 20 | import sys 21 | from redfish.messages import RedfishPasswordChangeRequiredError 22 | 23 | # Get the input arguments 24 | argget = argparse.ArgumentParser(description="A tool to walk a Redfish service and list sensor info") 25 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 26 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 27 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 28 | argget.add_argument("--id", "-i", action="store_true", help="Construct sensor names using 'Id' values") 29 | argget.add_argument("--name", "-n", action="store_true", help="Construct sensor names using 'Name' values") 30 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 31 | args = argget.parse_args() 32 | 33 | if args.debug: 34 | log_file = "rf_sensor_list-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 35 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 36 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 37 | logger.info("rf_sensor_list Trace") 38 | 39 | # Set up the Redfish object 40 | redfish_obj = None 41 | try: 42 | redfish_obj = redfish.redfish_client( 43 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 44 | ) 45 | redfish_obj.login(auth="session") 46 | except RedfishPasswordChangeRequiredError: 47 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 48 | sys.exit(1) 49 | except Exception: 50 | raise 51 | 52 | exit_code = 0 53 | try: 54 | use_id = False 55 | if args.id: 56 | use_id = True 57 | if args.name: 58 | use_id = False 59 | # Get and print the sensor info 60 | sensors = redfish_utilities.get_sensors(redfish_obj, use_id) 61 | redfish_utilities.print_sensors(sensors) 62 | except Exception as e: 63 | if args.debug: 64 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 65 | exit_code = 1 66 | print(e) 67 | finally: 68 | # Log out 69 | redfish_utilities.logout(redfish_obj) 70 | sys.exit(exit_code) 71 | -------------------------------------------------------------------------------- /scripts/rf_sys_inventory.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish System Inventory 8 | 9 | File : rf_sys_inventory.py 10 | 11 | Brief : This script uses the redfish_utilities module to dump system inventory 12 | information 13 | """ 14 | 15 | import argparse 16 | import datetime 17 | import logging 18 | import redfish 19 | import redfish_utilities 20 | import traceback 21 | import sys 22 | from redfish.messages import RedfishPasswordChangeRequiredError 23 | 24 | # Get the input arguments 25 | argget = argparse.ArgumentParser(description="A tool to walk a Redfish service and list component information") 26 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 27 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 28 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 29 | argget.add_argument( 30 | "--details", "-details", action="store_true", help="Indicates if the full details of each component should be shown" 31 | ) 32 | argget.add_argument( 33 | "--noabsent", "-noabsent", action="store_true", help="Indicates if absent devices should be skipped" 34 | ) 35 | argget.add_argument( 36 | "--write", 37 | "-w", 38 | nargs="?", 39 | const="Device_Inventory", 40 | type=str, 41 | help="Indicates if the inventory should be written to a spreadsheet and what the file name should be if given", 42 | ) 43 | argget.add_argument( 44 | "--workaround", 45 | "-workaround", 46 | action="store_true", 47 | help="Indicates if workarounds should be attempted for non-conformant services", 48 | default=False, 49 | ) 50 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 51 | args = argget.parse_args() 52 | 53 | if args.workaround: 54 | redfish_utilities.config.__workarounds__ = True 55 | 56 | if args.debug: 57 | log_file = "rf_sys_inventory-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 58 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 59 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 60 | logger.info("rf_sys_inventory Trace") 61 | 62 | # Set up the Redfish object 63 | redfish_obj = None 64 | try: 65 | redfish_obj = redfish.redfish_client( 66 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 67 | ) 68 | redfish_obj.login(auth="session") 69 | except RedfishPasswordChangeRequiredError: 70 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 71 | sys.exit(1) 72 | except Exception: 73 | raise 74 | 75 | exit_code = 0 76 | try: 77 | # Get and print the system inventory 78 | inventory = redfish_utilities.get_system_inventory(redfish_obj) 79 | redfish_utilities.print_system_inventory(inventory, args.details, args.noabsent) 80 | 81 | if args.write: 82 | redfish_utilities.write_system_inventory(inventory, args.write) 83 | except Exception as e: 84 | if args.debug: 85 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 86 | exit_code = 1 87 | print(e) 88 | finally: 89 | # Log out 90 | redfish_utilities.logout(redfish_obj) 91 | sys.exit(exit_code) 92 | -------------------------------------------------------------------------------- /scripts/rf_test_event_listener.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Test Event Listener 8 | 9 | File : rf_test_event_listener.py 10 | 11 | Brief : This script performs POST operations with event payloads to help verify 12 | an event listener. 13 | """ 14 | 15 | import argparse 16 | import json 17 | import requests 18 | from datetime import datetime 19 | 20 | event_headers = {"Content-Type": "application/json"} 21 | 22 | event_payload = { 23 | "@odata.type": "#Event.v1_7_0.Event", 24 | "Id": "1", 25 | "Name": "Sample Event", 26 | "Context": "Sample Event for Listener", 27 | "Events": [ 28 | { 29 | "EventType": "Other", 30 | "EventId": "1", 31 | "Severity": "OK", 32 | "MessageSeverity": "OK", 33 | "Message": "Test message.", 34 | "MessageId": "Resource.1.3.TestMessage", 35 | } 36 | ], 37 | } 38 | 39 | argget = argparse.ArgumentParser(description="A tool to help verify a Redfish event listener") 40 | argget.add_argument( 41 | "--listener", "-l", type=str, required=True, help="The absolute URI of the Redfish event listener (with scheme)" 42 | ) 43 | argget.add_argument( 44 | "--file", 45 | "-file", 46 | type=str, 47 | help="The filepath to a JSON file containing the event payload; if this argument is specified, all other arguments controlling the event data is ignored", 48 | ) 49 | argget.add_argument("--id", "-id", type=str, help="The value to specify in the Id property of the event") 50 | argget.add_argument("--name", "-name", type=str, help="The value to specify in the Name property of the event") 51 | argget.add_argument("--context", "-context", type=str, help="The value to specify in the Context property of the event") 52 | argget.add_argument( 53 | "--eventtype", "-eventtype", type=str, help="The value to specify in the EventType property of the event" 54 | ) 55 | argget.add_argument("--eventid", "-eventid", type=str, help="The value to specify in the EventId property of the event") 56 | argget.add_argument( 57 | "--severity", "-severity", type=str, help="The value to specify in the Severity property of the event" 58 | ) 59 | argget.add_argument("--message", "-message", type=str, help="The value to specify in the Message property of the event") 60 | argget.add_argument( 61 | "--messageid", "-messageid", type=str, help="The value to specify in the MessageId property of the event" 62 | ) 63 | argget.add_argument( 64 | "--timestamp", "-timestamp", type=str, help="The value to specify in the EventTimestamp property of the event" 65 | ) 66 | argget.add_argument( 67 | "--header", 68 | "-header", 69 | type=str, 70 | nargs=2, 71 | metavar=("name", "value"), 72 | action="append", 73 | help="Name-value pairs of HTTP headers to provide with the request", 74 | ) 75 | args = argget.parse_args() 76 | 77 | # Update the event payload based on the specified arguments 78 | if args.file: 79 | with open(args.file) as json_file: 80 | event_payload = json.load(json_file) 81 | else: 82 | if args.id: 83 | event_payload["Id"] = args.id 84 | if args.name: 85 | event_payload["Name"] = args.name 86 | if args.context: 87 | event_payload["Context"] = args.context 88 | if args.eventtype: 89 | event_payload["Events"][0]["EventType"] = args.eventtype 90 | if args.eventid: 91 | event_payload["Events"][0]["EventId"] = args.eventid 92 | if args.severity: 93 | event_payload["Events"][0]["Severity"] = args.severity 94 | event_payload["Events"][0]["MessageSeverity"] = args.severity 95 | if args.message: 96 | event_payload["Events"][0]["Message"] = args.message 97 | if args.messageid: 98 | event_payload["Events"][0]["MessageId"] = args.messageid 99 | if args.timestamp: 100 | event_payload["Events"][0]["EventTimestamp"] = args.timestamp 101 | else: 102 | event_payload["Events"][0]["EventTimestamp"] = datetime.now().replace(microsecond=0).astimezone().isoformat() 103 | 104 | # Update the HTTP headers based on the specified arguments 105 | if args.header: 106 | for header in args.header: 107 | event_headers[header[0]] = header[1] 108 | 109 | # Send the request 110 | response = requests.post(args.listener, json=event_payload, headers=event_headers, timeout=15, verify=False) 111 | print("Listener responded with {} {}".format(response.status_code, response.reason)) 112 | -------------------------------------------------------------------------------- /scripts/rf_update.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Update 8 | 9 | File : rf_update.py 10 | 11 | Brief : This script uses the redfish_utilities module to perform updates 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import os 18 | import re 19 | import redfish 20 | import redfish_utilities 21 | import shutil 22 | import socket 23 | import sys 24 | import threading 25 | import time 26 | import traceback 27 | from redfish.messages import RedfishPasswordChangeRequiredError 28 | 29 | if sys.version_info > (3,): 30 | import http.server 31 | else: 32 | import SimpleHTTPServer 33 | import SocketServer 34 | 35 | WEB_SERVER_PORT = 8888 36 | WEB_SERVER_FOLDER = "rf_update_server" 37 | 38 | 39 | def local_web_server(filepath): 40 | """ 41 | Creates a web server to host the image file 42 | 43 | Args: 44 | filepath: The filepath for the image 45 | """ 46 | 47 | # Create a temporary folder and move the file to the folder 48 | try: 49 | shutil.rmtree(WEB_SERVER_FOLDER) 50 | except Exception: 51 | pass 52 | os.mkdir(WEB_SERVER_FOLDER) 53 | shutil.copy(filepath, WEB_SERVER_FOLDER) 54 | os.chdir(WEB_SERVER_FOLDER) 55 | 56 | # Create a web server and host it out of the temporary folder 57 | if sys.version_info > (3,): 58 | handler = http.server.SimpleHTTPRequestHandler 59 | httpd = http.server.HTTPServer(("", WEB_SERVER_PORT), handler) 60 | else: 61 | handler = SimpleHTTPServer.SimpleHTTPRequestHandler 62 | httpd = SocketServer.TCPServer(("", WEB_SERVER_PORT), handler) 63 | httpd.serve_forever() 64 | 65 | return 66 | 67 | 68 | def print_error_payload(response): 69 | """ 70 | Prints an error payload, which can also be used for action responses 71 | 72 | Args: 73 | response: The response to print 74 | """ 75 | 76 | try: 77 | print(redfish_utilities.get_error_messages(response)) 78 | except Exception: 79 | # No response body 80 | if response.status >= 400: 81 | print("Failed") 82 | else: 83 | print("Success") 84 | 85 | 86 | # Get the input arguments 87 | argget = argparse.ArgumentParser(description="A tool to perform an update with a Redfish service") 88 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 89 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 90 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 91 | argget.add_argument("--image", "-i", type=str, required=True, help="The URI or filepath of the image") 92 | argget.add_argument("--target", "-t", type=str, help="The target resource to apply the image") 93 | argget.add_argument( 94 | "--applytime", 95 | "-at", 96 | type=redfish_utilities.operation_apply_times, 97 | help="The apply time for the update", 98 | choices=redfish_utilities.operation_apply_times, 99 | ) 100 | argget.add_argument( 101 | "--timeout", 102 | "-timeout", 103 | type=int, 104 | help="The timeout, in seconds, to transfer the image; by default this is 2 seconds per MB", 105 | ) 106 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 107 | args = argget.parse_args() 108 | 109 | if args.debug: 110 | log_file = "rf_update-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 111 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 112 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 113 | logger.info("rf_update Trace") 114 | 115 | # Set up the Redfish object 116 | redfish_obj = None 117 | try: 118 | redfish_obj = redfish.redfish_client( 119 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 120 | ) 121 | redfish_obj.login(auth="session") 122 | except RedfishPasswordChangeRequiredError: 123 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 124 | sys.exit(1) 125 | except Exception: 126 | raise 127 | 128 | start_path = os.getcwd() 129 | targets = None 130 | if args.target is not None: 131 | targets = [args.target] 132 | exit_code = 0 133 | try: 134 | # Determine what path to use to perform the update 135 | update_service = redfish_utilities.get_update_service(redfish_obj) 136 | if os.path.isfile(args.image): 137 | # Local image; see if we can push the image directly 138 | if "MultipartHttpPushUri" in update_service.dict: 139 | # Perform a multipart push update 140 | print( 141 | "Pushing the image to the service directly; depending on the size of the image, this can take a few minutes..." 142 | ) 143 | response = redfish_utilities.multipart_push_update( 144 | redfish_obj, args.image, targets=targets, timeout=args.timeout, apply_time=args.applytime 145 | ) 146 | else: 147 | # Host a local web server and perform a SimpleUpdate for the local image 148 | web_server_thread = threading.Thread(target=local_web_server, args=(args.image,)) 149 | web_server_thread.setDaemon(True) 150 | web_server_thread.start() 151 | # Wait for the server to start up 152 | time.sleep(3) 153 | 154 | # Build the proper image URI for the call based on how the web server will be hosting it 155 | # TODO: Find a better way of getting your own IP address 156 | # socket.gethostbyname( socket.gethostname() ) returns 127.0.0.1 on many systems 157 | # This will open a socket with the target, and pulls the address of the socket 158 | groups = re.search("^(https?)://([^:]+)(:(\\d+))?$", args.rhost) 159 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 160 | remote_port = groups.group(4) 161 | if remote_port is None: 162 | remote_port = "80" 163 | if groups.group(1) == "https": 164 | remote_port = "443" 165 | s.connect((groups.group(2), int(remote_port))) 166 | image_uri = "http://{}:{}/{}".format( 167 | s.getsockname()[0], WEB_SERVER_PORT, args.image.rsplit(os.path.sep, 1)[-1] 168 | ) 169 | s.close() 170 | response = redfish_utilities.simple_update( 171 | redfish_obj, image_uri, targets=targets, apply_time=args.applytime 172 | ) 173 | else: 174 | # Remote image; always use SimpleUpdate 175 | response = redfish_utilities.simple_update(redfish_obj, args.image, targets=targets, apply_time=args.applytime) 176 | 177 | # Monitor the response 178 | print("Update initiated...") 179 | response = redfish_utilities.poll_task_monitor(redfish_obj, response) 180 | 181 | # Display the results 182 | print("") 183 | print_error_payload(response) 184 | except Exception as e: 185 | if args.debug: 186 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 187 | exit_code = 1 188 | print(e) 189 | finally: 190 | # Log out and cleanup 191 | os.chdir(start_path) 192 | try: 193 | shutil.rmtree(WEB_SERVER_FOLDER) 194 | except Exception: 195 | pass 196 | # Log out 197 | redfish_utilities.logout(redfish_obj) 198 | sys.exit(exit_code) 199 | -------------------------------------------------------------------------------- /scripts/rf_virtual_media.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | """ 7 | Redfish Virtual Media 8 | 9 | File : rf_virtual_media.py 10 | 11 | Brief : This script uses the redfish_utilities module to manage virtual media 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import logging 17 | import redfish 18 | import redfish_utilities 19 | import traceback 20 | import sys 21 | from redfish.messages import RedfishPasswordChangeRequiredError 22 | 23 | # Get the input arguments 24 | argget = argparse.ArgumentParser(description="A tool to manage virtual media of a system") 25 | argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") 26 | argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") 27 | argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") 28 | argget.add_argument("--system", "-s", type=str, help="The ID of the system containing the virtual media") 29 | argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") 30 | subparsers = argget.add_subparsers(dest="command") 31 | info_argget = subparsers.add_parser("info", help="Displays information about the virtual media for a system") 32 | insert_argget = subparsers.add_parser("insert", help="Inserts virtual media for a system") 33 | insert_argget.add_argument("--image", "-image", type=str, required=True, help="The URI of the image to insert") 34 | insert_argget.add_argument("--id", "-i", type=str, help="The identifier of the virtual media instance to insert") 35 | insert_argget.add_argument( 36 | "--notinserted", 37 | "-notinserted", 38 | action="store_false", 39 | help="Indicates if the media is to be marked as not inserted for the system", 40 | default=None, 41 | ) 42 | insert_argget.add_argument( 43 | "--writable", 44 | "-writable", 45 | action="store_false", 46 | help="Indicates if the media is to be marked as writable for the system", 47 | default=None, 48 | ) 49 | insert_argget.add_argument( 50 | "--mediatypes", "-mt", type=str, nargs="+", help="A list of acceptable media types for the virtual media" 51 | ) 52 | eject_argget = subparsers.add_parser("eject", help="Ejects virtual media from a system") 53 | eject_argget.add_argument( 54 | "--id", "-i", type=str, required=True, help="The identifier of the virtual media instance to eject" 55 | ) 56 | args = argget.parse_args() 57 | 58 | if args.debug: 59 | log_file = "rf_virtual_media-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) 60 | log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 61 | logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) 62 | logger.info("rf_virtual_media Trace") 63 | 64 | # Set up the Redfish object 65 | redfish_obj = None 66 | try: 67 | redfish_obj = redfish.redfish_client( 68 | base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 69 | ) 70 | redfish_obj.login(auth="session") 71 | except RedfishPasswordChangeRequiredError: 72 | redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) 73 | sys.exit(1) 74 | except Exception: 75 | raise 76 | 77 | exit_code = 0 78 | try: 79 | if args.command == "insert": 80 | print("Inserting '{}'".format(args.image)) 81 | redfish_utilities.insert_virtual_media( 82 | redfish_obj, args.image, args.system, args.id, args.mediatypes, args.notinserted, args.writable 83 | ) 84 | elif args.command == "eject": 85 | print("Ejecting '{}'".format(args.id)) 86 | redfish_utilities.eject_virtual_media(redfish_obj, args.id, args.system) 87 | else: 88 | virtual_media = redfish_utilities.get_virtual_media(redfish_obj, args.system) 89 | redfish_utilities.print_virtual_media(virtual_media) 90 | except Exception as e: 91 | if args.debug: 92 | logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) 93 | exit_code = 1 94 | print(e) 95 | finally: 96 | # Log out 97 | redfish_utilities.logout(redfish_obj) 98 | sys.exit(exit_code) 99 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # Copyright Notice: 3 | # Copyright 2019-2025 DMTF. All rights reserved. 4 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md 5 | 6 | from setuptools import setup 7 | from setuptools import Command as _Command 8 | from codecs import open 9 | import os 10 | 11 | 12 | class Pyinstaller(_Command): 13 | description: "Pyinstaller" 14 | user_options = [] 15 | 16 | def __init__(self, dist, **kw): 17 | super().__init__(dist, **kw) 18 | self.flags = "" 19 | self.scripts = [] 20 | self.packages = [] 21 | if not os.path.exists("./dist"): 22 | os.mkdir("dist") 23 | if not os.path.exists("./spec"): 24 | os.mkdir("spec") 25 | 26 | def initialize_options(self): 27 | self.flags = "" 28 | self.scripts = [] 29 | self.packages = [] 30 | 31 | def finalize_options(self): 32 | self.flags = "--specpath ./spec" 33 | self.scripts = self.distribution.scripts 34 | self.packages = self.distribution.packages 35 | for package in self.packages: 36 | self.flags = "{} --collect-all {}".format(self.flags, package) 37 | 38 | def pyinstaller(self, target): 39 | if os.system("pyinstaller --onefile {} {}".format(target, self.flags)): 40 | raise Exception("PyInstaller failed!") 41 | 42 | def run(self): 43 | for scripts in self.scripts: 44 | self.pyinstaller(scripts) 45 | 46 | 47 | with open("README.md", "r", "utf-8") as f: 48 | long_description = f.read() 49 | 50 | setup( 51 | name="redfish_utilities", 52 | version="3.4.1", 53 | description="Redfish Utilities", 54 | long_description=long_description, 55 | long_description_content_type="text/markdown", 56 | author="DMTF, https://www.dmtf.org/standards/feedback", 57 | license='BSD 3-clause "New" or "Revised License"', 58 | classifiers=[ 59 | "Development Status :: 5 - Production/Stable", 60 | "License :: OSI Approved :: BSD License", 61 | "Programming Language :: Python :: 3", 62 | "Topic :: Communications", 63 | ], 64 | keywords="Redfish", 65 | url="https://github.com/DMTF/Redfish-Tacklebox", 66 | packages=["redfish_utilities"], 67 | scripts=[ 68 | "scripts/rf_accounts.py", 69 | "scripts/rf_assembly.py", 70 | "scripts/rf_bios_settings.py", 71 | "scripts/rf_boot_override.py", 72 | "scripts/rf_certificates.py", 73 | "scripts/rf_diagnostic_data.py", 74 | "scripts/rf_discover.py", 75 | "scripts/rf_event_service.py", 76 | "scripts/rf_firmware_inventory.py", 77 | "scripts/rf_licenses.py", 78 | "scripts/rf_logs.py", 79 | "scripts/rf_manager_config.py", 80 | "scripts/rf_power_equipment.py", 81 | "scripts/rf_power_reset.py", 82 | "scripts/rf_raw_request.py", 83 | "scripts/rf_sel.py", 84 | "scripts/rf_sensor_list.py", 85 | "scripts/rf_sys_inventory.py", 86 | "scripts/rf_test_event_listener.py", 87 | "scripts/rf_thermal_equipment.py", 88 | "scripts/rf_update.py", 89 | "scripts/rf_virtual_media.py", 90 | ], 91 | install_requires=["redfish>=3.2.1", "XlsxWriter>=1.2.7", "requests"], 92 | cmdclass={"pyinstaller": Pyinstaller}, 93 | ) 94 | --------------------------------------------------------------------------------