...

Source file src/k8s.io/kubectl/pkg/validation/schema.go

Documentation: k8s.io/kubectl/pkg/validation

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"fmt"
    23  
    24  	ejson "github.com/exponent-io/jsonpath"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    27  	"k8s.io/cli-runtime/pkg/resource"
    28  	"k8s.io/klog/v2"
    29  )
    30  
    31  // Schema is an interface that knows how to validate an API object serialized to a byte array.
    32  type Schema interface {
    33  	ValidateBytes(data []byte) error
    34  }
    35  
    36  // NullSchema always validates bytes.
    37  type NullSchema struct{}
    38  
    39  // ValidateBytes never fails for NullSchema.
    40  func (NullSchema) ValidateBytes(data []byte) error { return nil }
    41  
    42  // NoDoubleKeySchema is a schema that disallows double keys.
    43  type NoDoubleKeySchema struct{}
    44  
    45  // ValidateBytes validates bytes.
    46  func (NoDoubleKeySchema) ValidateBytes(data []byte) error {
    47  	var list []error
    48  	if err := validateNoDuplicateKeys(data, "metadata", "labels"); err != nil {
    49  		list = append(list, err)
    50  	}
    51  	if err := validateNoDuplicateKeys(data, "metadata", "annotations"); err != nil {
    52  		list = append(list, err)
    53  	}
    54  	return utilerrors.NewAggregate(list)
    55  }
    56  
    57  func validateNoDuplicateKeys(data []byte, path ...string) error {
    58  	r := ejson.NewDecoder(bytes.NewReader(data))
    59  	// This is Go being unfriendly. The 'path ...string' comes in as a
    60  	// []string, and SeekTo takes ...interface{}, so we can't just pass
    61  	// the path straight in, we have to copy it.  *sigh*
    62  	ifacePath := []interface{}{}
    63  	for ix := range path {
    64  		ifacePath = append(ifacePath, path[ix])
    65  	}
    66  	found, err := r.SeekTo(ifacePath...)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	if !found {
    71  		return nil
    72  	}
    73  	seen := map[string]bool{}
    74  	for {
    75  		tok, err := r.Token()
    76  		if err != nil {
    77  			return err
    78  		}
    79  		switch t := tok.(type) {
    80  		case json.Delim:
    81  			if t.String() == "}" {
    82  				return nil
    83  			}
    84  		case ejson.KeyString:
    85  			if seen[string(t)] {
    86  				return fmt.Errorf("duplicate key: %s", string(t))
    87  			}
    88  			seen[string(t)] = true
    89  		}
    90  	}
    91  }
    92  
    93  // ConjunctiveSchema encapsulates a schema list.
    94  type ConjunctiveSchema []Schema
    95  
    96  // ValidateBytes validates bytes per a ConjunctiveSchema.
    97  func (c ConjunctiveSchema) ValidateBytes(data []byte) error {
    98  	var list []error
    99  	schemas := []Schema(c)
   100  	for ix := range schemas {
   101  		if err := schemas[ix].ValidateBytes(data); err != nil {
   102  			list = append(list, err)
   103  		}
   104  	}
   105  	return utilerrors.NewAggregate(list)
   106  }
   107  
   108  func NewParamVerifyingSchema(s Schema, verifier resource.Verifier, directive string) Schema {
   109  	return &paramVerifyingSchema{
   110  		schema:    s,
   111  		verifier:  verifier,
   112  		directive: directive,
   113  	}
   114  }
   115  
   116  // paramVerifyingSchema only performs validation
   117  // based on the fieldValidation query param
   118  // being unsupported by the apiserver, because
   119  // server-side validation will be performed instead
   120  // of client-side validation.
   121  type paramVerifyingSchema struct {
   122  	schema    Schema
   123  	verifier  resource.Verifier
   124  	directive string
   125  }
   126  
   127  // ValidateBytes validates bytes per a ParamVerifyingSchema
   128  func (c *paramVerifyingSchema) ValidateBytes(data []byte) error {
   129  	obj, err := parse(data)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	gvk, errs := getObjectKind(obj)
   135  	if errs != nil {
   136  		return utilerrors.NewAggregate(errs)
   137  	}
   138  
   139  	err = c.verifier.HasSupport(gvk)
   140  	if resource.IsParamUnsupportedError(err) {
   141  		switch c.directive {
   142  		case metav1.FieldValidationStrict:
   143  			return c.schema.ValidateBytes(data)
   144  		case metav1.FieldValidationWarn:
   145  			klog.Warningf("cannot perform warn validation if server-side field validation is unsupported, skipping validation")
   146  		default:
   147  			// can't be reached
   148  			klog.Warningf("unexpected field validation directive: %s, skipping validation", c.directive)
   149  		}
   150  		return nil
   151  	}
   152  	return err
   153  }
   154  

View as plain text