-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathtypes.go
More file actions
179 lines (159 loc) · 6.28 KB
/
Copy pathtypes.go
File metadata and controls
179 lines (159 loc) · 6.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// SPDX-License-Identifier: Apache-2.0
/*
Package manifestedit is an isolated proof of concept for the manifest-inventory
"file-agnostic placement" feature. It indexes Kubernetes resources from YAML
content and edits a single document in place while preserving the formatting of
everything it did not change.
It is intentionally throw-away: the package proves whether gopkg.in/yaml.v3 node
editing is good enough before any of this is wired into the real writer. See
internal/git/manifestedit/DECISION.md.
*/
package manifestedit
// Identity is the manifest (content) identity of a Kubernetes object: the GVK
// plus name and, for namespaced objects, namespace, exactly as written in YAML.
// It is deliberately not the API-side resource identity (GVR); mapping a GVK to
// a GVR needs a live RESTMapper and is out of scope for this POC.
type Identity struct {
APIVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Namespace string `json:"namespace"`
Name string `json:"name"`
}
// Location points at one document inside one file, relative to the scan root.
type Location struct {
Path string
DocumentIndex int
}
// DiagnosticLevel classifies how serious a diagnostic is.
type DiagnosticLevel string
const (
// DiagInfo is informational and never blocks editing.
DiagInfo DiagnosticLevel = "info"
// DiagWarning marks something skipped or ignored but not fatal to the file.
DiagWarning DiagnosticLevel = "warning"
// DiagError marks content that cannot be edited safely.
DiagError DiagnosticLevel = "error"
)
// DiagReason is a structured, machine-readable cause for a diagnostic. It lets
// callers classify a document from a code rather than by parsing the
// human-readable Message — which the manifest materialization design explicitly
// forbids. The zero value is the empty reason, used for diagnostics that carry no
// structured classification (e.g. edit-time skips).
type DiagReason string
const (
// ReasonInvalidYAML marks a document that does not parse as YAML.
ReasonInvalidYAML DiagReason = "invalid-yaml"
// ReasonEmptyDocument marks an empty or comment-only document.
ReasonEmptyDocument DiagReason = "empty-document"
// ReasonNotKRM marks valid YAML that is not a Kubernetes manifest.
ReasonNotKRM DiagReason = "not-krm"
// ReasonNonEditable marks a manifest the editor refuses to edit in place
// (anchors, aliases, merge keys, unusual tags, duplicate keys).
ReasonNonEditable DiagReason = "non-editable"
// ReasonMissingSopsKey marks a .sops.yaml file lacking a sops stanza.
ReasonMissingSopsKey DiagReason = "missing-sops-key"
// ReasonDuplicateIdentity marks a document whose manifest identity duplicates
// an earlier occurrence.
ReasonDuplicateIdentity DiagReason = "duplicate-identity"
)
// Diagnostic explains an inventory or edit decision.
type Diagnostic struct {
Level DiagnosticLevel `json:"level"`
// Reason is the structured cause, set for index-time classification so callers
// never parse Message. It is empty for diagnostics with no structured code.
Reason DiagReason `json:"reason,omitempty"`
Message string `json:"message"`
Path string `json:"path"`
DocumentIndex int `json:"documentIndex"`
}
// DocumentRecord is one indexed Kubernetes document.
type DocumentRecord struct {
Identity Identity
Location Location
// Editable is false when the document uses constructs the POC refuses to edit
// (anchors, aliases, merge keys) or when it lost a duplicate-identity contest.
Editable bool
// Reason explains a non-editable record.
Reason string
// Encrypted is true for a SOPS-managed document with cleartext identity.
Encrypted bool
}
// Inventory is the mapping from resource identity to its authoritative location,
// plus the full list of records and any duplicate losers that must be deleted.
type Inventory struct {
// Records are all indexed documents in stable scan order (path, then index).
Records []DocumentRecord
// byIdentity holds the winning location for each identity.
byIdentity map[Identity]Location
// duplicates are records that lost the first-occurrence-wins contest and
// should be deleted so Git converges to a single copy.
duplicates []DocumentRecord
}
// Location returns the authoritative location for an identity, if indexed.
func (inv Inventory) Location(id Identity) (Location, bool) {
loc, ok := inv.byIdentity[id]
return loc, ok
}
// Duplicates returns the records that lost the first-occurrence-wins contest.
func (inv Inventory) Duplicates() []DocumentRecord {
return inv.duplicates
}
// Summary is a compact, bounded overview of an inventory. The vision flags that
// GitTarget status cannot enumerate thousands of manifests, so this seeds the
// "high-level stats first" direction: a status surface shows these counts and
// keeps per-resource detail for a separate read path.
type Summary struct {
Documents int
Editable int
NonEditable int
Encrypted int
Duplicates int
}
// Summary returns bounded counts over the inventory.
func (inv Inventory) Summary() Summary {
s := Summary{Duplicates: len(inv.duplicates)}
for _, r := range inv.Records {
s.Documents++
if r.Editable {
s.Editable++
} else {
s.NonEditable++
}
if r.Encrypted {
s.Encrypted++
}
}
return s
}
// CountByLevel groups diagnostics by severity, for a bounded status summary
// instead of listing every diagnostic.
func CountByLevel(diags []Diagnostic) map[DiagnosticLevel]int {
out := make(map[DiagnosticLevel]int)
for _, d := range diags {
out[d.Level]++
}
return out
}
// EditMode describes what PatchDocument did.
type EditMode string
const (
// EditNoChange means the document already matched the clean desired projection.
EditNoChange EditMode = "no-change"
// EditPatched means only the changed nodes were updated in place.
EditPatched EditMode = "patched"
// EditWholeReplace means the whole document body was re-rendered as a fallback.
EditWholeReplace EditMode = "whole-replace"
// EditSkipped means the document was left untouched because editing was unsafe.
EditSkipped EditMode = "skipped"
)
// EditResult is the outcome of editing one document.
type EditResult struct {
// Content is the full file content after the edit.
Content []byte
Mode EditMode
}
// FileContent pairs a path with its raw bytes for multi-file indexing.
type FileContent struct {
Path string
Content []byte
}