1
16
17 package generators
18
19 import (
20 "bytes"
21 "fmt"
22 "io"
23 "os"
24 "sort"
25
26 "k8s.io/kube-openapi/pkg/generators/rules"
27
28 "k8s.io/gengo/v2/generator"
29 "k8s.io/gengo/v2/types"
30 "k8s.io/klog/v2"
31 )
32
33 const apiViolationFileType = "api-violation"
34
35 type apiViolationFile struct {
36
37
38 unmangledPath string
39 }
40
41 func (a apiViolationFile) AssembleFile(f *generator.File, path string) error {
42 path = a.unmangledPath
43 klog.V(2).Infof("Assembling file %q", path)
44 if path == "-" {
45 _, err := io.Copy(os.Stdout, &f.Body)
46 return err
47 }
48
49 output, err := os.Create(path)
50 if err != nil {
51 return err
52 }
53 defer output.Close()
54 _, err = io.Copy(output, &f.Body)
55 return err
56 }
57
58 func (a apiViolationFile) VerifyFile(f *generator.File, path string) error {
59 if path == "-" {
60
61 return nil
62 }
63 path = a.unmangledPath
64
65 formatted := f.Body.Bytes()
66 existing, err := os.ReadFile(path)
67 if err != nil {
68 return fmt.Errorf("unable to read file %q for comparison: %v", path, err)
69 }
70 if bytes.Compare(formatted, existing) == 0 {
71 return nil
72 }
73
74
75
76 i := 0
77 for i < len(formatted) && i < len(existing) && formatted[i] == existing[i] {
78 i++
79 }
80 eDiff, fDiff := existing[i:], formatted[i:]
81 if len(eDiff) > 100 {
82 eDiff = eDiff[:100]
83 }
84 if len(fDiff) > 100 {
85 fDiff = fDiff[:100]
86 }
87 return fmt.Errorf("output for %q differs; first existing/expected diff: \n %q\n %q", path, string(eDiff), string(fDiff))
88 }
89
90 func newAPIViolationGen() *apiViolationGen {
91 return &apiViolationGen{
92 linter: newAPILinter(),
93 }
94 }
95
96 type apiViolationGen struct {
97 generator.GoGenerator
98
99 linter *apiLinter
100 }
101
102 func (v *apiViolationGen) FileType() string { return apiViolationFileType }
103 func (v *apiViolationGen) Filename() string {
104 return "this file is ignored by the file assembler"
105 }
106
107 func (v *apiViolationGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
108 klog.V(5).Infof("validating API rules for type %v", t)
109 if err := v.linter.validate(t); err != nil {
110 return err
111 }
112 return nil
113 }
114
115
116
117 func (v *apiViolationGen) Finalize(c *generator.Context, w io.Writer) error {
118
119
120
121
122 v.linter.report(w)
123 return nil
124 }
125
126
127
128 type apiLinter struct {
129
130 rules []APIRule
131 violations []apiViolation
132 }
133
134
135
136 func newAPILinter() *apiLinter {
137 return &apiLinter{
138 rules: []APIRule{
139 &rules.NamesMatch{},
140 &rules.OmitEmptyMatchCase{},
141 &rules.ListTypeMissing{},
142 },
143 }
144 }
145
146
147 type apiViolation struct {
148
149 rule string
150
151 packageName string
152 typeName string
153
154
155
156 field string
157 }
158
159
160
161 type apiViolations []apiViolation
162
163 func (a apiViolations) Len() int { return len(a) }
164 func (a apiViolations) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
165 func (a apiViolations) Less(i, j int) bool {
166 if a[i].rule != a[j].rule {
167 return a[i].rule < a[j].rule
168 }
169 if a[i].packageName != a[j].packageName {
170 return a[i].packageName < a[j].packageName
171 }
172 if a[i].typeName != a[j].typeName {
173 return a[i].typeName < a[j].typeName
174 }
175 return a[i].field < a[j].field
176 }
177
178
179 type APIRule interface {
180
181
182
183 Validate(t *types.Type) ([]string, error)
184
185
186 Name() string
187 }
188
189
190 func (l *apiLinter) validate(t *types.Type) error {
191 for _, r := range l.rules {
192 klog.V(5).Infof("validating API rule %v for type %v", r.Name(), t)
193 fields, err := r.Validate(t)
194 if err != nil {
195 return err
196 }
197 for _, field := range fields {
198 l.violations = append(l.violations, apiViolation{
199 rule: r.Name(),
200 packageName: t.Name.Package,
201 typeName: t.Name.Name,
202 field: field,
203 })
204 }
205 }
206 return nil
207 }
208
209
210 func (l *apiLinter) report(w io.Writer) error {
211 sort.Sort(apiViolations(l.violations))
212 for _, v := range l.violations {
213 fmt.Fprintf(w, "API rule violation: %s,%s,%s,%s\n", v.rule, v.packageName, v.typeName, v.field)
214 }
215 if len(l.violations) > 0 {
216 return fmt.Errorf("API rule violations exist")
217 }
218 return nil
219 }
220
View as plain text