원클릭으로
juju-doctor
Probe-based deployment validation with juju-doctor — scriptlet probes, rulesets, live and offline (sosreport) checks
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Probe-based deployment validation with juju-doctor — scriptlet probes, rulesets, live and offline (sosreport) checks
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Known-good Juju bundle shapes (COS Lite, 12-Factor + COS, Identity Platform, Charmed Kubeflow) — canonical app lists and relation edges
Implementing Juju actions for operational tasks in charms. WHEN: add Juju actions to a charm, implement backup / rotate-credentials / restore actions, declare actions in charmcraft.yaml, write action handlers, test actions with Scenario, run actions with juju run.
Adding and validating charm configuration options. WHEN: add charm configuration options, declare config in charmcraft.yaml, validate config values in config-changed, apply config to a Pebble layer, apply config to a machine charm, write Scenario tests for config.
End-to-end workflow for building ops-framework charms for custom applications (K8s and machine). WHEN: build a custom Juju charm, write src/charm.py with Pebble, write a machine charm with systemd, decide K8s vs machine substrate, scaffold a non-paas-charm with charmcraft init, customise the ops framework charm lifecycle.
Workflow for charming infrastructure software (databases, caches, message brokers, proxies, monitoring). WHEN: charm infrastructure software, charm a database / cache / message broker / proxy / monitoring system, implement peer relations, leader election, primary/replica failover, backup and restore actions, clustering.
Expert guidance for developing, building, testing, and publishing Juju charms using charmcraft. WHEN: edit charmcraft.yaml, run charmcraft pack, run charmcraft init, manage charm libraries, publish a charm to Charmhub, set up multi-base builds, configure relations / config options, set up storage or containers.
| name | juju-doctor |
| description | Probe-based deployment validation with juju-doctor — scriptlet probes, rulesets, live and offline (sosreport) checks |
Probe-based diagnostic tool for validating Juju deployments. juju-doctor closes a gap collect-status / update-status / Pebble checks don't: a single tool that asserts deployment-wide invariants, runs against a live model or static sosreport artefacts, and composes simple probes into solution-level rulesets.
Adapted from the
juju-doctorskill intonyandrewmeyer/charming-with-claude, CC BY 4.0 (Tony Meyer, 2025). Reformatted for cantrip's bundled-skill frontmatter; content otherwise unchanged. See Provenance at the foot of this skill for the source link.
uv pip install juju-doctor
# or
pip install juju-doctor
Development install:
git clone https://github.com/canonical/juju-doctor.git
cd juju-doctor
uv sync --extra=dev && uv pip install -e .
juju-doctor operates on three types of Juju artefacts:
| Artefact | Source command | Description |
|---|---|---|
| status | juju status --format=yaml | Unit status, application status, machine info |
| bundle | juju export-bundle | Deployed bundle configuration |
| show-unit | juju show-unit --format=yaml | Detailed unit information |
Scriptlet probes (Python) — functions named after artefact types that receive data indexed by model name:
def status(juju_statuses: dict[str, dict]):
"""Validate that all units are active/idle."""
for model_name, status_data in juju_statuses.items():
apps = status_data.get("applications", {})
for app_name, app_data in apps.items():
for unit_name, unit_data in app_data.get("units", {}).items():
ws = unit_data.get("workload-status", {})
if ws.get("current") != "active":
raise Exception(
f"{unit_name} in {model_name} is "
f"{ws.get('current')}, not active"
)
Ruleset probes (YAML) — declarative files that coordinate multiple probes:
name: my-solution-ruleset
probes:
- type: scriptlet
url: file://probes/check_active.py
- type: scriptlet
url: file://probes/check_relations.py
- type: ruleset
url: file://rulesets/nested.yaml
- type: scriptlet
url: github://canonical/my-charm//probes/validate.py
# Single model
juju-doctor check -p file://probes/check_active.py -m mymodel
# Multiple models
juju-doctor check -p file://probes/check_active.py -m model1 -m model2
# Multiple probes
juju-doctor check \
-p file://probes/check_active.py \
-p file://probes/check_relations.py \
-m mymodel
# Using a ruleset
juju-doctor check -p file://rulesets/solution.yaml -m mymodel
juju status --format=yaml > status.yaml
juju-doctor check -p file://probes/check_active.py --status status.yaml
juju export-bundle > bundle.yaml
juju-doctor check -p file://probes/check_bundle.py --bundle bundle.yaml
juju show-unit myapp/0 --format=yaml > show-unit.yaml
juju-doctor check -p file://probes/check_unit.py --show-unit show-unit.yaml
# Combine multiple artefacts
juju-doctor check \
-p file://probes/full_check.py \
--status status.yaml \
--bundle bundle.yaml \
--show-unit show-unit.yaml
juju-doctor check \
-p github://canonical/my-charm//probes/validate.py \
-m mymodel
# Default tree output
juju-doctor check -p file://probes/check.py -m mymodel
# Example output:
# Results
# ├── 🔴 probes_check_relations.py
# └── 🟢 probes_check_active.py
# Total: 🟢 1/2 🔴 1/2
# Machine-readable
juju-doctor check -p file://probes/check.py -m mymodel --format json
# Verbose
juju-doctor check -p file://probes/check.py -m mymodel -v
juju-doctor schema
juju-doctor schema --builtins
"""Probe: Validate deployment health."""
def status(juju_statuses: dict[str, dict]):
"""Check all units are active and idle."""
for model_name, data in juju_statuses.items():
for app_name, app in data.get("applications", {}).items():
for unit_name, unit in app.get("units", {}).items():
ws = unit.get("workload-status", {})
agent = unit.get("juju-status", {})
if ws.get("current") != "active":
raise Exception(f"{unit_name}: workload is {ws.get('current')}")
if agent.get("current") != "idle":
raise Exception(f"{unit_name}: agent is {agent.get('current')}")
def bundle(juju_bundles: dict[str, dict]):
"""Validate bundle configuration."""
for model_name, bundle_data in juju_bundles.items():
apps = bundle_data.get("applications", {})
if not apps:
raise Exception(f"{model_name}: no applications in bundle")
Rules:
status, bundle, show_unit.dict[str, dict] indexed by model name.Exception to signal failure (message becomes the error output).None) to signal success.def status(juju_statuses: dict[str, dict]):
for model, data in juju_statuses.items():
for app, app_data in data.get("applications", {}).items():
for unit, unit_data in app_data.get("units", {}).items():
ws = unit_data.get("workload-status", {})
if ws.get("current") != "active":
raise Exception(f"{unit}: {ws.get('current')}")
def status(juju_statuses: dict[str, dict]):
required = {"database", "ingress"}
for model, data in juju_statuses.items():
relations = set(data.get("relations", {}).keys())
for app, app_data in data.get("applications", {}).items():
for rel in app_data.get("relations", {}):
relations.add(rel)
missing = required - relations
if missing:
raise Exception(f"{model}: missing relations: {missing}")
def bundle(juju_bundles: dict[str, dict]):
for model, bundle_data in juju_bundles.items():
apps = bundle_data.get("applications", {})
for app_name, app in apps.items():
num_units = app.get("num_units", 1)
if num_units < 1:
raise Exception(f"{app_name}: has {num_units} units")
charm = app.get("charm", "")
if not charm:
raise Exception(f"{app_name}: no charm specified")
def status(juju_statuses: dict[str, dict]):
for model, data in juju_statuses.items():
for app, app_data in data.get("applications", {}).items():
app_status = app_data.get("application-status", {})
if app_status.get("current") in ("blocked", "error"):
msg = app_status.get("message", "no message")
raise Exception(f"{app}: {app_status['current']} — {msg}")
probes/
├── check_active.py # Unit health checks
├── check_relations.py # Relation validation
├── check_config.py # Configuration validation
└── rulesets/
└── full-check.yaml # Combined ruleset
.get() with defaults; not all fields are always present.juju status --format=yaml and validate against those before running against live models.github:// URLs in rulesets for reusable probes.juju status and juju export-bundle output.juju-doctor check --status status.yaml --bundle bundle.yaml.--format json is the machine-readable surface.pip show juju-doctor # or: uv pip show juju-doctor
pip install --upgrade juju-doctor
file://path/to/probe.py (relative to working directory).status, bundle, show_unit).juju status
juju models
juju-doctor check -p file://probe.py -m $(juju models --format=json | python3 -c "import sys,json; print(json.load(sys.stdin)['models'][0]['name'])")
juju-doctor check -p file://probe.py -m mymodel -v
# Capture the artefact and inspect manually
juju status --format=yaml > /tmp/status.yaml
python3 -c "import yaml; print(yaml.safe_load(open('/tmp/status.yaml')))"
# Then test the probe offline
juju-doctor check -p file://probe.py --status /tmp/status.yaml -v
juju-doctor check [OPTIONS]
--probe, -p Probe URL (file://, github://) — repeatable
--model, -m Live model name — repeatable
--status Path to juju status YAML file
--bundle Path to juju bundle YAML file
--show-unit Path to juju show-unit YAML file
--verbose, -v Verbose output
--format, -o Output format: tree (default) or json
juju-doctor schema
juju-doctor schema --builtins
juju-doctor --help
juju-doctor --version
Exception to signal failure, return normally for success.file:// URLs for local probes, github:// for remote ones.Adapted from the juju-doctor skill
in tonyandrewmeyer/charming-with-claude,
CC BY 4.0 (Tony Meyer, 2025). Content reformatted for cantrip's
bundled-skill frontmatter; otherwise unchanged.