1package dto
2
3import "github.com/bnema/gordon/internal/domain"
4
5// PruneCandidate is one planned prune candidate with its verdict and the
6// stable reason codes behind it.
7type PruneCandidate struct {
8 Kind string `json:"kind"`
9 Ref string `json:"ref"`
10 Verdict string `json:"verdict"`
11 Reasons []string `json:"reasons"`
12}
13
14// PruneGap is one inventory that could not be read in full. A gap never
15// fails the operation; it makes the dependent candidates unknown.
16type PruneGap struct {
17 Source string `json:"source"`
18 Reason string `json:"reason"`
19 Detail string `json:"detail,omitempty"`
20}
21
22// PruneFailure is one deletion that failed without aborting the run.
23type PruneFailure struct {
24 Kind string `json:"kind"`
25 Ref string `json:"ref"`
26 Error string `json:"error"`
27}
28
29// PruneSummary is the shared prune report shape: dry-run and execution
30// return the same structure, distinguished by Applied.
31type PruneSummary struct {
32 // Applied is false for a dry run, where nothing was deleted.
33 Applied bool `json:"applied"`
34 // Eligible, Protected, and Unknown count the planned candidates.
35 Eligible int `json:"eligible"`
36 Protected int `json:"protected"`
37 Unknown int `json:"unknown"`
38 // Candidates lists every planned candidate with its verdict, so an
39 // operator can see why each resource was kept or skipped.
40 Candidates []PruneCandidate `json:"candidates"`
41 // Deleted lists exactly the identities that were removed.
42 Deleted []PruneCandidate `json:"deleted"`
43 // Failures lists deletions that failed without aborting the run.
44 Failures []PruneFailure `json:"failures,omitempty"`
45 // Gaps lists every inventory that could not be read in full.
46 Gaps []PruneGap `json:"gaps,omitempty"`
47 // ReclaimedBytes is set only when the adapter can attribute exact
48 // bytes to the deletions it performed.
49 ReclaimedBytes int64 `json:"reclaimed_bytes"`
50 ReclaimedKnown bool `json:"reclaimed_known"`
51}
52
53// CountByKind counts the eligible candidates of one resource kind.
54func (s PruneSummary) CountByKind(kind domain.PruneResourceKind) int {
55 count := 0
56 for _, candidate := range s.Candidates {
57 if candidate.Kind == string(kind) && candidate.Verdict == string(domain.PruneVerdictEligible) {
58 count++
59 }
60 }
61 return count
62}
63
64// PruneSummaryFromDomain maps the domain prune report to its wire form.
65// Dry-run and execution share the shape, distinguished by Applied.
66func PruneSummaryFromDomain(report domain.PruneReport) PruneSummary {
67 summary := PruneSummary{
68 Applied: report.Applied,
69 Eligible: report.CountByVerdict(domain.PruneVerdictEligible),
70 Protected: report.CountByVerdict(domain.PruneVerdictProtected),
71 Unknown: report.CountByVerdict(domain.PruneVerdictUnknown),
72 ReclaimedBytes: report.ReclaimedBytes,
73 ReclaimedKnown: report.ReclaimedKnown,
74 }
75 for _, candidate := range report.Candidates {
76 summary.Candidates = append(summary.Candidates, pruneCandidateFromDomain(candidate))
77 }
78 for _, candidate := range report.Deleted {
79 summary.Deleted = append(summary.Deleted, pruneCandidateFromDomain(candidate))
80 }
81 for _, failure := range report.Failures {
82 summary.Failures = append(summary.Failures, PruneFailure{
83 Kind: string(failure.Kind), Ref: failure.Ref, Error: failure.Err,
84 })
85 }
86 for _, gap := range report.Gaps {
87 summary.Gaps = append(summary.Gaps, PruneGap{
88 Source: string(gap.Source), Reason: string(gap.Reason), Detail: gap.Detail,
89 })
90 }
91 return summary
92}
93
94func pruneCandidateFromDomain(candidate domain.PruneCandidateReport) PruneCandidate {
95 reasons := make([]string, 0, len(candidate.Reasons))
96 for _, reason := range candidate.Reasons {
97 reasons = append(reasons, string(reason))
98 }
99 return PruneCandidate{
100 Kind: string(candidate.Kind), Ref: candidate.Ref, Verdict: string(candidate.Verdict), Reasons: reasons,
101 }
102}