123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- /*
- Copyright 2018 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package generators
- import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "sort"
- "k8s.io/kube-openapi/pkg/generators/rules"
- "k8s.io/gengo/generator"
- "k8s.io/gengo/types"
- "k8s.io/klog"
- )
- const apiViolationFileType = "api-violation"
- type apiViolationFile struct {
- // Since our file actually is unrelated to the package structure, use a
- // path that hasn't been mangled by the framework.
- unmangledPath string
- }
- func (a apiViolationFile) AssembleFile(f *generator.File, path string) error {
- path = a.unmangledPath
- klog.V(2).Infof("Assembling file %q", path)
- if path == "-" {
- _, err := io.Copy(os.Stdout, &f.Body)
- return err
- }
- output, err := os.Create(path)
- if err != nil {
- return err
- }
- defer output.Close()
- _, err = io.Copy(output, &f.Body)
- return err
- }
- func (a apiViolationFile) VerifyFile(f *generator.File, path string) error {
- if path == "-" {
- // Nothing to verify against.
- return nil
- }
- path = a.unmangledPath
- formatted := f.Body.Bytes()
- existing, err := ioutil.ReadFile(path)
- if err != nil {
- return fmt.Errorf("unable to read file %q for comparison: %v", path, err)
- }
- if bytes.Compare(formatted, existing) == 0 {
- return nil
- }
- // Be nice and find the first place where they differ
- // (Copied from gengo's default file type)
- i := 0
- for i < len(formatted) && i < len(existing) && formatted[i] == existing[i] {
- i++
- }
- eDiff, fDiff := existing[i:], formatted[i:]
- if len(eDiff) > 100 {
- eDiff = eDiff[:100]
- }
- if len(fDiff) > 100 {
- fDiff = fDiff[:100]
- }
- return fmt.Errorf("output for %q differs; first existing/expected diff: \n %q\n %q", path, string(eDiff), string(fDiff))
- }
- func newAPIViolationGen() *apiViolationGen {
- return &apiViolationGen{
- linter: newAPILinter(),
- }
- }
- type apiViolationGen struct {
- generator.DefaultGen
- linter *apiLinter
- }
- func (v *apiViolationGen) FileType() string { return apiViolationFileType }
- func (v *apiViolationGen) Filename() string {
- return "this file is ignored by the file assembler"
- }
- func (v *apiViolationGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
- klog.V(5).Infof("validating API rules for type %v", t)
- if err := v.linter.validate(t); err != nil {
- return err
- }
- return nil
- }
- // Finalize prints the API rule violations to report file (if specified from
- // arguments) or stdout (default)
- func (v *apiViolationGen) Finalize(c *generator.Context, w io.Writer) error {
- // NOTE: we don't return error here because we assume that the report file will
- // get evaluated afterwards to determine if error should be raised. For example,
- // you can have make rules that compare the report file with existing known
- // violations (whitelist) and determine no error if no change is detected.
- v.linter.report(w)
- return nil
- }
- // apiLinter is the framework hosting multiple API rules and recording API rule
- // violations
- type apiLinter struct {
- // API rules that implement APIRule interface and output API rule violations
- rules []APIRule
- violations []apiViolation
- }
- // newAPILinter creates an apiLinter object with API rules in package rules. Please
- // add APIRule here when new API rule is implemented.
- func newAPILinter() *apiLinter {
- return &apiLinter{
- rules: []APIRule{
- &rules.NamesMatch{},
- &rules.OmitEmptyMatchCase{},
- },
- }
- }
- // apiViolation uniquely identifies single API rule violation
- type apiViolation struct {
- // Name of rule from APIRule.Name()
- rule string
- packageName string
- typeName string
- // Optional: name of field that violates API rule. Empty fieldName implies that
- // the entire type violates the rule.
- field string
- }
- // apiViolations implements sort.Interface for []apiViolation based on the fields: rule,
- // packageName, typeName and field.
- type apiViolations []apiViolation
- func (a apiViolations) Len() int { return len(a) }
- func (a apiViolations) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
- func (a apiViolations) Less(i, j int) bool {
- if a[i].rule != a[j].rule {
- return a[i].rule < a[j].rule
- }
- if a[i].packageName != a[j].packageName {
- return a[i].packageName < a[j].packageName
- }
- if a[i].typeName != a[j].typeName {
- return a[i].typeName < a[j].typeName
- }
- return a[i].field < a[j].field
- }
- // APIRule is the interface for validating API rule on Go types
- type APIRule interface {
- // Validate evaluates API rule on type t and returns a list of field names in
- // the type that violate the rule. Empty field name [""] implies the entire
- // type violates the rule.
- Validate(t *types.Type) ([]string, error)
- // Name returns the name of APIRule
- Name() string
- }
- // validate runs all API rules on type t and records any API rule violation
- func (l *apiLinter) validate(t *types.Type) error {
- for _, r := range l.rules {
- klog.V(5).Infof("validating API rule %v for type %v", r.Name(), t)
- fields, err := r.Validate(t)
- if err != nil {
- return err
- }
- for _, field := range fields {
- l.violations = append(l.violations, apiViolation{
- rule: r.Name(),
- packageName: t.Name.Package,
- typeName: t.Name.Name,
- field: field,
- })
- }
- }
- return nil
- }
- // report prints any API rule violation to writer w and returns error if violation exists
- func (l *apiLinter) report(w io.Writer) error {
- sort.Sort(apiViolations(l.violations))
- for _, v := range l.violations {
- fmt.Fprintf(w, "API rule violation: %s,%s,%s,%s\n", v.rule, v.packageName, v.typeName, v.field)
- }
- if len(l.violations) > 0 {
- return fmt.Errorf("API rule violations exist")
- }
- return nil
- }
|