Universal JSON-driven CLI health check library — Robot Framework, pytest, Python, CLI
Project description
health-check-runner
Universal JSON-driven CLI health check library. Write your test cases once in a JSON template and run them via Robot Framework, pytest, plain Python, or the CLI — no code changes required.
Table of Contents
- Install
- Quick Start
- Template Format
- Validation Types
- Variable Resolution
- Running via CLI
- Running via pytest
- Running via Robot Framework
- Running via Plain Python
- Session Configuration
- Environment Files
- Output Formats
- Credential Masking
- Reconnect on Failure
Install
# Core (SSH only)
pip install health-check-runner
# With Robot Framework support
pip install "health-check-runner[robot]"
# With pytest support
pip install "health-check-runner[pytest]"
# All optional dependencies
pip install "health-check-runner[all]"
# Development install (editable)
pip install -e .
Quick Start
from health_check_runner import Runner
with Runner(
template="templates/mydevice.json",
sessions="config/sessions.json",
env="config/envs/site.json",
) as r:
result = r.run()
print(result.summary())
print(result.to_table())
Template Format
{
"suite_key": "MYDEVICE",
"shared_rules_key": "SHARED",
"test_cases": [
{
"id": "TC1",
"name": "Check software version",
"steps": [
{
"action": "execute",
"session": "mydevice",
"command": "show version",
"store_as": "version_output"
},
{
"action": "validate",
"source": "version_output",
"type": "assert_contains",
"expected": "${expected_sw_version}"
}
]
}
]
}
Step Fields
| Field | Description |
|---|---|
action |
execute or validate |
session |
Session ID from sessions.json |
command |
Shell command to run |
store_as |
Variable name to store output in |
source |
Variable name to validate against |
type |
Validation type (see below) |
expected |
Expected value / pattern |
continue_on_failure |
true = soft failure, keep running |
timeout |
Per-step SSH timeout override (seconds) |
Validation Types
String Assertions
| Type | Description |
|---|---|
assert_contains |
Output contains expected string |
assert_not_contains |
Output does NOT contain expected string |
assert_equal |
Output (stripped) equals expected |
assert_contains_any |
Output contains at least one of expected list |
assert_line_count_equal |
Line count equals expected integer |
assert_line_count_gte |
Line count >= expected integer |
for_each_line |
Every non-empty line contains expected |
lines_containing |
Lines matching filter all contain expected |
all_lines_match_any |
Every non-empty line matches one of expected list |
section_lines_contain_any |
Lines in section between start/end contain one of expected |
Extraction
| Type | Description |
|---|---|
regex_extract |
Extract named group via pattern, store in store_as |
regex_line_match |
A line matches pattern |
regex_findall |
Find all matches, store list in store_as |
string_extract |
Extract text after after marker, store in store_as |
string_extract_between |
Extract between start and end, store in store_as |
extract_between |
Alias for string_extract_between |
extract_between_parens |
Extract content inside first (…) |
split_extract |
Split on delimiter, take index field_index, store in store_as |
fetch_between |
Extract between before / after, store in store_as |
split_field_assert |
Split on delimiter, assert field at field_index contains expected |
split_fields_numeric_gt |
Split on delimiter, assert all numeric fields > threshold |
Numeric
| Type | Description |
|---|---|
assert_numeric_lt |
Extracted number < expected |
assert_numeric_gt |
Extracted number > expected |
assert_greater_than |
Alias for assert_numeric_gt |
strip_percent_and_assert_lte |
Strip %, assert value <= expected |
Network-Specific
| Type | Description |
|---|---|
unlocked_must_be_enabled |
Unlocked cells must also be enabled |
verify_ping_ipv4 |
Parse ping output, assert 0% packet loss |
verify_ping_ipv6 |
Parse IPv6 ping output, assert 0% packet loss |
version_in_active_partition |
Version string found in active partition listing |
Date / Time
| Type | Description |
|---|---|
date_diff_assert_lte |
Parse date from output, assert diff from now <= max_days |
last_line_date_diff |
Last non-empty line is a date; assert diff <= max_days |
XML
| Type | Description |
|---|---|
xml_xpath |
Parse XML body from output; evaluate xpath; assert result contains expected |
Example:
{
"action": "validate",
"source": "curl_output",
"type": "xml_xpath",
"xpath": "//response/status",
"expected": "SUCCESS"
}
Namespaces are stripped automatically so XPath can use simple element names.
JSON
| Type | Description |
|---|---|
json_extract |
Parse JSON body from output; navigate dot-notation path; assert or store result |
Example — assert:
{
"action": "validate",
"source": "curl_output",
"type": "json_extract",
"path": "data.items.0.status",
"expected": "active"
}
Example — store:
{
"action": "validate",
"source": "curl_output",
"type": "json_extract",
"path": "data.version",
"store_as": "api_version"
}
Path syntax:
a.b.c— nested keysitems.0— array indexitems.*.status— wildcard, returns list of values
Informational
| Type | Description |
|---|---|
log_output |
Log the source variable at INFO level; always passes |
store_nf_version |
Log NF version info; always passes |
Variable Resolution
Variables in command, expected, and other string fields are resolved in this order:
- Template
varsblock — flat key/value dict at the top of the template - Environment file — merged env/sessions data
- OS environment variables —
${MY_VAR}→os.environ["MY_VAR"] - Stored step variables — set via
store_asin earlier steps - Unresolved — token left as-is (no error)
Example:
{
"vars": {
"expected_sw_version": "R22B"
}
}
Running via CLI
healthcheck-run \
--template templates/mydevice.json \
--sessions config/sessions.json \
--env config/envs/site.json \
[--shared config/shared_rules.json] \
[--tc TC1 --tc TC2] \
[--output table|json|junit] \
[--out-file results.xml] \
[--timeout 60] \
[--loglevel DEBUG]
Exit codes: 0 = all passed, 1 = failures, 2 = config/connection error.
Running via pytest
# conftest.py
from health_check_runner.integrations.pytest_plugin import make_suite_fixture
mydevice = make_suite_fixture(
template="templates/mydevice.json",
sessions="config/sessions.json",
env="config/envs/site.json",
)
# test_bbu.py
import pytest
@pytest.mark.parametrize("tc_id", ["TC1", "TC2", "TC3"])
def test_mydevice(mydevice, tc_id):
result = bbu.run_tc(tc_id)
assert result.passed, result.failure_summary()
Auto-parametrize from the template file:
from health_check_runner.integrations.pytest_plugin import parametrize_tcs
@parametrize_tcs("templates/mydevice.json")
def test_mydevice(mydevice, tc_id):
result = bbu.run_tc(tc_id)
assert result.passed, result.failure_summary()
Running via Robot Framework
# conftest_keywords.py (or directly in .robot)
from health_check_runner.integrations.robot_keywords import RobotKeywords
*** Settings ***
Library health_check_runner.integrations.robot_keywords.RobotKeywords
... template=templates/mydevice.json
... sessions=config/sessions.json
... env=config/envs/site.json
Suite Setup Open Sessions
Suite Teardown Close Sessions
*** Test Cases ***
TC1 Software Version
Run Json Test Case TC1
TC2 Cell Status
Run Json Test Case TC2
Running via Plain Python
from health_check_runner import Runner
runner = Runner(
template="templates/mydevice.json",
sessions="config/sessions.json",
env="config/envs/site.json",
shared_rules="config/shared_rules.json", # optional
timeout=60,
)
runner.open()
try:
# Run all TCs
suite = runner.run()
print(suite.summary())
# Or run a single TC
tc = runner.run_tc("TC1")
if not tc.passed:
print(tc.failure_summary())
finally:
runner.close()
Context manager form:
with Runner(template=..., sessions=..., env=...) as r:
suite = r.run()
print(suite.to_table())
Session Configuration
config/sessions.json:
{
"sessions": [
{
"id": "jump",
"type": "direct",
"host": "${JUMP_HOST}",
"username": "admin",
"password": "${JUMP_PASS}",
"port": 22
},
{
"id": "mydevice",
"type": "ssh_from",
"via": "jump",
"host": "192.168.1.10",
"username": "admin",
"password": "secret"
},
{
"id": "mydevice_cli",
"type": "amos_from",
"via": "jump",
"host": "192.168.1.10"
},
{
"id": "root",
"type": "su_from",
"via": "bbu",
"password": "rootpass"
}
]
}
Session Types
| Type | Description |
|---|---|
direct |
SSH directly to host |
ssh_from |
SSH to host from an existing session (via) |
amos_from |
Start AMOS on host from an existing session (via) |
command_from |
Run a custom command from via to open a new shell |
su_from |
Run su - on via session |
Environment Files
config/envs/site.json contains site-specific values that override template defaults:
{
"expected_sw_version": "R22B",
"bbu_ip": "192.168.1.10"
}
Variables are merged with template vars (env file wins on conflict).
Output Formats
Table (default)
Suite: MYDEVICE | Passed: 18 | Failed: 2 | Total: 20
TC_ID NAME STATUS DURATION
--------- ----------------------------- -------- --------
TC1 Software version check PASS 2.3s
TC2 Cell status check FAIL 1.1s
FAIL: expected 'active' but got 'inactive'
JSON
suite.to_json() # pretty-printed JSON string
JUnit XML
suite.to_junit_xml() # JUnit-compatible XML for CI systems
Write to file via CLI:
healthcheck-run --template ... --output junit --out-file results.xml
Credential Masking
Passwords loaded from the env/sessions files are automatically registered as secrets and replaced with *** in all log output.
To register additional secrets programmatically:
runner = Runner(...)
runner.logger.register_secret("my-api-token")
Reconnect on Failure
If an SSH connection drops mid-suite (idle timeout, network blip), the SessionManager automatically:
- Detects the dead channel (
paramiko.SSHException,EOFError,socket.error,OSError) - Tears down the session and any sub-contexts (e.g., AMOS, su) that depended on it
- Re-establishes the full connection chain
- Replays the failed command once
If the replay also fails, the error is raised as a normal step failure — the TC is marked failed and the suite continues.
No configuration needed; reconnect logic is always active.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file health_check_runner-1.0.0.tar.gz.
File metadata
- Download URL: health_check_runner-1.0.0.tar.gz
- Upload date:
- Size: 35.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d9995cc68fdf996be639ac88b3117a78ed39827ff7ae2d456a0563d2a538b400
|
|
| MD5 |
17662e409bace4f71e99d36ed1ead82f
|
|
| BLAKE2b-256 |
51188d49392f486a39ec6eab8318db121fcceb0534ac97e1aa475f3297664ff5
|
File details
Details for the file health_check_runner-1.0.0-py3-none-any.whl.
File metadata
- Download URL: health_check_runner-1.0.0-py3-none-any.whl
- Upload date:
- Size: 34.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b9ba5a37099b72bb49177a7dd3a60259aaf71f6f91c08d702b74982b97a79434
|
|
| MD5 |
0621e193e0aef6bb011f0f1d8901b249
|
|
| BLAKE2b-256 |
781740e4b15100112db0cb289f5ab742307169a8ec940083f06939d60127dd0b
|