Files
kustomize/functions/examples/validator-kubeval/image/main.go
2021-10-11 12:35:22 -05:00

135 lines
3.3 KiB
Go

// 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
}