Skip to content

feat(find): talonctl find — resolve any identifier to a template#12

Merged
willwebster5 merged 14 commits into
masterfrom
feature/find
Apr 22, 2026
Merged

feat(find): talonctl find — resolve any identifier to a template#12
willwebster5 merged 14 commits into
masterfrom
feature/find

Conversation

@willwebster5
Copy link
Copy Markdown
Owner

Summary

Adds talonctl find QUERY — an offline, read-only subcommand that resolves an arbitrary identifier to one or more talonctl-managed resources. Closes #10.

  • Resolution strategies (ordered, first non-empty wins): rule_id UUID → resource_id → composite ID (ngsiem: / fcs: / thirdparty: / cwpp:) → display-name substring → glob on resource_id.
  • All seven resource types covered (detection, saved_search, workflow, lookup_file, rtr_script, rtr_put_file, dashboard).
  • Three output formats: table (default, rich Table / Panel for non-IaC composite alerts), json (stable shape), path (bare template paths for xargs).
  • --include-undeployed scans on-disk templates alongside state (state wins on dedup).
  • Offline-only: never loads credentials, never calls the Falcon API.
  • Exit codes: 0 on match or recognized non-IaC prefix, 1 on zero-match, 2 on malformed args or corrupt state file.

Architecture

  • Pure logic in src/talonctl/core/resource_finder.py (dataclasses, constants, ResourceFinder with ordered method chain).
  • Thin Click wrapper in src/talonctl/commands/find.py.
  • cli.py gets a _TalonGroup subclass that suppresses the shared banner when find is invoked with --format json / --format path, so stdout pipes cleanly.

Spec + plan were brainstormed first (see docs/superpowers/specs/2026-04-21-talonctl-find-design.md and docs/superpowers/plans/2026-04-21-talonctl-find.md — both gitignored, local only).

Tests

Test plan

  • CI unit-test job passes on the PR.
  • Local smoke: against a real deployed_state.json, run `talonctl find <rule_id>` and confirm the matching template path appears.
  • Pipe smoke: `talonctl find "aws_*" --format path | head -3` prints three bare paths with no banner preamble.
  • JSON smoke: `talonctl find <rule_id> --format json | jq '.strategy_used'` returns `"rule_id"`.
  • Non-IaC: `talonctl find fcs:abc --format json | jq '.non_iac_info.prefix'` returns `"fcs"` with exit 0.
  • No-creds: run `talonctl find` in a directory without `~/.config/falcon/credentials.json` and confirm no auth error.

Follow-up items (not blocking)

  1. Click 9 migration: _TalonGroup.invoke() in src/talonctl/cli.py:26-27 reads ctx.protected_args, which emits a DeprecationWarning under Click 8 and is removed in Click 9. Tests pass today (12 warnings in the suite), but we'll need to port the banner-suppression mechanism before bumping to Click 9.
  2. JSON output helper: The spec describes console.print_json(...); the implementation uses click.echo(json.dumps(..., indent=2)) instead so stdout is guaranteed Rich-free and pipe-clean. Intentional departure from the spec wording.

🤖 Generated with Claude Code

willwebster5 and others added 14 commits April 21, 2026 19:43
Pure module with no I/O — dataclasses, RULE_ID_RE, NON_IAC_PREFIXES,
and an empty ResourceFinder.find() that always returns strategy_used=none.
Subsequent tasks will layer on each resolution strategy behind TDD.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches 32-char hex query against provider_metadata.rule_id across all
resource types. Case-insensitive, --type-filterable, tolerates missing/
malformed provider_metadata. Returns strategy_used=rule_id.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bare-key match across type buckets plus explicit "type.name" form.
--type filter narrows which buckets are probed. Same key across
multiple types returns all matches, sorted (resource_type, resource_id).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ngsiem:<rule_id> delegates to the rule_id strategy with a distinct
strategy_used="composite_id_ngsiem". fcs:/thirdparty:/cwpp: return
a populated non_iac_info block and an empty match set, marking the
alert as non-IaC-tunable. Unknown prefixes fall through so keys that
happen to contain a colon still hit later strategies.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Case-insensitive substring match on display_name across state, skipping
queries that contain * or ? (those go to glob). Deterministic
(resource_type, resource_id) sort.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fnmatch.fnmatchcase against state keys; activated only when query
contains * or ?. Honors --type filter. Deterministic sort.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
resource_id, name_substring, and glob strategies now also scan the
templates list passed to ResourceFinder. Dedup suppresses templates
whose (resource_type, name) is already in state (state wins).
Template-only hits have deployed=False, rule_id=None, deployed_at=None.
rule_id strategy is intentionally a no-op for undeployed templates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Thin Click wrapper around ResourceFinder. Loads state directly via
JSON (no orchestrator, no creds). Renders matches to a rich Table
with a one-line header; non-IaC composite matches render as a Panel.
Exit code 0 for matches or recognized non-IaC, 1 otherwise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Emit a stable JSON shape (query, strategy_used, matches, non_iac_info)
regardless of match count. Suppress the shared talonctl banner when
find is invoked with --format json/path so stdout pipes cleanly.
Uses ctx.args in the group callback (works under CliRunner).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One template_path per stdout line, no header, no color, no banner.
Entries missing template_path produce a single-line stderr warning
and are skipped (does not affect exit code).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Exit 1 with a stderr note when state file is absent (unless
--include-undeployed is set with an empty templates list, which is
still exit 1 but silent). Exit 2 on corrupt JSON. Locks in the
exit-code contract documented in the spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Registration itself landed in the Task 9 commit as part of the
_TalonGroup wiring; this test is the contract that keeps it there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the spec's acceptance list verbatim so regressions across
any strategy, format, or exit-code path fail loudly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #10.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@willwebster5 willwebster5 merged commit 3189f05 into master Apr 22, 2026
6 checks passed
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.

feat: add talonctl find — resolve any identifier (UUID, resource_id, name, composite alert ID, glob) to a template path

1 participant