api_linter.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package generators
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io"
  18. "io/ioutil"
  19. "os"
  20. "sort"
  21. "k8s.io/kube-openapi/pkg/generators/rules"
  22. "k8s.io/gengo/generator"
  23. "k8s.io/gengo/types"
  24. "k8s.io/klog"
  25. )
  26. const apiViolationFileType = "api-violation"
  27. type apiViolationFile struct {
  28. // Since our file actually is unrelated to the package structure, use a
  29. // path that hasn't been mangled by the framework.
  30. unmangledPath string
  31. }
  32. func (a apiViolationFile) AssembleFile(f *generator.File, path string) error {
  33. path = a.unmangledPath
  34. klog.V(2).Infof("Assembling file %q", path)
  35. if path == "-" {
  36. _, err := io.Copy(os.Stdout, &f.Body)
  37. return err
  38. }
  39. output, err := os.Create(path)
  40. if err != nil {
  41. return err
  42. }
  43. defer output.Close()
  44. _, err = io.Copy(output, &f.Body)
  45. return err
  46. }
  47. func (a apiViolationFile) VerifyFile(f *generator.File, path string) error {
  48. if path == "-" {
  49. // Nothing to verify against.
  50. return nil
  51. }
  52. path = a.unmangledPath
  53. formatted := f.Body.Bytes()
  54. existing, err := ioutil.ReadFile(path)
  55. if err != nil {
  56. return fmt.Errorf("unable to read file %q for comparison: %v", path, err)
  57. }
  58. if bytes.Compare(formatted, existing) == 0 {
  59. return nil
  60. }
  61. // Be nice and find the first place where they differ
  62. // (Copied from gengo's default file type)
  63. i := 0
  64. for i < len(formatted) && i < len(existing) && formatted[i] == existing[i] {
  65. i++
  66. }
  67. eDiff, fDiff := existing[i:], formatted[i:]
  68. if len(eDiff) > 100 {
  69. eDiff = eDiff[:100]
  70. }
  71. if len(fDiff) > 100 {
  72. fDiff = fDiff[:100]
  73. }
  74. return fmt.Errorf("output for %q differs; first existing/expected diff: \n %q\n %q", path, string(eDiff), string(fDiff))
  75. }
  76. func newAPIViolationGen() *apiViolationGen {
  77. return &apiViolationGen{
  78. linter: newAPILinter(),
  79. }
  80. }
  81. type apiViolationGen struct {
  82. generator.DefaultGen
  83. linter *apiLinter
  84. }
  85. func (v *apiViolationGen) FileType() string { return apiViolationFileType }
  86. func (v *apiViolationGen) Filename() string {
  87. return "this file is ignored by the file assembler"
  88. }
  89. func (v *apiViolationGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
  90. klog.V(5).Infof("validating API rules for type %v", t)
  91. if err := v.linter.validate(t); err != nil {
  92. return err
  93. }
  94. return nil
  95. }
  96. // Finalize prints the API rule violations to report file (if specified from
  97. // arguments) or stdout (default)
  98. func (v *apiViolationGen) Finalize(c *generator.Context, w io.Writer) error {
  99. // NOTE: we don't return error here because we assume that the report file will
  100. // get evaluated afterwards to determine if error should be raised. For example,
  101. // you can have make rules that compare the report file with existing known
  102. // violations (whitelist) and determine no error if no change is detected.
  103. v.linter.report(w)
  104. return nil
  105. }
  106. // apiLinter is the framework hosting multiple API rules and recording API rule
  107. // violations
  108. type apiLinter struct {
  109. // API rules that implement APIRule interface and output API rule violations
  110. rules []APIRule
  111. violations []apiViolation
  112. }
  113. // newAPILinter creates an apiLinter object with API rules in package rules. Please
  114. // add APIRule here when new API rule is implemented.
  115. func newAPILinter() *apiLinter {
  116. return &apiLinter{
  117. rules: []APIRule{
  118. &rules.NamesMatch{},
  119. &rules.OmitEmptyMatchCase{},
  120. },
  121. }
  122. }
  123. // apiViolation uniquely identifies single API rule violation
  124. type apiViolation struct {
  125. // Name of rule from APIRule.Name()
  126. rule string
  127. packageName string
  128. typeName string
  129. // Optional: name of field that violates API rule. Empty fieldName implies that
  130. // the entire type violates the rule.
  131. field string
  132. }
  133. // apiViolations implements sort.Interface for []apiViolation based on the fields: rule,
  134. // packageName, typeName and field.
  135. type apiViolations []apiViolation
  136. func (a apiViolations) Len() int { return len(a) }
  137. func (a apiViolations) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  138. func (a apiViolations) Less(i, j int) bool {
  139. if a[i].rule != a[j].rule {
  140. return a[i].rule < a[j].rule
  141. }
  142. if a[i].packageName != a[j].packageName {
  143. return a[i].packageName < a[j].packageName
  144. }
  145. if a[i].typeName != a[j].typeName {
  146. return a[i].typeName < a[j].typeName
  147. }
  148. return a[i].field < a[j].field
  149. }
  150. // APIRule is the interface for validating API rule on Go types
  151. type APIRule interface {
  152. // Validate evaluates API rule on type t and returns a list of field names in
  153. // the type that violate the rule. Empty field name [""] implies the entire
  154. // type violates the rule.
  155. Validate(t *types.Type) ([]string, error)
  156. // Name returns the name of APIRule
  157. Name() string
  158. }
  159. // validate runs all API rules on type t and records any API rule violation
  160. func (l *apiLinter) validate(t *types.Type) error {
  161. for _, r := range l.rules {
  162. klog.V(5).Infof("validating API rule %v for type %v", r.Name(), t)
  163. fields, err := r.Validate(t)
  164. if err != nil {
  165. return err
  166. }
  167. for _, field := range fields {
  168. l.violations = append(l.violations, apiViolation{
  169. rule: r.Name(),
  170. packageName: t.Name.Package,
  171. typeName: t.Name.Name,
  172. field: field,
  173. })
  174. }
  175. }
  176. return nil
  177. }
  178. // report prints any API rule violation to writer w and returns error if violation exists
  179. func (l *apiLinter) report(w io.Writer) error {
  180. sort.Sort(apiViolations(l.violations))
  181. for _, v := range l.violations {
  182. fmt.Fprintf(w, "API rule violation: %s,%s,%s,%s\n", v.rule, v.packageName, v.typeName, v.field)
  183. }
  184. if len(l.violations) > 0 {
  185. return fmt.Errorf("API rule violations exist")
  186. }
  187. return nil
  188. }