Skip to content

refactor(skills): convert kerberator-inspect scripts from bash to Python - #23

Open
snirvan wants to merge 1 commit into
skillsfrom
snirvan/convert-inspect-to-python
Open

snirvan wants to merge 1 commit into
skillsfrom
snirvan/convert-inspect-to-python

Conversation

@snirvan

@snirvan snirvan commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

What

Convert kerberator-inspect from bash to Python. Replaces scripts/inspect.sh
with scripts/inspect.py, adds tests/test-inspect.py (bash stub kubectl, no
cluster needed), updates SKILL.md references. No behavioural change: the skill's
STATUS / CONTEXT / HINT contract, exit codes, and the 12 status values are preserved.

Pure stdlib Python 3.9+ — no new install step. kubectl i
subprocess.run(), which preserves the PATH-stub test pattern.

How it was tested

  • Scripts run on the local or team cluster
  • A new Claude chat picked up the skill
  • Unit tests pass (23/23 — same count as the bash orig
  • Byte-for-byte diff of structured lines (STATUS / CONTEXT / HINT) against the
    bash original on a live kind cluster — identical

Safety

  • No keytabs, kubeconfigs or secrets in the diff
  • Read-only: only kubectl krb list|describe and kubectlrent-context

Signed-off-by: Saksham Nirvan <snirvan2k5@gmail.com>

@gsukhotin gsukhotin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of the OP-01 inspect Python conversion at commit f490ba5. The nine inline findings focus on safe scope selection, meaningful error handling, complete discovery, subprocess reliability, and test coverage. These were checked against the changed files and relevant PR dependencies; reported reproduction results originate from the independent Codex review and have not all been rerun here. Please investigate or address the actionable concerns before approval.

i += 2
elif a in ("-h", "--help"):
die_usage()
else:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 — Reject unknown options and unexpected positional arguments. The argument parser puts every unrecognized token into pos, but the list path never validates pos. As a result, list --context=prod or a mistyped --contex prod can exit successfully while using the current cluster instead of the requested one. The same problem applies to namespace/kubeconfig spelling or value forms. Please parse supported forms explicitly, reject unknown switches and leftover arguments for each subcommand, and reject missing/flag-looking values before making any cluster call. Add regression cases for typos and --flag=value.

def ctx_name():
if ctx_flag:
return ctx_flag
r = subprocess.run(["kubectl", "config", "current-context", *kcfg],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 — Missing kubectl crashes while emitting its own installation error. The preflight at lines 163–165 calls emit("plugin-missing", ...), which calls ctx_name() here and executes the missing kubectl again. This raises FileNotFoundError instead of returning the documented structured plugin-missing result and exit code 3. Please make context reporting safe without a binary and add a test with kubectl absent from PATH.

# Map raw kubectl error text to a status. Order matters: forbidden is checked
# first so a permissions failure is never reported as "doesn't exist".
def classify_error(raw):
if re.search(r"forbidden|cannot (list|get) resource", raw, re.I):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 — Classifying the whole stderr/stdout by keyword can confuse names with error types. For example, a NotFound response for a Tenant literally named forbidden can match this regex first and be labeled RBAC forbidden; unrelated configuration errors containing not found can be labeled resource absence. Please classify structured/recognizable API error prefixes instead of arbitrary substrings in the complete message, and add cases with error words inside resource names and config errors.

elif by_uid:
emit("not-found-by-name", raw,
f"'{name}' is a UID, not a CR name; Principal(s) with UID {name}: {','.join(by_uid)} (ns/CR-name; re-run: describe principal <CR-name> -n <ns>)")
hints = [f"no {kind} matching '{name}' in any namespace"]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 — kubectl krb list -A is not a complete inventory of orphaned Principals. find_elsewhere derives global absence from the plugin’s list output, but that CLI lists Principals under discovered Tenants; a Principal whose TenantRef no longer resolves in another namespace may be absent from this listing. Consequently no Principal matching ... in any namespace can be false. Please use an authoritative cluster-wide Principal query when available, or downgrade the conclusion to unverified, and test an orphaned Principal in another namespace.

if sub == "list":
# list: ok | empty, or, for `--tenant X` without -A, a cluster-wide search
# that turns "nothing here" into not-found / not-found-in-ns / -unverified.
args = ["list", *ns_flags]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 — A selected context can use the wrong implicit namespace. This passes --context through to the plugin but leaves namespace implicit. The CLI namespace resolver can use the kubeconfig’s current-context namespace instead of the specified context’s default, so selecting prod while current is dev/default may inspect prod/default instead of prod/payments. Please resolve the selected context’s namespace and pass -n explicitly when the user did not provide one; add a test with different namespace defaults.

# Run `kubectl krb` with the same --context/--kubeconfig on every call, so
# all lookups in one run hit the same cluster. Returns (stdout+stderr, rc).
def krb(*args):
r = subprocess.run(["kubectl", "krb", *args, *common],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 — One inspection can combine evidence from different current contexts. krb() subprocesses inherit a mutable kubeconfig unless the user supplied --context, while ctx_name() also rereads it independently. If the current context changes between a failed describe, list -A fallback, and result labeling, the wrapper can combine results from different clusters. Resolve and pin the context (and namespace) once at startup, then pass them on every cluster call. Add a context-switching stub test.

# all lookups in one run hit the same cluster. Returns (stdout+stderr, rc).
def krb(*args):
r = subprocess.run(["kubectl", "krb", *args, *common],
capture_output=True, text=True)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 — Add an execution deadline for external commands. subprocess.run has no timeout here (and the same applies to the other subprocess calls), so a stalled Kubernetes API call or credential helper can hang the entire inspection indefinitely without the promised structured result. Use a shared subprocess helper with a bounded, configurable timeout; catch TimeoutExpired and return an explicit error. Add a sleeping-stub regression test.

---
name: kerberator-inspect
description: Read-only inspection of Kerberator Tenants and Principals via kubectl-krb. Use when asked to list, show, describe, check the status or health of, or find a Kerberator Tenant, Principal or UID (e.g. "what tenants exist", "is principal svc-2001 healthy", "why isn't uid 2001 minting", "does tenant demo exist"). Reports observed status and explains missing resources. Do NOT use for creating, rotating, editing, disabling or draining — those are out of scope.
description: Read-only inspection of Kerberator Tenants and Principals via kubectl-krb. Use when asked to list, show, describe or find a Kerberator Tenant, Principal or UID, or to check the status or health of a Principal or UID (e.g. "what tenants exist", "describe tenant demo", "is principal svc-2001 healthy", "is uid 2001 minting", "does tenant demo exist"). Reports observed status and explains missing resources. Do NOT use to judge whether a Tenant is healthy, ok or ready, or why not (e.g. "is tenant demo ok", "health report for tenant demo") — use kerberator-diagnose-tenant. Do NOT use to explain why a Principal or UID is failing to authenticate or mint (e.g. "why isn't uid 2001 minting", "why is svc-2001 failing") — use kerberator-diagnose-auth-failure. Do NOT use for creating, rotating, editing, disabling or draining — those are out of scope.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 — Tenant-health routing depends on a skill not yet available on the base branch. This description excludes Tenant health from kerberator-inspect and hands it exclusively to kerberator-diagnose-tenant, but that skill is in separate, currently unmerged PR #24. If #23 merges first, the agent can be sent to a nonexistent skill. Please merge the dependency first or retain an explicitly limited observed-status fallback until DG-01 is present.


_, out = _run(["list", "--context", "prod"])
second = out.splitlines()[1] if len(out.splitlines()) > 1 else ""
_assert(second == "CONTEXT: prod",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 — The context test verifies only the printed label, not which cluster was queried. The kubectl stub reports fixed listing/describe data regardless of --context or --kubeconfig, so the test passes even if those flags stop being forwarded to the actual cluster operations. Please make the stub assert invocation arguments and return context-dependent results (including discovery fallback and namespace defaults). This would catch the selection regressions above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants