Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
75f0fc1
fix: --export-scp hang bug.
sadsfae Apr 15, 2026
7a73d7a
chore: bump CI python version
sadsfae Apr 15, 2026
69e7c2c
chore: fix test with duplicate 75% response where it's skipped
sadsfae Apr 15, 2026
cfe2f71
chore: fix codecov
sadsfae Apr 15, 2026
42b7441
feat: add --insecure flag and SSL verification.
sadsfae Apr 16, 2026
2dd9971
chore: fix tests again
sadsfae Apr 16, 2026
6fdc73c
chore: hopefully fix rest of tests
sadsfae Apr 16, 2026
3d89b25
chore: further test fixes
sadsfae Apr 16, 2026
ecb03a2
fix: resolve export_scp hanging at low percentages due to iDRAC API c…
sadsfae Apr 16, 2026
b91a73a
fix: resort back to a simpler approach.
sadsfae Apr 16, 2026
0621a67
chore: add more SSL test coverage
sadsfae Apr 16, 2026
93b5e53
chore: try to eek out more test coverage
sadsfae Apr 16, 2026
a2419d5
chore: fix formatting in GHA lint.yml
sadsfae Apr 16, 2026
cf8a454
chore: address code review points, DRY.
sadsfae Apr 17, 2026
26223b9
chore: address code review feedback
sadsfae Apr 17, 2026
c117a23
Merge pull request #524 from sadsfae/development
grafuls Apr 17, 2026
da0ef9e
fix: sriov set nic attributes dont persist on reboot.
sadsfae Apr 17, 2026
2d662a5
feat: refactor to include three helper functions.
sadsfae Apr 17, 2026
a726de9
fix: add fallback / warn intelligence for XX710 NIC
sadsfae Apr 20, 2026
6e8fb92
chore: update testing coverage
sadsfae Apr 20, 2026
6f60320
chore: simplify tests
sadsfae Apr 20, 2026
28cca05
fix: Refactor create_job to use _extract_job_id_from_response
sadsfae Apr 21, 2026
918d99c
chore: make codecov happy
sadsfae Apr 21, 2026
ddfa57e
chore: further tweak tests
sadsfae Apr 21, 2026
7bbc07a
Merge pull request #526 from sadsfae/inv_sriov
grafuls Apr 21, 2026
66158ca
chore: black fixes and flake8
sadsfae Apr 21, 2026
eaeb70d
Merge pull request #528 from sadsfae/development
grafuls Apr 21, 2026
093c005
feat: add --wait to racreset
sadsfae Apr 21, 2026
f80b915
chore: fix tests, add doc mention
sadsfae Apr 21, 2026
f3e118a
chore: further fix test coverage
sadsfae Apr 21, 2026
b1db697
chore: add poll_helpers test
sadsfae Apr 21, 2026
e1516e6
chore: adust tests and PR feedback, increase default retries to 30.
sadsfae Apr 22, 2026
0d57420
Merge pull request #529 from sadsfae/idrac_wait
grafuls Apr 22, 2026
215d1ea
feat: add --version flag to CLI
mmahut Apr 22, 2026
1cdf5f9
Merge pull request #530 from mmahut/master
sadsfae Apr 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.12.12
python-version: 3.13.13

- name: Install Python dependencies
run: pip install black flake8
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ For the replacement of `racadm racreset`, the optional argument `--racreset` was
```bash
badfish -H mgmt-your-server.example.com --racreset
```
* You can also specify `--racreset --wait` and Badfish will poll the iDrac for it to complete and keep you updated on progress.


> [!NOTE]
> Dell specific command, for Supermicro servers there is an equivalent of `--bmc-reset`

Expand Down
2 changes: 1 addition & 1 deletion src/badfish/config.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
RETRIES = 15
RETRIES = 30
45 changes: 39 additions & 6 deletions src/badfish/helpers/http_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import json
import ssl
from typing import Any, Dict, Optional

import aiohttp
Expand All @@ -10,19 +11,39 @@

class HTTPClient:

def __init__(self, host: str, username: str, password: str, logger, retries: int = 15):
def __init__(self, host: str, username: str, password: str, logger, retries: int = 15, insecure: bool = False):
self.host = host
self.username = username
self.password = password
self.logger = logger
self.retries = retries
self.insecure = insecure
self.host_uri = f"https://{host}"
self.redfish_uri = "/redfish/v1"
self.root_uri = f"{self.host_uri}{self.redfish_uri}"
self.semaphore = asyncio.Semaphore(50)
self.token = None
self.session_id = None

