feat: infrahub-collect troubleshooting-bundle tool (INFP-415)#140
Merged
Conversation
fatih-acar
force-pushed
the
fac/collect-tool-docs
branch
2 times, most recently
from
July 9, 2026 08:19
5b773ed to
17b532f
Compare
An error occurred while trying to automatically change base from
fac/collect-tool-docs
to
main
July 9, 2026 09:41
…ect-tool Plan, Phase 0 research decisions, data model, CLI/bundle/manifest contracts, and quickstart validation guide for the infrahub-collect troubleshooting-bundle binary. Records agent-context tech entries in AGENTS.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dual-lens critique report (verdict: proceed with updates). Applied fixes: per-collector subprocess timeouts (E1), per-container pod log handling (E2), normative masked-output list (E3), collect e2e CI wiring (E4), disk-full/bundle-size behavior (P1), human-meaningful replica log filenames (P2). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
40 tasks across setup, foundational collector framework, three user stories (k8s MVP, docker parity, include-backup), opt-in benchmark, and polish/CI wiring. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 1 (Setup) of 003-collect-tool: thin Cobra entry point at src/cmd/infrahub-collect mirroring infrahub-taskmanager, with the full published CLI surface — persistent --output-dir (INFRAHUB_OUTPUT_DIR, default ./infrahub_bundles) and create-local --log-lines / --include-backup / --include-queries / --benchmark, all viper-bound. The create RunE is a stub (validates --log-lines, returns a clear not-implemented error) until the orchestrator lands (T013). Build surface: infrahub-collect added to BINARIES in the Makefile and as a third buildGoModule package + symlinkJoin entry in flake.nix (go.mod untouched, vendorHash unchanged). Tasks: T001-T004 (specs/003-collect-tool/tasks.md). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rator Phase 2 (Foundational) of the 003-collect-tool feature (T005-T013): - masking.go: key-name secret masking (password|secret|token|key, case-insensitive substring) for env dumps, Redis config pairs and RabbitMQ Erlang-term dumps (research R5) - collect_manifest.go: BundleManifest/CollectorResult with normative JSON field names, manifestVersion 2026070200, bundle_information.json writer mirroring backup_metadata.go conventions (research R6) - collect.go: Replica struct and narrow collectBackend seam (resolved from the active EnvironmentBackend via runtime type assertion so backend primitives can land in later tasks), CollectOptions, and the CollectBundle orchestrator: staging via os.MkdirTemp under the output dir, ordered collector loop with skip preconditions and non-fatal failures (FR-009), manifest finalized last, createTarball packaging, staging cleanup on success/failure/SIGINT/SIGTERM, final log with archive path and size (research R7) - command_executor.go: context/timeout-bounded runCommandContext and runCommandPipeContext; timeouts surface as a distinguishable timeoutError whose message is exactly "timed out after <duration>" (research R2; 60s exec dumps, 5 min transfers) - infrahub-collect create now builds CollectOptions from viper and calls CollectBundle (empty run plan until T025) - table-driven tests for masking, manifest schema semantics, executor timeouts, and orchestrator outcomes with a fake collectBackend Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement the collect-side primitives on KubernetesBackend (T014-T016): ServiceReplicas enumerates one Replica per pod container with per-container restart counts via kubectl jsonpath, ReplicaLogs streams kubectl logs through the 5-minute timeout-bounded pipe, and Metrics captures kubectl top pods under the 60s exec timeout with metrics-server absence surfacing as a normal collector failure. Add the environment-agnostic service-log collector (T017) in collect_logs.go: one collector per canonical service writing per-replica files named per data-model.md, .previous.log for restarted containers, skipped entries for services that are not deployed, and partial-failure aggregation that keeps already-written logs in the bundle. Cover filename derivation, kubectl argument construction, jsonpath replica parsing, and collector skip/failure semantics with table-driven unit tests (T026). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement the US1 diagnostics collectors (T018-T025): - database-logs: CopyFrom Neo4j neo4j.log/debug.log; --include-queries copies the enumerated /logs directory including rotated query logs - message-queue-status: rabbitmqctl queues/exchanges/bindings/connections/ channels/status/environment dumps, Erlang-config masking on status and environment - cache-status: redis-cli info/clients/config (masked)/slowlog/dbsize - task-worker-state: prefect version/config (masked)/work-pool ls per replica; failed only when nothing could be collected - task-manager-state: prefect work-pool/work-queue/flow-run/automation ls with a PREFECT_API_URL localhost fallback, plus recent events via the embedded collect_prefect_events.py script (paginates the events API, which caps pages at 50) - server-info: version (also populates manifest infrahub_version), pip list, masked env, and /api/info|config|schema fetched inside the server container against INFRAHUB_INTERNAL_ADDRESS (config masked via a new JSON-aware masker) - metrics: backend Metrics primitive into bundle/metrics/metrics.txt - register the full ordered run plan in collectPlan with TODO hooks for the T033/T036 opt-in extras Supporting changes: - masking: widen the sensitive-key list from "password" to "pass" (catches requirepass, default_pass, anonymous_login_pass; FR-008 allows widening) and add maskJSON for the API configuration dump - backends: optional ExecContext (timeout-bounded exec, both backends) and ExecReplica (per-replica exec, Kubernetes) capabilities consumed by the diagnostics collectors with plain-Exec fallback - collect_logs: extract replicaBaseName, shared with the per-replica task-worker directories Smoke-tested against a live Docker Compose deployment: database, message-queue, cache and task-manager collectors succeed end to end; log/metrics/task-worker collectors correctly report the Docker backend's missing collect primitives until US2 lands them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Helm chart labels infrahub-server pods with infrahub/service=server, so none of the podSelectors match the canonical service name and ServiceReplicas reported the service as not deployed, silently skipping logs/infrahub-server in the bundle. Mirror the pod-name substring fallback getPodForService already uses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the collect_binary session fixture and run_collect helper (T027) and the Kubernetes e2e test (T028): full collect against the vcluster+Helm stack, asserting archive integrity, manifest conformance with contracts/manifest.schema.json (manual schema checks; jsonschema is not a test dependency), one log file per replica with task-worker scaled to two, a .previous.log for an induced in-place container restart, bundle layout per contracts/bundle-layout.md, and zero pod restarts or scale events caused by the run (SC-003). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement the collect-side backend seam on DockerBackend (US2, T029-T031): - ServiceReplicas enumerates compose containers per service via `docker compose ps -a --format json` (NDJSON and array forms parsed by a pure helper), one Replica per container carrying the container name; stopped containers are included so a deployed-but-stopped service surfaces as a failed collector with its logs still captured (FR-009), and one-off `compose run` containers are excluded - ReplicaLogs streams `docker logs --tail <n> <container>` through a new timeout-bounded combined-output pipe primitive: the Docker daemon demuxes container output onto stdout and stderr (Infrahub services log to stderr), so both streams merge into the log file; previous logs are rejected — Restarted is always false on Docker - Metrics captures `docker stats --no-stream` over the project's running containers, names included - ExecReplica targets one container by name so per-replica task-worker state works on scaled compose services E2E (T032, tests/e2e/test_docker_collect.py, -m docker): full parity bundle with schema-validated manifest against a compose stack scaled to two task-workers, per-replica logs and state dirs, no .previous.log on Docker, masked env dump with no plaintext secrets (FR-008), project selection with and without --project mirroring the backup tool (US2 scenario 2), --log-lines/INFRAHUB_LOG_LINES precedence in the manifest (FR-011), and the degraded case: stopped cache -> exit 0 + failed cache-status entry while its logs are still collected (FR-009/SC-005). Shared bundle assertions move to tests/helpers/bundle.py, now used by the k8s e2e as well (SC-004 parity asserted with the same code on both sides). Tick T029-T032 in specs/003-collect-tool/tasks.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement US3 of spec 003-collect-tool (T033-T035): - New include-backup collector in collect_extras.go delegates to the existing CreateBackup unmodified with non-interactive `infrahub-backup create` defaults (no --force, all Neo4j metadata, task-manager DB included, no S3 upload, no sleep, no redaction, no encryption — research R10, Principle II). The collector runs last in the plan because the inherited backup behavior may stop/restart application containers. - The produced archive stays a standalone file in the standard backup directory; its path is recorded in the manifest entry via a new optional CollectorResult field `artifact` (documented in contracts/manifest.schema.json). Backup failure records a failed manifest entry while the bundle is still produced (US3 scenario 2); an unset flag records skipped/"not requested". - Unit tests cover success/failure/skipped outcome recording through a fake backup runner seam, the artifact JSON shape, and the directory-diff helper that locates the produced archive. - Docker e2e gains an --include-backup case asserting the backup artifact next to the bundle, valid backup metadata inside it, the manifest artifact reference, exit 0, and that the backup is never embedded in the bundle archive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement T036 (FR-013, research R11): the benchmark collector runs the OpsMill benchmark image — registry.opsmill.io/opsmill/bench, ported from the Python invoke bundle collect implementation (opsmill/infrahub tasks/container_ops.py collect_benchmark) — as a transient resource the tool creates and always removes: a named `docker run --pull always --rm` container attached to the compose project network on Docker, a one-off attached `kubectl run` pod in the namespace on Kubernetes, both bounded by a dedicated 10-minute timeout and force-removed even when the run times out or is interrupted. Output is captured into bundle/benchmark/ as benchmark.log (raw) and benchmark.json (parsed measures, shape-compatible with the Python tool's report, with ANSI codes stripped). The image reference is overridable via INFRAHUB_BENCHMARK_IMAGE for private registries. An image pull or run failure is recorded in the manifest as skipped with a warning reason — never failed — via a new collectSkipError seam in the run loop, so air-gapped runs stay clean; the flag left unset records skipped/"not requested" as documented. The e2e helpers now expect the benchmark entry in every manifest. Docs: add the INFRAHUB_BENCHMARK_IMAGE row to the configuration reference (Vale and rumdl pass). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add infrahub-collect (troubleshooting-bundle collection) to the project overview, architecture core-components, key design patterns, and Docker Compose dependency notes, per constitution v1.1.0 Sync Impact Report. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The docker and k8s e2e jobs run pytest by marker (-m docker / -m k8s) over tests/e2e, so test_docker_collect.py and test_k8s_collect.py are already selected. Rename the misleading 'Build infrahub-backup binary' step to reflect that make build produces all three binaries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- install-collect: bump build-from-source prerequisite Go 1.21 -> 1.25 to match go.mod (two occurrences). - collect-troubleshooting-bundle: correct the benchmark description; the OpsMill benchmark measures host resource requirements (disk IOPS, memory, single-core CPU) rather than generating load against the instance. Image-download and air-gapped-skip wording kept intact. - Tick T037-T040 complete in tasks.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tion Address review findings that could hang a collect run or produce a misleading empty bundle: - Add a bounded contextCopier (CopyFromContext) on both backends and use it for the Neo4j log collector, so a wedged container/daemon can no longer hang on the context-free CopyFrom (FIX-1). The shared CopyFrom is left untouched for the backup tool. - Distinguish "no pods matched" (errNoPodsMatched sentinel) from a real kubectl/cluster failure in GetAllPods, and map only the sentinel to a skipped collector in ServiceReplicas; cluster outages now surface as failed instead of masquerading as "service not deployed" (FIX-2). - Recover collector run/skip panics into a recorded failure so one bad collector never aborts the run (FR-009, FIX-3). - Wrap command timeouts with %w through collector aggregation so the manifest reason is the bare "timed out after <duration>" contract string rather than a verbose composite (FIX-4). - Bound the collect path's version/internal-address lookups and the k8s pod-resolution kubectl calls the collect primitives depend on, via collect-local bounded variants; the shared getPodForService/GetAllPods stay unbounded for backup (FIX-5). - Close the ReplicaLogs reader to fix a Docker combined-pipe fd leak (FIX-6); fall back to the default Neo4j log set when --include-queries enumeration is empty (FIX-7); write the Prefect events script's failure output to a .err.txt sibling instead of a mislabeled events.json (FIX-8); cap the events pagination loop so a truthy next_page cannot spin (FIX-9). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Drop the brittle exact top-level field-count assertion in the manifest JSON-shape test; the schema allows additionalProperties, so it would break on the next legal field. Keep the required-field presence checks (FIX-T1). - Add a real-aggregation timeout test that drives the message-queue collector against a backend whose exec times out and asserts the manifest reason is exactly "timed out after 60s" (FIX-4 coverage). - Cover the FR-004 --include-queries path (default vs enumerated set) and the empty-enumeration fallback via a CopyFromContext-recording backend (FIX-T2/FIX-7). - Add a read-only guard test that runs the full plan through a backend failing the test on any Start/Stop, proving FR-010/SC-003 without a cluster (FIX-T3). - Add collector panic-recovery tests and getAllPodsWith branch tests (sentinel vs real cluster failure) for FIX-3 and FIX-2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fatih-acar
force-pushed
the
fac/collect-tool-implem-opip6
branch
from
July 9, 2026 11:21
17e0d7c to
ba7fba0
Compare
…g-tasks wait CreateBackup(force=false) runs waitForRunningTasks, an unbounded loop that returns only when no Prefect tasks are running/pending. On a busy instance (notably the Enterprise edition, with recurring background tasks and automations that never fully drain) this hangs or aborts the backup, so --include-backup produced no archive and the e2e failed at find_latest_backup. The standalone backup e2e passes on Enterprise because it uses --force. Collection is non-interactive and designed never to hang, so the troubleshooting backup now uses force=true — skipping only the consistency gate, not the backup's integrity guarantees. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uidelines Bootstrap the dev/ documentation system (adr/, knowledge/, guidelines/, README index) and extract durable decisions from the completed infrahub-collect spec: - 7 ADRs (CLI shell-out, non-fatal timeout-bounded collectors, read-only collection, key-name masking choke-point, uniform bundle+manifest, --include-backup reuse, offline-by-default/opt-in benchmark) - knowledge/infrahub-collect.md (CLI, bundle layout, manifest, backend seam) - guidelines/collectors.md (conventions for future collectors) Archive specs/003-collect-tool under specs/archive/ and mark it Extracted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fatih-acar
marked this pull request as ready for review
July 10, 2026 07:09
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
infrahub-collect, the third CLI binary in this repo, implementing the troubleshooting-bundle collection tool from Jira INFP-415 (spec003-collect-tool). It replaces the Pythoninvoke bundle collectscript and adds first-class Kubernetes support. Collection is strictly read-only — it never stops, restarts, or scales any workload — so it is safe to run against a degraded production instance.infrahub-collect createproduces a singlesupport_bundle_<timestamp>.tar.gzwith an identical internal layout on Docker Compose and Kubernetes, plus abundle_information.jsonmanifest recording an explicit outcome (success / failed / skipped) for every collector.What it collects
docker stats/kubectl top)--include-backup(delegates to the existing backup logic unmodified),--benchmark,--include-queriesDesign
src/cmd/infrahub-collect; all logic insrc/internal/app(constitution Principle I). Reuses the existing environment abstraction,CommandExecutor, tarball helper, and CNPG-primary detection.docker/kubectllike the existing backends.Testing
make fmt/vet/lint(0 issues) /testgreen.tests/e2e/test_docker_collect.py, 5 tests) — passed locally against a real compose stack (Infrahub 1.8.2): full bundle, project selection, log-lines flag/env precedence, degraded-cache (exit 0 +cache-status: failed), include-backup.tests/e2e/test_k8s_collect.py) — passed locally against a real vcluster + Helm stack (Infrahub 1.10.2): multi-replica logs, induced-restart.previous.log, manifest schema, and a before/after read-only assertion (SC-003).-m docker/-m k8smarkers).Review
A multi-lens review (correctness, error handling, test coverage, types/comments/simplify) ran across the full diff. All HIGH and MEDIUM findings were fixed and re-verified, notably:
CopyFrom, server-info/version execs, k8s pod resolution) so a wedged container can't hang the run — collect-only bounded variants added without touching the shared helpers backup depends on.failedinstead of masquerading as "service not deployed" (which would have produced a misleadingly-empty bundle).--include-queriesand the read-only guarantee.Lower-severity items (key-name masking not catching inline connection-string credentials; double-SIGINT archive window) are documented as by-design/deferred in
specs/003-collect-tool/opsmill-implement-report.md.🤖 Generated with Claude Code
Summary by cubic
Add
infrahub-collect, a read-only troubleshooting-bundle CLI for Infrahub on Docker Compose and Kubernetes, with logs, diagnostics, metrics, and opt-in backup/benchmark. Implements INFP-415, integrates full build/CI, and adds developer ADRs/guidelines with the spec archived.New Features
createwritessupport_bundle_<timestamp>.tar.gzwith a uniform layout andbundle_information.jsonmanifest across Docker and Kubernetes..previous.logon Kubernetes), diagnostics (Neo4j, RabbitMQ, Redis, Prefect, server info/config/schema/env), and container metrics; secrets masked; per-collector timeouts.--include-backup(delegates to existing backup; artifact path recorded) and--benchmark(usesINFRAHUB_BENCHMARK_IMAGE; writes benchmark.log/json; skipped if the image can’t be pulled).kubectl top.Makefileandflake.nix; CI builds all binaries and runs Docker/K8s e2e; no new Go module deps. Docs/dev: extracted ADRs/guidelines/knowledge underdev/, archived spec underspecs/archive/, updated guides (Go 1.25; benchmark measures host resources; documentedINFRAHUB_BENCHMARK_IMAGE).Bug Fixes
--include-backup: run withforce=trueto avoid unbounded running-tasks wait on busy instances; records a clear outcome.Written for commit 5fd8210. Summary will update on new commits.