Report fact records dropped from the estate table #3
Reference in New Issue
Block a user
Delete Branch "benvin/report-dropped-fact-records"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
catalog-impactexists to over-include: a host missing from the estate table is a catalog nobody validates. Records dropped by PuppetDB were reported only when the load succeeded, and--explaincounted warning messages rather than records, so one batch of N drops always read as "1".Warnhook; count records in--explainand insummary.dropped_fact_recordsWarnto stderr so forgetting the hook costs visibility, not the recordWarnmust be safe to call concurrently when aClientis sharedVerdict: issues found — one blocking correctness bug in the new reporting path itself, plus a wrong count. Not atomicity/scope problems; tests are real (all 4 fail on reverted logic);
go test -race ./...,make lint,make pre-commitall green.Blocking
The new dropped-record reporting is itself lost on
LoadNodes's error path.cmd/catalog-impact/main.go:facts.Warnpopulatesdroppedvia closure, butdroppedis never consulted here. Repro: point--puppetdbat a server that returns fact records for every host with novaluefield (all undecodable).FactsByNamecallsc.warn("skipped 2 undecodable fact(s)...")(captured intodropped) but returns an empty record slice;LoadNodesthen has zero nodes and returnserror: puppetdb returned no nodes.run()returns that error immediately — stdout, stderr,--jsonand--explainall say nothing but "puppetdb returned no nodes"; the actual reason never surfaces anywhere. Verified by running it. This is the exact silent-under-include class this PR exists to close, and it directly contradicts the new comment above it ("rides the same NodeWarnings channel ... reaches every output path") — it reaches none of them on this path. Same root cause as theinternal/impact/node.gogap the PR body flags as unfixed (LoadNodes discardsnodeWarningson its own error return) — that gap should have been in scope here, since the fix just built now inherits it and loses its own new warnings too, not just the pre-existing anonymous-record ones.The new explain-mode count is wrong.
internal/impact/render.goprints"%d fact record(s) dropped"fromlen(r.NodeWarnings), butFactsByNamebatches every skipped record in one query into a single warning string ("skipped N undecodable fact(s)..."), so N dropped records always show as "1". Repro with the PR's own fixture (web1 has 3 undecodable records): explain output readsresolution incomplete: 0 class reference(s) unresolved, 1 fact record(s) dropped, immediately followed byskipped 3 undecodable fact(s) in the estate fact table: enc_role, country, os. The summary undercounts the real drop count; none of the new tests catch it since they only assert substrings, never the numbers.Not blocking (would have done differently)
Warn's doc comment doesn't say the callback must be goroutine-safe; nothing calls it concurrently today, but a closure likedropped = append(...)would race if a future caller shared one*Clientacross goroutines (e.g. concurrentFacts()calls). Worth a one-line doc note, not a bug now.Everything else — atomicity, PR body format (6 lines/733 chars, why + bullets, no narrative) — is clean.
Reviewed
04ec9e6in isolation (worktree checkout verified at that exact SHA). Verdict: clean, no blocking bugs found.What I checked:
LoadNodes/FactsByName: HTTP error, malformed JSON body, context cancellation, and a genuinely empty query result all fail loudly with no dropped-record claim (correct, since nothing was actually dropped in those cases) — no silent-loss path found.README.md, bothmain.gos,impact.go,node.go,render.go,puppetdb.goback to the pre-fix commit while keeping the new tests →TestRunReportsDroppedFactRecordsWhenTheLoadFailsfails in all three modes (plain/json/explain) exactly as claimed, plus two packages fail to compile. Restored the fix,go test -race ./...,make lint,make pre-commitall pass.loadFailed/Summary.DroppedFactRecordssumNodeWarning.Records, not message count. Added a throwaway test mixing one undecodable-value drop with one no-certname drop in the same run — merged correctly todropped_fact_records: 2, no double count, no lost source.Warn(msg, records)updated coherently at both call sites.catalog-diffdiscarding the count is fine — its messages already embed the count inline for a single host, no aggregate is computed there.dropped_fact_recordsis additive;node_warningsstays[]stringviaNodeWarningMessages.Non-blocking observations:
--jsoncallers get nothing on stdout and must parse the stderr/error text for the tally rather than a structured field. Consistent with how every other failure in this tool behaves, but worth confirming that's acceptable for scripted consumers before this pattern gets copied elsewhere.