// Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 // Package main implements a validator function run by `kustomize fn run` package main import ( "errors" "fmt" "os" "strings" "github.com/instrumenta/kubeval/kubeval" "sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/yaml" ) func main() { rw := &kio.ByteReadWriter{ Reader: os.Stdin, Writer: os.Stdout, OmitReaderAnnotations: true, KeepReaderAnnotations: true, } p := kio.Pipeline{ Inputs: []kio.Reader{rw}, // read the inputs into a slice Filters: []kio.Filter{kubevalFilter{rw: rw}}, Outputs: []kio.Writer{rw}, // copy the inputs to the output } if err := p.Execute(); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) } } // kubevalFilter implements kio.Filter type kubevalFilter struct { rw *kio.ByteReadWriter } // define the input API schema as a struct type API struct { Spec struct { // Strict disallows additional properties not in schema if set. Strict bool `yaml:"strict"` // IgnoreMissingSchemas skips validation for resource // definitions without a schema. IgnoreMissingSchemas bool `yaml:"ignoreMissingSchemas"` // KubernetesVersion is the version of Kubernetes to validate // against (default "master"). KubernetesVersion string `yaml:"kubernetesVersion"` // SchemaLocation is the base URL used to download schemas. SchemaLocation string `yaml:"schemaLocation"` } `yaml:"spec"` } // Filter checks each resource for validity, otherwise returning an error. func (f kubevalFilter) Filter(in []*yaml.RNode) ([]*yaml.RNode, error) { api := f.parseAPI() config := kubeval.NewDefaultConfig() config.Strict = api.Spec.Strict config.IgnoreMissingSchemas = api.Spec.IgnoreMissingSchemas config.KubernetesVersion = api.Spec.KubernetesVersion config.SchemaLocation = api.Spec.SchemaLocation // validate each Resource for _, r := range in { if err := validate(r.MustString(), config); err != nil { meta, merr := r.GetMeta() if merr != nil { return nil, merr } fmt.Fprintf( os.Stderr, "Resource invalid: (Kind: %s, Name: %s)\n", meta.Kind, meta.Name, ) return nil, err } } return in, nil } func validate(r string, config *kubeval.Config) error { results, err := kubeval.Validate([]byte(r), config) if err != nil { return err } return checkResults(results) } func checkResults(results []kubeval.ValidationResult) error { if len(results) == 0 { return nil } errs := []string{} for _, r := range results { for _, e := range r.Errors { // Workaround a bug where the // "config.kubernetes.io/index" annotation value is a // number (invalid), and is still set on the Resource // regardless of OmitReaderAnnotations. // TODO: Remove this once the above issues are // resolved. if e.String() == "metadata.annotations: Invalid type. Expected: [string,null], given: integer" { continue } errs = append(errs, e.String()) } } if len(errs) > 0 { return errors.New(strings.Join(errs, "\n")) } return nil } // parseAPI parses the functionConfig into an API struct. func (f *kubevalFilter) parseAPI() API { // parse the input function config -- TODO: simplify this var api API if err := yaml.Unmarshal([]byte(f.rw.FunctionConfig.MustString()), &api); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) } return api }