def _handle_ssl_error(self, ex: Exception) -> None:
"""Handle SSL certificate verification errors by logging and raising exception.

Args:
ex: The SSL exception that was raised

Raises:
BadfishException: Always raises with detailed SSL error information
"""
self.logger.debug(f"SSL certificate verification failed: {ex}")
raise BadfishException(
f"SSL certificate verification failed for {self.host}. "
"This is likely due to a self-signed or untrusted certificate. "
"To fix this:\n"
" 1. Use a valid, trusted certificate on your BMC/iDRAC, OR\n"
" 2. Add the certificate to your system's trust store, OR\n"
" 3. For testing only, use the --insecure flag to skip verification (not recommended for production)"
)

async def error_handler(self, response: aiohttp.ClientResponse, message: Optional[str] = None) -> None:
try:
raw = await response.text("utf-8", "ignore")
Expand Down Expand Up @@ -71,18 +92,22 @@ async def get_raw(self, uri: str, _continue: bool = False, _get_token: bool = Fa
async with session.get(
uri,
headers={"X-Auth-Token": self.token} if self.token else {},
ssl=False,
ssl=False if self.insecure else True,
timeout=60,
) as _response:
await _response.read()
else:
async with session.get(
uri,
auth=aiohttp.BasicAuth(self.username, self.password),
ssl=False,
ssl=False if self.insecure else True,
timeout=60,
) as _response:
await _response.read()
except (ssl.SSLError, aiohttp.ClientConnectorCertificateError, aiohttp.ClientSSLError) as ex:
if _continue:
return
self._handle_ssl_error(ex)
except (Exception, TimeoutError) as ex:
if _continue:
return
Expand All @@ -109,12 +134,14 @@ async def post_request(
uri,
data=json.dumps(payload),
headers=headers,
ssl=False,
ssl=False if self.insecure else True,
) as _response:
if _response.status != 204:
await _response.read()
else:
return _response
except (ssl.SSLError, aiohttp.ClientConnectorCertificateError, aiohttp.ClientSSLError) as ex:
self._handle_ssl_error(ex)
except (Exception, TimeoutError):
raise BadfishException("Failed to communicate with server.")
return _response
Expand All @@ -129,11 +156,15 @@ async def patch_request(self, uri: str, payload: Dict[str, Any], headers: Dict[s
uri,
data=json.dumps(payload),
headers=headers,
ssl=False,
ssl=False if self.insecure else True,
) as _response:
raw_data = await _response.read()
self.logger.debug(raw_data)
return _response
except (ssl.SSLError, aiohttp.ClientConnectorCertificateError, aiohttp.ClientSSLError) as ex:
if _continue:
return None
self._handle_ssl_error(ex)
except Exception as ex:
if _continue:
return None
Expand All @@ -150,11 +181,13 @@ async def delete_request(self, uri: str, headers: Dict[str, str]):
async with session.delete(
uri,
headers=headers,
ssl=False,
ssl=False if self.insecure else True,
) as _response:
raw_data = await _response.read()
self.logger.debug(raw_data)
return _response
except (ssl.SSLError, aiohttp.ClientConnectorCertificateError, aiohttp.ClientSSLError) as ex:
self._handle_ssl_error(ex)
except (Exception, TimeoutError):
raise BadfishException("Failed to communicate with server.")

Expand Down
8 changes: 8 additions & 0 deletions src/badfish/helpers/parser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import argparse

from badfish import __version__
from badfish.config import RETRIES


Expand All @@ -9,6 +10,7 @@ def create_parser():
description="Tool for managing server hardware via the Redfish API.",
allow_abbrev=False,
)
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
parser.add_argument("-H", "--host", help="iDRAC host address")
parser.add_argument("-u", help="iDRAC username")
parser.add_argument("-p", help="iDRAC password")
Expand Down Expand Up @@ -70,6 +72,7 @@ def create_parser():
action="store_true",
)
parser.add_argument("--racreset", help="Flag for iDRAC reset", action="store_true")
parser.add_argument("--wait", help="Wait for iDRAC to be responsive after reset", action="store_true")
parser.add_argument("--bmc-reset", help="Flag for BMC reset", action="store_true")
parser.add_argument(
"--factory-reset",
Expand Down Expand Up @@ -237,6 +240,11 @@ def create_parser():
help="Number of retries for executing actions.",
default=RETRIES,
)
parser.add_argument(
"--insecure",
help="Disable SSL/TLS certificate verification (insecure, use only for testing)",
action="store_true",
)
parser.add_argument(
"--get-scp-targets",
help="Get allowable target values to export or import with iDRAC SCP. Choices=['Export', 'Import']",
Expand Down
Loading
Loading