1package cli
2
3import (
4 "fmt"
5 "io"
6
7 "github.com/bnema/gordon/internal/adapters/dto"
8 "github.com/bnema/gordon/internal/domain"
9 "github.com/bnema/gordon/pkg/bytesize"
10)
11
12// cliRenderPruneSummary writes the shared prune verdict summary: counts
13// per verdict, what was removed, and the reasons protected or unknown
14// candidates were skipped. Dry-run and execution use the same renderer.
15func cliRenderPruneSummary(out io.Writer, summary dto.PruneSummary) error {
16 action := "Deleted"
17 if !summary.Applied {
18 action = "Would delete"
19 }
20 // The wire summary carries the authoritative counts, so the human
21 // output can never disagree with the JSON body.
22 actionCount := len(summary.Deleted)
23 if !summary.Applied {
24 actionCount = summary.Eligible
25 }
26 if err := cliWritef(out, "%s: %d (eligible=%d protected=%d unknown=%d)\n",
27 action, actionCount, summary.Eligible, summary.Protected, summary.Unknown); err != nil {
28 return err
29 }
30 if summary.ReclaimedKnown {
31 if err := cliWritef(out, "Space reclaimed: %s\n", bytesize.Format(summary.ReclaimedBytes)); err != nil {
32 return err
33 }
34 }
35 for _, failure := range summary.Failures {
36 line := fmt.Sprintf("failed to delete %s %s: %s", failure.Kind, failure.Ref, failure.Error)
37 if err := cliWriteLine(out, cliRenderWarning(line)); err != nil {
38 return err
39 }
40 }
41 for _, gap := range summary.Gaps {
42 line := fmt.Sprintf("inventory incomplete (%s: %s); dependent candidates were left untouched", gap.Source, gap.Reason)
43 if err := cliWriteLine(out, cliRenderWarning(line)); err != nil {
44 return err
45 }
46 }
47 return cliRenderSkippedCandidates(out, summary.Candidates)
48}
49
50// cliRenderSkippedCandidates lists the not-deleted candidates with their
51// first reason, so an operator can see why each resource survived.
52func cliRenderSkippedCandidates(out io.Writer, candidates []dto.PruneCandidate) error {
53 skipped := make([]dto.PruneCandidate, 0, len(candidates))
54 for _, candidate := range candidates {
55 if candidate.Verdict == string(domain.PruneVerdictEligible) {
56 continue
57 }
58 skipped = append(skipped, candidate)
59 }
60 if len(skipped) == 0 {
61 return nil
62 }
63 if err := cliWriteLine(out, cliRenderMuted("Skipped:")); err != nil {
64 return err
65 }
66 for _, candidate := range skipped {
67 reason := ""
68 if len(candidate.Reasons) > 0 {
69 reason = candidate.Reasons[0]
70 }
71 if err := cliWritef(out, " %s %s (%s) %s\n", candidate.Kind, candidate.Ref, candidate.Verdict, reason); err != nil {
72 return err
73 }
74 }
75 return nil
76}