feat: kubesphere 4.0 (#6115)
* feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> * feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> --------- Signed-off-by: ci-bot <ci-bot@kubesphere.io> Co-authored-by: ks-ci-bot <ks-ci-bot@example.com> Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
committed by
GitHub
parent
b5015ec7b9
commit
447a51f08b
356
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation.go
generated
vendored
Normal file
356
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation.go
generated
vendored
Normal file
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
Copyright 2021 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 cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker"
|
||||
"github.com/google/cel-go/common/types"
|
||||
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
"k8s.io/apiserver/pkg/cel/library"
|
||||
"k8s.io/apiserver/pkg/cel/metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
// ScopedVarName is the variable name assigned to the locally scoped data element of a CEL validation
|
||||
// expression.
|
||||
ScopedVarName = "self"
|
||||
|
||||
// OldScopedVarName is the variable name assigned to the existing value of the locally scoped data element of a
|
||||
// CEL validation expression.
|
||||
OldScopedVarName = "oldSelf"
|
||||
)
|
||||
|
||||
// CompilationResult represents the cel compilation result for one rule
|
||||
type CompilationResult struct {
|
||||
Program cel.Program
|
||||
Error *apiservercel.Error
|
||||
// If true, the compiled expression contains a reference to the identifier "oldSelf".
|
||||
UsesOldSelf bool
|
||||
// Represents the worst-case cost of the compiled expression in terms of CEL's cost units, as used by cel.EstimateCost.
|
||||
MaxCost uint64
|
||||
// MaxCardinality represents the worse case number of times this validation rule could be invoked if contained under an
|
||||
// unbounded map or list in an OpenAPIv3 schema.
|
||||
MaxCardinality uint64
|
||||
// MessageExpression represents the cel Program that should be evaluated to generate an error message if the rule
|
||||
// fails to validate. If no MessageExpression was given, or if this expression failed to compile, this will be nil.
|
||||
MessageExpression cel.Program
|
||||
// MessageExpressionError represents an error encountered during compilation of MessageExpression. If no error was
|
||||
// encountered, this will be nil.
|
||||
MessageExpressionError *apiservercel.Error
|
||||
// MessageExpressionMaxCost represents the worst-case cost of the compiled MessageExpression in terms of CEL's cost units,
|
||||
// as used by cel.EstimateCost.
|
||||
MessageExpressionMaxCost uint64
|
||||
// NormalizedRuleFieldPath represents the relative fieldPath specified by user after normalization.
|
||||
NormalizedRuleFieldPath string
|
||||
}
|
||||
|
||||
// EnvLoader delegates the decision of which CEL environment to use for each expression.
|
||||
// Callers should return the appropriate CEL environment based on the guidelines from
|
||||
// environment.NewExpressions and environment.StoredExpressions.
|
||||
type EnvLoader interface {
|
||||
// RuleEnv returns the appropriate environment from the EnvSet for the given CEL rule.
|
||||
RuleEnv(envSet *environment.EnvSet, expression string) *cel.Env
|
||||
// MessageExpressionEnv returns the appropriate environment from the EnvSet for the given
|
||||
// CEL messageExpressions.
|
||||
MessageExpressionEnv(envSet *environment.EnvSet, expression string) *cel.Env
|
||||
}
|
||||
|
||||
// NewExpressionsEnvLoader creates an EnvLoader that always uses the NewExpressions environment type.
|
||||
func NewExpressionsEnvLoader() EnvLoader {
|
||||
return alwaysNewEnvLoader{loadFn: func(envSet *environment.EnvSet) *cel.Env {
|
||||
return envSet.NewExpressionsEnv()
|
||||
}}
|
||||
}
|
||||
|
||||
// StoredExpressionsEnvLoader creates an EnvLoader that always uses the StoredExpressions environment type.
|
||||
func StoredExpressionsEnvLoader() EnvLoader {
|
||||
return alwaysNewEnvLoader{loadFn: func(envSet *environment.EnvSet) *cel.Env {
|
||||
return envSet.StoredExpressionsEnv()
|
||||
}}
|
||||
}
|
||||
|
||||
type alwaysNewEnvLoader struct {
|
||||
loadFn func(envSet *environment.EnvSet) *cel.Env
|
||||
}
|
||||
|
||||
func (pe alwaysNewEnvLoader) RuleEnv(envSet *environment.EnvSet, _ string) *cel.Env {
|
||||
return pe.loadFn(envSet)
|
||||
}
|
||||
|
||||
func (pe alwaysNewEnvLoader) MessageExpressionEnv(envSet *environment.EnvSet, _ string) *cel.Env {
|
||||
return pe.loadFn(envSet)
|
||||
}
|
||||
|
||||
// Compile compiles all the XValidations rules (without recursing into the schema) and returns a slice containing a
|
||||
// CompilationResult for each ValidationRule, or an error. declType is expected to be a CEL DeclType corresponding
|
||||
// to the structural schema.
|
||||
// Each CompilationResult may contain:
|
||||
// - non-nil Program, nil Error: The program was compiled successfully
|
||||
// - nil Program, non-nil Error: Compilation resulted in an error
|
||||
// - nil Program, nil Error: The provided rule was empty so compilation was not attempted
|
||||
//
|
||||
// perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
// baseEnv is used as the base CEL environment, see common.BaseEnvironment.
|
||||
func Compile(s *schema.Structural, declType *apiservercel.DeclType, perCallLimit uint64, baseEnvSet *environment.EnvSet, envLoader EnvLoader) ([]CompilationResult, error) {
|
||||
t := time.Now()
|
||||
defer func() {
|
||||
metrics.Metrics.ObserveCompilation(time.Since(t))
|
||||
}()
|
||||
|
||||
if len(s.Extensions.XValidations) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
celRules := s.Extensions.XValidations
|
||||
|
||||
oldSelfEnvSet, optionalOldSelfEnvSet, err := prepareEnvSet(baseEnvSet, declType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
estimator := newCostEstimator(declType)
|
||||
// compResults is the return value which saves a list of compilation results in the same order as x-kubernetes-validations rules.
|
||||
compResults := make([]CompilationResult, len(celRules))
|
||||
maxCardinality := maxCardinality(declType.MinSerializedSize)
|
||||
for i, rule := range celRules {
|
||||
ruleEnvSet := oldSelfEnvSet
|
||||
if rule.OptionalOldSelf != nil && *rule.OptionalOldSelf {
|
||||
ruleEnvSet = optionalOldSelfEnvSet
|
||||
}
|
||||
compResults[i] = compileRule(s, rule, ruleEnvSet, envLoader, estimator, maxCardinality, perCallLimit)
|
||||
}
|
||||
|
||||
return compResults, nil
|
||||
}
|
||||
|
||||
func prepareEnvSet(baseEnvSet *environment.EnvSet, declType *apiservercel.DeclType) (oldSelfEnvSet *environment.EnvSet, optionalOldSelfEnvSet *environment.EnvSet, err error) {
|
||||
scopedType := declType.MaybeAssignTypeName(generateUniqueSelfTypeName())
|
||||
|
||||
oldSelfEnvSet, err = baseEnvSet.Extend(
|
||||
environment.VersionedOptions{
|
||||
// Feature epoch was actually 1.23, but we artificially set it to 1.0 because these
|
||||
// options should always be present.
|
||||
IntroducedVersion: version.MajorMinor(1, 0),
|
||||
EnvOptions: []cel.EnvOption{
|
||||
cel.Variable(ScopedVarName, scopedType.CelType()),
|
||||
},
|
||||
DeclTypes: []*apiservercel.DeclType{
|
||||
scopedType,
|
||||
},
|
||||
},
|
||||
environment.VersionedOptions{
|
||||
IntroducedVersion: version.MajorMinor(1, 24),
|
||||
EnvOptions: []cel.EnvOption{
|
||||
cel.Variable(OldScopedVarName, scopedType.CelType()),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
optionalOldSelfEnvSet, err = baseEnvSet.Extend(
|
||||
environment.VersionedOptions{
|
||||
// Feature epoch was actually 1.23, but we artificially set it to 1.0 because these
|
||||
// options should always be present.
|
||||
IntroducedVersion: version.MajorMinor(1, 0),
|
||||
EnvOptions: []cel.EnvOption{
|
||||
cel.Variable(ScopedVarName, scopedType.CelType()),
|
||||
},
|
||||
DeclTypes: []*apiservercel.DeclType{
|
||||
scopedType,
|
||||
},
|
||||
},
|
||||
environment.VersionedOptions{
|
||||
IntroducedVersion: version.MajorMinor(1, 24),
|
||||
EnvOptions: []cel.EnvOption{
|
||||
cel.Variable(OldScopedVarName, types.NewOptionalType(scopedType.CelType())),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return oldSelfEnvSet, optionalOldSelfEnvSet, nil
|
||||
}
|
||||
|
||||
func compileRule(s *schema.Structural, rule apiextensions.ValidationRule, envSet *environment.EnvSet, envLoader EnvLoader, estimator *library.CostEstimator, maxCardinality uint64, perCallLimit uint64) (compilationResult CompilationResult) {
|
||||
if len(strings.TrimSpace(rule.Rule)) == 0 {
|
||||
// include a compilation result, but leave both program and error nil per documented return semantics of this
|
||||
// function
|
||||
return
|
||||
}
|
||||
ruleEnv := envLoader.RuleEnv(envSet, rule.Rule)
|
||||
ast, issues := ruleEnv.Compile(rule.Rule)
|
||||
if issues != nil {
|
||||
compilationResult.Error = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "compilation failed: " + issues.String()}
|
||||
return
|
||||
}
|
||||
if ast.OutputType() != cel.BoolType {
|
||||
compilationResult.Error = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "cel expression must evaluate to a bool"}
|
||||
return
|
||||
}
|
||||
|
||||
checkedExpr, err := cel.AstToCheckedExpr(ast)
|
||||
if err != nil {
|
||||
// should be impossible since env.Compile returned no issues
|
||||
compilationResult.Error = &apiservercel.Error{Type: apiservercel.ErrorTypeInternal, Detail: "unexpected compilation error: " + err.Error()}
|
||||
return
|
||||
}
|
||||
for _, ref := range checkedExpr.ReferenceMap {
|
||||
if ref.Name == OldScopedVarName {
|
||||
compilationResult.UsesOldSelf = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Ideally we could configure the per expression limit at validation time and set it to the remaining overall budget, but we would either need a way to pass in a limit at evaluation time or move program creation to validation time
|
||||
prog, err := ruleEnv.Program(ast,
|
||||
cel.CostLimit(perCallLimit),
|
||||
cel.CostTracking(estimator),
|
||||
cel.InterruptCheckFrequency(celconfig.CheckFrequency),
|
||||
)
|
||||
if err != nil {
|
||||
compilationResult.Error = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "program instantiation failed: " + err.Error()}
|
||||
return
|
||||
}
|
||||
costEst, err := ruleEnv.EstimateCost(ast, estimator)
|
||||
if err != nil {
|
||||
compilationResult.Error = &apiservercel.Error{Type: apiservercel.ErrorTypeInternal, Detail: "cost estimation failed: " + err.Error()}
|
||||
return
|
||||
}
|
||||
compilationResult.MaxCost = costEst.Max
|
||||
compilationResult.MaxCardinality = maxCardinality
|
||||
compilationResult.Program = prog
|
||||
if rule.MessageExpression != "" {
|
||||
messageEnv := envLoader.MessageExpressionEnv(envSet, rule.MessageExpression)
|
||||
ast, issues := messageEnv.Compile(rule.MessageExpression)
|
||||
if issues != nil {
|
||||
compilationResult.MessageExpressionError = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "messageExpression compilation failed: " + issues.String()}
|
||||
return
|
||||
}
|
||||
if ast.OutputType() != cel.StringType {
|
||||
compilationResult.MessageExpressionError = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "messageExpression must evaluate to a string"}
|
||||
return
|
||||
}
|
||||
|
||||
_, err := cel.AstToCheckedExpr(ast)
|
||||
if err != nil {
|
||||
compilationResult.MessageExpressionError = &apiservercel.Error{Type: apiservercel.ErrorTypeInternal, Detail: "unexpected messageExpression compilation error: " + err.Error()}
|
||||
return
|
||||
}
|
||||
|
||||
msgProg, err := messageEnv.Program(ast,
|
||||
cel.CostLimit(perCallLimit),
|
||||
cel.CostTracking(estimator),
|
||||
cel.InterruptCheckFrequency(celconfig.CheckFrequency),
|
||||
)
|
||||
if err != nil {
|
||||
compilationResult.MessageExpressionError = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "messageExpression instantiation failed: " + err.Error()}
|
||||
return
|
||||
}
|
||||
costEst, err := messageEnv.EstimateCost(ast, estimator)
|
||||
if err != nil {
|
||||
compilationResult.MessageExpressionError = &apiservercel.Error{Type: apiservercel.ErrorTypeInternal, Detail: "cost estimation failed for messageExpression: " + err.Error()}
|
||||
return
|
||||
}
|
||||
compilationResult.MessageExpression = msgProg
|
||||
compilationResult.MessageExpressionMaxCost = costEst.Max
|
||||
}
|
||||
if rule.FieldPath != "" {
|
||||
validFieldPath, err := ValidFieldPath(rule.FieldPath, s)
|
||||
if err == nil {
|
||||
compilationResult.NormalizedRuleFieldPath = validFieldPath.String()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// generateUniqueSelfTypeName creates a placeholder type name to use in a CEL programs for cases
|
||||
// where we do not wish to expose a stable type name to CEL validator rule authors. For this to effectively prevent
|
||||
// developers from depending on the generated name (i.e. using it in CEL programs), it must be changed each time a
|
||||
// CRD is created or updated.
|
||||
func generateUniqueSelfTypeName() string {
|
||||
return fmt.Sprintf("selfType%d", time.Now().Nanosecond())
|
||||
}
|
||||
|
||||
func newCostEstimator(root *apiservercel.DeclType) *library.CostEstimator {
|
||||
return &library.CostEstimator{SizeEstimator: &sizeEstimator{root: root}}
|
||||
}
|
||||
|
||||
type sizeEstimator struct {
|
||||
root *apiservercel.DeclType
|
||||
}
|
||||
|
||||
func (c *sizeEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstimate {
|
||||
if len(element.Path()) == 0 {
|
||||
// Path() can return an empty list, early exit if it does since we can't
|
||||
// provide size estimates when that happens
|
||||
return nil
|
||||
}
|
||||
currentNode := c.root
|
||||
// cut off "self" from path, since we always start there
|
||||
for _, name := range element.Path()[1:] {
|
||||
switch name {
|
||||
case "@items", "@values":
|
||||
if currentNode.ElemType == nil {
|
||||
return nil
|
||||
}
|
||||
currentNode = currentNode.ElemType
|
||||
case "@keys":
|
||||
if currentNode.KeyType == nil {
|
||||
return nil
|
||||
}
|
||||
currentNode = currentNode.KeyType
|
||||
default:
|
||||
field, ok := currentNode.Fields[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if field.Type == nil {
|
||||
return nil
|
||||
}
|
||||
currentNode = field.Type
|
||||
}
|
||||
}
|
||||
return &checker.SizeEstimate{Min: 0, Max: uint64(currentNode.MaxElements)}
|
||||
}
|
||||
|
||||
func (c *sizeEstimator) EstimateCallCost(function, overloadID string, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
|
||||
return nil
|
||||
}
|
||||
|
||||
// maxCardinality returns the maximum number of times data conforming to the minimum size given could possibly exist in
|
||||
// an object serialized to JSON. For cases where a schema is contained under map or array schemas of unbounded
|
||||
// size, this can be used as an estimate as the worst case number of times data matching the schema could be repeated.
|
||||
// Note that this only assumes a single comma between data elements, so if the schema is contained under only maps,
|
||||
// this estimates a higher cardinality that would be possible. DeclType.MinSerializedSize is meant to be passed to
|
||||
// this function.
|
||||
func maxCardinality(minSize int64) uint64 {
|
||||
sz := minSize + 1 // assume at least one comma between elements
|
||||
return uint64(celconfig.MaxRequestSizeBytes / sz)
|
||||
}
|
||||
30
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/maplist.go
generated
vendored
Normal file
30
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/maplist.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright 2022 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 cel
|
||||
|
||||
import (
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
|
||||
"k8s.io/apiserver/pkg/cel/common"
|
||||
)
|
||||
|
||||
// makeMapList returns a queryable interface over the provided x-kubernetes-list-type=map
|
||||
// keyedItems. If the provided schema is _not_ an array with x-kubernetes-list-type=map, returns an
|
||||
// empty mapList.
|
||||
func makeMapList(sts *schema.Structural, items []interface{}) (rv common.MapList) {
|
||||
return common.MakeMapList(&model.Structural{Structural: sts}, items)
|
||||
}
|
||||
321
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/adaptor.go
generated
vendored
Normal file
321
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/adaptor.go
generated
vendored
Normal file
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
Copyright 2023 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 model
|
||||
|
||||
import (
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apiserver/pkg/cel/common"
|
||||
)
|
||||
|
||||
var _ common.Schema = (*Structural)(nil)
|
||||
var _ common.SchemaOrBool = (*StructuralOrBool)(nil)
|
||||
|
||||
type Structural struct {
|
||||
Structural *schema.Structural
|
||||
}
|
||||
|
||||
type StructuralOrBool struct {
|
||||
StructuralOrBool *schema.StructuralOrBool
|
||||
}
|
||||
|
||||
func (sb *StructuralOrBool) Schema() common.Schema {
|
||||
if sb.StructuralOrBool.Structural == nil {
|
||||
return nil
|
||||
}
|
||||
return &Structural{Structural: sb.StructuralOrBool.Structural}
|
||||
}
|
||||
|
||||
func (sb *StructuralOrBool) Allows() bool {
|
||||
return sb.StructuralOrBool.Bool
|
||||
}
|
||||
|
||||
func (s *Structural) Type() string {
|
||||
return s.Structural.Type
|
||||
}
|
||||
|
||||
func (s *Structural) Format() string {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return ""
|
||||
}
|
||||
return s.Structural.ValueValidation.Format
|
||||
}
|
||||
|
||||
func (s *Structural) Pattern() string {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return ""
|
||||
}
|
||||
return s.Structural.ValueValidation.Pattern
|
||||
}
|
||||
|
||||
func (s *Structural) Items() common.Schema {
|
||||
return &Structural{Structural: s.Structural.Items}
|
||||
}
|
||||
|
||||
func (s *Structural) Properties() map[string]common.Schema {
|
||||
if s.Structural.Properties == nil {
|
||||
return nil
|
||||
}
|
||||
res := make(map[string]common.Schema, len(s.Structural.Properties))
|
||||
for n, prop := range s.Structural.Properties {
|
||||
s := prop
|
||||
res[n] = &Structural{Structural: &s}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *Structural) AdditionalProperties() common.SchemaOrBool {
|
||||
if s.Structural.AdditionalProperties == nil {
|
||||
return nil
|
||||
}
|
||||
return &StructuralOrBool{StructuralOrBool: s.Structural.AdditionalProperties}
|
||||
}
|
||||
|
||||
func (s *Structural) Default() any {
|
||||
return s.Structural.Default.Object
|
||||
}
|
||||
|
||||
func (s *Structural) Minimum() *float64 {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return nil
|
||||
}
|
||||
return s.Structural.ValueValidation.Minimum
|
||||
}
|
||||
|
||||
func (s *Structural) IsExclusiveMinimum() bool {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return false
|
||||
}
|
||||
return s.Structural.ValueValidation.ExclusiveMinimum
|
||||
}
|
||||
|
||||
func (s *Structural) Maximum() *float64 {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return nil
|
||||
}
|
||||
return s.Structural.ValueValidation.Maximum
|
||||
}
|
||||
|
||||
func (s *Structural) IsExclusiveMaximum() bool {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return false
|
||||
}
|
||||
return s.Structural.ValueValidation.ExclusiveMaximum
|
||||
}
|
||||
|
||||
func (s *Structural) MultipleOf() *float64 {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return nil
|
||||
}
|
||||
return s.Structural.ValueValidation.MultipleOf
|
||||
}
|
||||
|
||||
func (s *Structural) MinItems() *int64 {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return nil
|
||||
}
|
||||
return s.Structural.ValueValidation.MinItems
|
||||
}
|
||||
|
||||
func (s *Structural) MaxItems() *int64 {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return nil
|
||||
}
|
||||
return s.Structural.ValueValidation.MaxItems
|
||||
}
|
||||
|
||||
func (s *Structural) MinLength() *int64 {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return nil
|
||||
}
|
||||
return s.Structural.ValueValidation.MinLength
|
||||
}
|
||||
|
||||
func (s *Structural) MaxLength() *int64 {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return nil
|
||||
}
|
||||
return s.Structural.ValueValidation.MaxLength
|
||||
}
|
||||
|
||||
func (s *Structural) MinProperties() *int64 {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return nil
|
||||
}
|
||||
return s.Structural.ValueValidation.MinProperties
|
||||
}
|
||||
|
||||
func (s *Structural) MaxProperties() *int64 {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return nil
|
||||
}
|
||||
return s.Structural.ValueValidation.MaxProperties
|
||||
}
|
||||
|
||||
func (s *Structural) Required() []string {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return nil
|
||||
}
|
||||
return s.Structural.ValueValidation.Required
|
||||
}
|
||||
|
||||
func (s *Structural) UniqueItems() bool {
|
||||
// This field is forbidden in structural schema.
|
||||
// but you can just you x-kubernetes-list-type:set to get around it :)
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Structural) Enum() []any {
|
||||
if s.Structural.ValueValidation == nil {
|
||||
return nil
|
||||
}
|
||||
ret := make([]any, 0, len(s.Structural.ValueValidation.Enum))
|
||||
for _, e := range s.Structural.ValueValidation.Enum {
|
||||
ret = append(ret, e.Object)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *Structural) Nullable() bool {
|
||||
return s.Structural.Nullable
|
||||
}
|
||||
|
||||
func (s *Structural) IsXIntOrString() bool {
|
||||
return s.Structural.XIntOrString
|
||||
}
|
||||
|
||||
func (s *Structural) IsXEmbeddedResource() bool {
|
||||
return s.Structural.XEmbeddedResource
|
||||
}
|
||||
|
||||
func (s *Structural) IsXPreserveUnknownFields() bool {
|
||||
return s.Structural.XPreserveUnknownFields
|
||||
}
|
||||
|
||||
func (s *Structural) XListType() string {
|
||||
if s.Structural.XListType == nil {
|
||||
return ""
|
||||
}
|
||||
return *s.Structural.XListType
|
||||
}
|
||||
|
||||
func (s *Structural) XMapType() string {
|
||||
if s.Structural.XMapType == nil {
|
||||
return ""
|
||||
}
|
||||
return *s.Structural.XMapType
|
||||
}
|
||||
|
||||
func (s *Structural) XListMapKeys() []string {
|
||||
return s.Structural.XListMapKeys
|
||||
}
|
||||
|
||||
func (s *Structural) AllOf() []common.Schema {
|
||||
var res []common.Schema
|
||||
for _, subSchema := range s.Structural.ValueValidation.AllOf {
|
||||
subSchema := subSchema
|
||||
res = append(res, nestedValueValidationToStructural(&subSchema))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *Structural) AnyOf() []common.Schema {
|
||||
var res []common.Schema
|
||||
for _, subSchema := range s.Structural.ValueValidation.AnyOf {
|
||||
subSchema := subSchema
|
||||
res = append(res, nestedValueValidationToStructural(&subSchema))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *Structural) OneOf() []common.Schema {
|
||||
var res []common.Schema
|
||||
for _, subSchema := range s.Structural.ValueValidation.OneOf {
|
||||
subSchema := subSchema
|
||||
res = append(res, nestedValueValidationToStructural(&subSchema))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *Structural) Not() common.Schema {
|
||||
if s.Structural.ValueValidation.Not == nil {
|
||||
return nil
|
||||
}
|
||||
return nestedValueValidationToStructural(s.Structural.ValueValidation.Not)
|
||||
}
|
||||
|
||||
// nestedValueValidationToStructural converts a nested value validation to
|
||||
// an equivalent structural schema instance.
|
||||
//
|
||||
// This lets us avoid needing a separate adaptor for the nested value
|
||||
// validations, and doesn't cost too much since since we are usually exploring the
|
||||
// entire schema anyway.
|
||||
func nestedValueValidationToStructural(nvv *schema.NestedValueValidation) *Structural {
|
||||
var newItems *schema.Structural
|
||||
if nvv.Items != nil {
|
||||
newItems = nestedValueValidationToStructural(nvv.Items).Structural
|
||||
}
|
||||
|
||||
var newProperties map[string]schema.Structural
|
||||
for k, v := range nvv.Properties {
|
||||
if newProperties == nil {
|
||||
newProperties = make(map[string]schema.Structural)
|
||||
}
|
||||
|
||||
v := v
|
||||
newProperties[k] = *nestedValueValidationToStructural(&v).Structural
|
||||
}
|
||||
|
||||
return &Structural{
|
||||
Structural: &schema.Structural{
|
||||
Items: newItems,
|
||||
Properties: newProperties,
|
||||
ValueValidation: &nvv.ValueValidation,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type StructuralValidationRule struct {
|
||||
rule, message, messageExpression, fieldPath string
|
||||
}
|
||||
|
||||
func (s *StructuralValidationRule) Rule() string {
|
||||
return s.rule
|
||||
}
|
||||
func (s *StructuralValidationRule) Message() string {
|
||||
return s.message
|
||||
}
|
||||
func (s *StructuralValidationRule) FieldPath() string {
|
||||
return s.fieldPath
|
||||
}
|
||||
func (s *StructuralValidationRule) MessageExpression() string {
|
||||
return s.messageExpression
|
||||
}
|
||||
|
||||
func (s *Structural) XValidations() []common.ValidationRule {
|
||||
if len(s.Structural.XValidations) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := make([]common.ValidationRule, len(s.Structural.XValidations))
|
||||
for i, v := range s.Structural.XValidations {
|
||||
result[i] = &StructuralValidationRule{rule: v.Rule, message: v.Message, messageExpression: v.MessageExpression, fieldPath: v.FieldPath}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *Structural) WithTypeAndObjectMeta() common.Schema {
|
||||
return &Structural{Structural: WithTypeAndObjectMeta(s.Structural)}
|
||||
}
|
||||
73
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/schemas.go
generated
vendored
Normal file
73
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/schemas.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright 2022 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 model
|
||||
|
||||
import (
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/common"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
)
|
||||
|
||||
// SchemaDeclType converts the structural schema to a CEL declaration, or returns nil if the
|
||||
// structural schema should not be exposed in CEL expressions.
|
||||
// Set isResourceRoot to true for the root of a custom resource or embedded resource.
|
||||
//
|
||||
// Schemas with XPreserveUnknownFields not exposed unless they are objects. Array and "maps" schemas
|
||||
// are not exposed if their items or additionalProperties schemas are not exposed. Object Properties are not exposed
|
||||
// if their schema is not exposed.
|
||||
//
|
||||
// The CEL declaration for objects with XPreserveUnknownFields does not expose unknown fields.
|
||||
func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *apiservercel.DeclType {
|
||||
return common.SchemaDeclType(&Structural{Structural: s}, isResourceRoot)
|
||||
}
|
||||
|
||||
// WithTypeAndObjectMeta ensures the kind, apiVersion and
|
||||
// metadata.name and metadata.generateName properties are specified, making a shallow copy of the provided schema if needed.
|
||||
func WithTypeAndObjectMeta(s *schema.Structural) *schema.Structural {
|
||||
if s.Properties != nil &&
|
||||
s.Properties["kind"].Type == "string" &&
|
||||
s.Properties["apiVersion"].Type == "string" &&
|
||||
s.Properties["metadata"].Type == "object" &&
|
||||
s.Properties["metadata"].Properties != nil &&
|
||||
s.Properties["metadata"].Properties["name"].Type == "string" &&
|
||||
s.Properties["metadata"].Properties["generateName"].Type == "string" {
|
||||
return s
|
||||
}
|
||||
result := &schema.Structural{
|
||||
Generic: s.Generic,
|
||||
Extensions: s.Extensions,
|
||||
ValueValidation: s.ValueValidation,
|
||||
}
|
||||
props := make(map[string]schema.Structural, len(s.Properties))
|
||||
for k, prop := range s.Properties {
|
||||
props[k] = prop
|
||||
}
|
||||
stringType := schema.Structural{Generic: schema.Generic{Type: "string"}}
|
||||
props["kind"] = stringType
|
||||
props["apiVersion"] = stringType
|
||||
props["metadata"] = schema.Structural{
|
||||
Generic: schema.Generic{Type: "object"},
|
||||
Properties: map[string]schema.Structural{
|
||||
"name": stringType,
|
||||
"generateName": stringType,
|
||||
},
|
||||
}
|
||||
result.Properties = props
|
||||
|
||||
return result
|
||||
}
|
||||
789
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation.go
generated
vendored
Normal file
789
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation.go
generated
vendored
Normal file
@@ -0,0 +1,789 @@
|
||||
/*
|
||||
Copyright 2021 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 cel
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
|
||||
"k8s.io/apiextensions-apiserver/pkg/features"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/common"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
"k8s.io/apiserver/pkg/cel/metrics"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
)
|
||||
|
||||
// Validator parallels the structure of schema.Structural and includes the compiled CEL programs
|
||||
// for the x-kubernetes-validations of each schema node.
|
||||
type Validator struct {
|
||||
Items *Validator
|
||||
Properties map[string]Validator
|
||||
|
||||
AdditionalProperties *Validator
|
||||
|
||||
compiledRules []CompilationResult
|
||||
|
||||
// Program compilation is pre-checked at CRD creation/update time, so we don't expect compilation to fail
|
||||
// they are recompiled and added to this type, and it does, it is an internal bug.
|
||||
// But if somehow we get any compilation errors, we track them and then surface them as validation errors.
|
||||
compilationErr error
|
||||
|
||||
// isResourceRoot is true if this validator node is for the root of a resource. Either the root of the
|
||||
// custom resource being validated, or the root of an XEmbeddedResource object.
|
||||
isResourceRoot bool
|
||||
|
||||
// celActivationFactory produces a Activations, which resolve identifiers
|
||||
// (e.g. self and oldSelf) to CEL values. One activation must be produced
|
||||
// for each of the cases when oldSelf is optional and non-optional.
|
||||
celActivationFactory func(sts *schema.Structural, obj, oldObj interface{}) (activation interpreter.Activation, optionalOldSelfActivation interpreter.Activation)
|
||||
}
|
||||
|
||||
// NewValidator returns compiles all the CEL programs defined in x-kubernetes-validations extensions
|
||||
// of the Structural schema and returns a custom resource validator that contains nested
|
||||
// validators for all items, properties and additionalProperties that transitively contain validator rules.
|
||||
// Returns nil if there are no validator rules in the Structural schema. May return a validator containing only errors.
|
||||
// Adding perCallLimit as input arg for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input
|
||||
func NewValidator(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) *Validator {
|
||||
if !hasXValidations(s) {
|
||||
return nil
|
||||
}
|
||||
return validator(s, isResourceRoot, model.SchemaDeclType(s, isResourceRoot), perCallLimit)
|
||||
}
|
||||
|
||||
// validator creates a Validator for all x-kubernetes-validations at the level of the provided schema and lower and
|
||||
// returns the Validator if any x-kubernetes-validations exist in the schema, or nil if no x-kubernetes-validations
|
||||
// exist. declType is expected to be a CEL DeclType corresponding to the structural schema.
|
||||
// perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
func validator(s *schema.Structural, isResourceRoot bool, declType *cel.DeclType, perCallLimit uint64) *Validator {
|
||||
compiledRules, err := Compile(s, declType, perCallLimit, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()), StoredExpressionsEnvLoader())
|
||||
var itemsValidator, additionalPropertiesValidator *Validator
|
||||
var propertiesValidators map[string]Validator
|
||||
if s.Items != nil {
|
||||
itemsValidator = validator(s.Items, s.Items.XEmbeddedResource, declType.ElemType, perCallLimit)
|
||||
}
|
||||
if len(s.Properties) > 0 {
|
||||
propertiesValidators = make(map[string]Validator, len(s.Properties))
|
||||
for k, p := range s.Properties {
|
||||
prop := p
|
||||
var fieldType *cel.DeclType
|
||||
if escapedPropName, ok := cel.Escape(k); ok {
|
||||
if f, ok := declType.Fields[escapedPropName]; ok {
|
||||
fieldType = f.Type
|
||||
} else {
|
||||
// fields with unknown types are omitted from CEL validation entirely
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// field may be absent from declType if the property name is unescapable, in which case we should convert
|
||||
// the field value type to a DeclType.
|
||||
fieldType = model.SchemaDeclType(&prop, prop.XEmbeddedResource)
|
||||
if fieldType == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if p := validator(&prop, prop.XEmbeddedResource, fieldType, perCallLimit); p != nil {
|
||||
propertiesValidators[k] = *p
|
||||
}
|
||||
}
|
||||
}
|
||||
if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
|
||||
additionalPropertiesValidator = validator(s.AdditionalProperties.Structural, s.AdditionalProperties.Structural.XEmbeddedResource, declType.ElemType, perCallLimit)
|
||||
}
|
||||
if len(compiledRules) > 0 || err != nil || itemsValidator != nil || additionalPropertiesValidator != nil || len(propertiesValidators) > 0 {
|
||||
activationFactory := validationActivationWithoutOldSelf
|
||||
for _, rule := range compiledRules {
|
||||
if rule.UsesOldSelf {
|
||||
activationFactory = validationActivationWithOldSelf
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &Validator{
|
||||
compiledRules: compiledRules,
|
||||
compilationErr: err,
|
||||
isResourceRoot: isResourceRoot,
|
||||
Items: itemsValidator,
|
||||
AdditionalProperties: additionalPropertiesValidator,
|
||||
Properties: propertiesValidators,
|
||||
celActivationFactory: activationFactory,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type options struct {
|
||||
ratchetingOptions
|
||||
}
|
||||
|
||||
type Option func(*options)
|
||||
|
||||
func WithRatcheting(correlation *common.CorrelatedObject) Option {
|
||||
return func(o *options) {
|
||||
o.currentCorrelation = correlation
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates all x-kubernetes-validations rules in Validator against obj and returns any errors.
|
||||
// If the validation rules exceed the costBudget, subsequent evaluations will be skipped, the list of errs returned will not be empty, and a negative remainingBudget will be returned.
|
||||
// Most callers can ignore the returned remainingBudget value unless another validate call is going to be made
|
||||
// context is passed for supporting context cancellation during cel validation
|
||||
func (s *Validator) Validate(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj interface{}, costBudget int64, opts ...Option) (errs field.ErrorList, remainingBudget int64) {
|
||||
opt := options{}
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return s.validate(ctx, fldPath, sts, obj, oldObj, opt.ratchetingOptions, costBudget)
|
||||
}
|
||||
|
||||
// ratchetingOptions stores the current correlation object and the nearest
|
||||
// parent which was correlatable. The parent is stored so that we can check at
|
||||
// the point an error is thrown whether it should be ratcheted using simple
|
||||
// logic
|
||||
// Key and Index should be used as normally to traverse to the next node.
|
||||
type ratchetingOptions struct {
|
||||
// Current correlation object. If nil, then this node is from an uncorrelatable
|
||||
// part of the schema
|
||||
currentCorrelation *common.CorrelatedObject
|
||||
|
||||
// If currentCorrelation is nil, this is the nearest parent to this node
|
||||
// which was correlatable. If the parent is deepequal to its old value,
|
||||
// then errors thrown by this node are ratcheted
|
||||
nearestParentCorrelation *common.CorrelatedObject
|
||||
}
|
||||
|
||||
// shouldRatchetError returns true if the errors raised by the current node
|
||||
// should be ratcheted.
|
||||
//
|
||||
// Errors for the current node should be ratcheted if one of the following is true:
|
||||
// 1. The current node is correlatable, and it is equal to its old value
|
||||
// 2. The current node has a correlatable ancestor, and the ancestor is equal
|
||||
// to its old value.
|
||||
func (r ratchetingOptions) shouldRatchetError() bool {
|
||||
if r.currentCorrelation != nil {
|
||||
return r.currentCorrelation.CachedDeepEqual()
|
||||
}
|
||||
|
||||
return r.nearestParentCorrelation.CachedDeepEqual()
|
||||
}
|
||||
|
||||
// Finds the next node following the field in the tree and returns options using
|
||||
// that node. If none could be found, then retains a reference to the last
|
||||
// correlatable ancestor for ratcheting purposes
|
||||
func (r ratchetingOptions) key(field string) ratchetingOptions {
|
||||
if r.currentCorrelation == nil {
|
||||
return r
|
||||
}
|
||||
|
||||
return ratchetingOptions{currentCorrelation: r.currentCorrelation.Key(field), nearestParentCorrelation: r.currentCorrelation}
|
||||
}
|
||||
|
||||
// Finds the next node following the index in the tree and returns options using
|
||||
// that node. If none could be found, then retains a reference to the last
|
||||
// correlatable ancestor for ratcheting purposes
|
||||
func (r ratchetingOptions) index(idx int) ratchetingOptions {
|
||||
if r.currentCorrelation == nil {
|
||||
return r
|
||||
}
|
||||
|
||||
return ratchetingOptions{currentCorrelation: r.currentCorrelation.Index(idx), nearestParentCorrelation: r.currentCorrelation}
|
||||
}
|
||||
|
||||
func (s *Validator) validate(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj interface{}, correlation ratchetingOptions, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
|
||||
t := time.Now()
|
||||
defer func() {
|
||||
metrics.Metrics.ObserveEvaluation(time.Since(t))
|
||||
}()
|
||||
remainingBudget = costBudget
|
||||
if s == nil || obj == nil {
|
||||
return nil, remainingBudget
|
||||
}
|
||||
|
||||
errs, remainingBudget = s.validateExpressions(ctx, fldPath, sts, obj, oldObj, correlation, remainingBudget)
|
||||
|
||||
if remainingBudget < 0 {
|
||||
return errs, remainingBudget
|
||||
}
|
||||
|
||||
switch obj := obj.(type) {
|
||||
case []interface{}:
|
||||
oldArray, _ := oldObj.([]interface{})
|
||||
var arrayErrs field.ErrorList
|
||||
arrayErrs, remainingBudget = s.validateArray(ctx, fldPath, sts, obj, oldArray, correlation, remainingBudget)
|
||||
errs = append(errs, arrayErrs...)
|
||||
return errs, remainingBudget
|
||||
case map[string]interface{}:
|
||||
oldMap, _ := oldObj.(map[string]interface{})
|
||||
var mapErrs field.ErrorList
|
||||
mapErrs, remainingBudget = s.validateMap(ctx, fldPath, sts, obj, oldMap, correlation, remainingBudget)
|
||||
errs = append(errs, mapErrs...)
|
||||
return errs, remainingBudget
|
||||
}
|
||||
|
||||
return errs, remainingBudget
|
||||
}
|
||||
|
||||
func (s *Validator) validateExpressions(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj interface{}, correlation ratchetingOptions, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
|
||||
// guard against oldObj being a non-nil interface with a nil value
|
||||
if oldObj != nil {
|
||||
v := reflect.ValueOf(oldObj)
|
||||
switch v.Kind() {
|
||||
case reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice:
|
||||
if v.IsNil() {
|
||||
oldObj = nil // +k8s:verify-mutation:reason=clone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remainingBudget = costBudget
|
||||
if obj == nil {
|
||||
// We only validate non-null values. Rules that need to check for the state of a nullable value or the presence of an optional
|
||||
// field must do so from the surrounding schema. E.g. if an array has nullable string items, a rule on the array
|
||||
// schema can check if items are null, but a rule on the nullable string schema only validates the non-null strings.
|
||||
return nil, remainingBudget
|
||||
}
|
||||
if s.compilationErr != nil {
|
||||
errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("rule compiler initialization error: %v", s.compilationErr)))
|
||||
return errs, remainingBudget
|
||||
}
|
||||
if len(s.compiledRules) == 0 {
|
||||
return nil, remainingBudget // nothing to do
|
||||
}
|
||||
if remainingBudget <= 0 {
|
||||
errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run")))
|
||||
return errs, -1
|
||||
}
|
||||
if s.isResourceRoot {
|
||||
sts = model.WithTypeAndObjectMeta(sts)
|
||||
}
|
||||
activation, optionalOldSelfActivation := s.celActivationFactory(sts, obj, oldObj)
|
||||
for i, compiled := range s.compiledRules {
|
||||
rule := sts.XValidations[i]
|
||||
if compiled.Error != nil {
|
||||
errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("rule compile error: %v", compiled.Error)))
|
||||
continue
|
||||
}
|
||||
if compiled.Program == nil {
|
||||
// rule is empty
|
||||
continue
|
||||
}
|
||||
|
||||
// If ratcheting is enabled, allow rule with oldSelf to evaluate
|
||||
// when `optionalOldSelf` is set to true
|
||||
optionalOldSelfRule := ptr.Deref(rule.OptionalOldSelf, false)
|
||||
if compiled.UsesOldSelf && oldObj == nil {
|
||||
// transition rules are evaluated only if there is a comparable existing value
|
||||
// But if the rule uses optional oldSelf and gate is enabled we allow
|
||||
// the rule to be evaluated
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.CRDValidationRatcheting) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !optionalOldSelfRule {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
ruleActivation := activation
|
||||
if optionalOldSelfRule {
|
||||
ruleActivation = optionalOldSelfActivation
|
||||
}
|
||||
|
||||
evalResult, evalDetails, err := compiled.Program.ContextEval(ctx, ruleActivation)
|
||||
if evalDetails == nil {
|
||||
errs = append(errs, field.InternalError(fldPath, fmt.Errorf("runtime cost could not be calculated for validation rule: %v, no further validation rules will be run", ruleErrorString(rule))))
|
||||
return errs, -1
|
||||
} else {
|
||||
rtCost := evalDetails.ActualCost()
|
||||
if rtCost == nil {
|
||||
errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("runtime cost could not be calculated for validation rule: %v, no further validation rules will be run", ruleErrorString(rule))))
|
||||
return errs, -1
|
||||
} else {
|
||||
if *rtCost > math.MaxInt64 || int64(*rtCost) > remainingBudget {
|
||||
errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run")))
|
||||
return errs, -1
|
||||
}
|
||||
remainingBudget -= int64(*rtCost)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
// see types.Err for list of well defined error types
|
||||
if strings.HasPrefix(err.Error(), "no such overload") {
|
||||
// Most overload errors are caught by the compiler, which provides details on where exactly in the rule
|
||||
// error was found. Here, an overload error has occurred at runtime no details are provided, so we
|
||||
// append a more descriptive error message. This error can only occur when static type checking has
|
||||
// been bypassed. int-or-string is typed as dynamic and so bypasses compiler type checking.
|
||||
errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("'%v': call arguments did not match a supported operator, function or macro signature for rule: %v", err, ruleErrorString(rule))))
|
||||
} else if strings.HasPrefix(err.Error(), "operation cancelled: actual cost limit exceeded") {
|
||||
errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("'%v': no further validation rules will be run due to call cost exceeds limit for rule: %v", err, ruleErrorString(rule))))
|
||||
return errs, -1
|
||||
} else {
|
||||
// no such key: {key}, index out of bounds: {index}, integer overflow, division by zero, ...
|
||||
errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("%v evaluating rule: %v", err, ruleErrorString(rule))))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if evalResult != types.True {
|
||||
if len(compiled.NormalizedRuleFieldPath) > 0 {
|
||||
fldPath = fldPath.Child(compiled.NormalizedRuleFieldPath)
|
||||
}
|
||||
|
||||
addErr := func(e *field.Error) {
|
||||
if !compiled.UsesOldSelf && correlation.shouldRatchetError() {
|
||||
warning.AddWarning(ctx, "", e.Error())
|
||||
} else {
|
||||
errs = append(errs, e)
|
||||
}
|
||||
}
|
||||
|
||||
if compiled.MessageExpression != nil {
|
||||
messageExpression, newRemainingBudget, msgErr := evalMessageExpression(ctx, compiled.MessageExpression, rule.MessageExpression, activation, remainingBudget)
|
||||
if msgErr != nil {
|
||||
if msgErr.Type == cel.ErrorTypeInternal {
|
||||
addErr(field.InternalError(fldPath, msgErr))
|
||||
return errs, -1
|
||||
} else if msgErr.Type == cel.ErrorTypeInvalid {
|
||||
addErr(field.Invalid(fldPath, sts.Type, msgErr.Error()))
|
||||
return errs, -1
|
||||
} else {
|
||||
klog.V(2).ErrorS(msgErr, "messageExpression evaluation failed")
|
||||
addErr(fieldErrorForReason(fldPath, sts.Type, ruleMessageOrDefault(rule), rule.Reason))
|
||||
remainingBudget = newRemainingBudget
|
||||
}
|
||||
} else {
|
||||
addErr(fieldErrorForReason(fldPath, sts.Type, messageExpression, rule.Reason))
|
||||
remainingBudget = newRemainingBudget
|
||||
}
|
||||
} else {
|
||||
addErr(fieldErrorForReason(fldPath, sts.Type, ruleMessageOrDefault(rule), rule.Reason))
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs, remainingBudget
|
||||
}
|
||||
|
||||
var unescapeMatcher = regexp.MustCompile(`\\.`)
|
||||
|
||||
func unescapeSingleQuote(s string) (string, error) {
|
||||
var err error
|
||||
unescaped := unescapeMatcher.ReplaceAllStringFunc(s, func(matchStr string) string {
|
||||
directive := matchStr[1]
|
||||
switch directive {
|
||||
case 'a':
|
||||
return "\a"
|
||||
case 'b':
|
||||
return "\b"
|
||||
case 'f':
|
||||
return "\f"
|
||||
case 'n':
|
||||
return "\n"
|
||||
case 'r':
|
||||
return "\r"
|
||||
case 't':
|
||||
return "\t"
|
||||
case 'v':
|
||||
return "\v"
|
||||
case '\'':
|
||||
return "'"
|
||||
case '\\':
|
||||
return "\\"
|
||||
default:
|
||||
err = fmt.Errorf("invalid escape char %s", matchStr)
|
||||
return ""
|
||||
}
|
||||
})
|
||||
return unescaped, err
|
||||
}
|
||||
|
||||
// ValidFieldPath validates that jsonPath is a valid JSON Path containing only field and map accessors
|
||||
// that are valid for the given schema, and returns a field.Path representation of the validated jsonPath or an error.
|
||||
func ValidFieldPath(jsonPath string, schema *schema.Structural) (validFieldPath *field.Path, err error) {
|
||||
appendToPath := func(name string, isNamed bool) error {
|
||||
if !isNamed {
|
||||
validFieldPath = validFieldPath.Key(name)
|
||||
schema = schema.AdditionalProperties.Structural
|
||||
} else {
|
||||
validFieldPath = validFieldPath.Child(name)
|
||||
val, ok := schema.Properties[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("does not refer to a valid field")
|
||||
}
|
||||
schema = &val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
validFieldPath = nil
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(jsonPath))
|
||||
|
||||
// configure the scanner to split the string into tokens.
|
||||
// The three delimiters ('.', '[', ']') will be returned as single char tokens.
|
||||
// All other text between delimiters is returned as string tokens.
|
||||
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if len(data) > 0 {
|
||||
for i := 0; i < len(data); i++ {
|
||||
// If in a single quoted string, look for the end of string
|
||||
// ignoring delimiters.
|
||||
if data[0] == '\'' {
|
||||
if i > 0 && data[i] == '\'' && data[i-1] != '\\' {
|
||||
// Return quoted string
|
||||
return i + 1, data[:i+1], nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
switch data[i] {
|
||||
case '.', '[', ']': // delimiters
|
||||
if i == 0 {
|
||||
// Return the delimiter.
|
||||
return 1, data[:1], nil
|
||||
} else {
|
||||
// Return identifier leading up to the delimiter.
|
||||
// The next call to split will return the delimiter.
|
||||
return i, data[:i], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if atEOF {
|
||||
// Return the string.
|
||||
return len(data), data, nil
|
||||
}
|
||||
}
|
||||
return 0, nil, nil
|
||||
})
|
||||
|
||||
var tok string
|
||||
var isNamed bool
|
||||
for scanner.Scan() {
|
||||
tok = scanner.Text()
|
||||
switch tok {
|
||||
case "[":
|
||||
if !scanner.Scan() {
|
||||
return nil, fmt.Errorf("unexpected end of JSON path")
|
||||
}
|
||||
tok = scanner.Text()
|
||||
if len(tok) < 2 || tok[0] != '\'' || tok[len(tok)-1] != '\'' {
|
||||
return nil, fmt.Errorf("expected single quoted string but got %s", tok)
|
||||
}
|
||||
unescaped, err := unescapeSingleQuote(tok[1 : len(tok)-1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid string literal: %v", err)
|
||||
}
|
||||
|
||||
if schema.Properties != nil {
|
||||
isNamed = true
|
||||
} else if schema.AdditionalProperties != nil {
|
||||
isNamed = false
|
||||
} else {
|
||||
return nil, fmt.Errorf("does not refer to a valid field")
|
||||
}
|
||||
if err := appendToPath(unescaped, isNamed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !scanner.Scan() {
|
||||
return nil, fmt.Errorf("unexpected end of JSON path")
|
||||
}
|
||||
tok = scanner.Text()
|
||||
if tok != "]" {
|
||||
return nil, fmt.Errorf("expected ] but got %s", tok)
|
||||
}
|
||||
case ".":
|
||||
if !scanner.Scan() {
|
||||
return nil, fmt.Errorf("unexpected end of JSON path")
|
||||
}
|
||||
tok = scanner.Text()
|
||||
if schema.Properties != nil {
|
||||
isNamed = true
|
||||
} else if schema.AdditionalProperties != nil {
|
||||
isNamed = false
|
||||
} else {
|
||||
return nil, fmt.Errorf("does not refer to a valid field")
|
||||
}
|
||||
if err := appendToPath(tok, isNamed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("expected [ or . but got: %s", tok)
|
||||
}
|
||||
}
|
||||
return validFieldPath, nil
|
||||
}
|
||||
|
||||
func fieldErrorForReason(fldPath *field.Path, value interface{}, detail string, reason *apiextensions.FieldValueErrorReason) *field.Error {
|
||||
if reason == nil {
|
||||
return field.Invalid(fldPath, value, detail)
|
||||
}
|
||||
switch *reason {
|
||||
case apiextensions.FieldValueForbidden:
|
||||
return field.Forbidden(fldPath, detail)
|
||||
case apiextensions.FieldValueRequired:
|
||||
return field.Required(fldPath, detail)
|
||||
case apiextensions.FieldValueDuplicate:
|
||||
return field.Duplicate(fldPath, value)
|
||||
default:
|
||||
return field.Invalid(fldPath, value, detail)
|
||||
}
|
||||
}
|
||||
|
||||
// evalMessageExpression evaluates the given message expression and returns the evaluated string form and the remaining budget, or an error if one
|
||||
// occurred during evaluation.
|
||||
func evalMessageExpression(ctx context.Context, expr celgo.Program, exprSrc string, activation interpreter.Activation, remainingBudget int64) (string, int64, *cel.Error) {
|
||||
evalResult, evalDetails, err := expr.ContextEval(ctx, activation)
|
||||
if evalDetails == nil {
|
||||
return "", -1, &cel.Error{
|
||||
Type: cel.ErrorTypeInternal,
|
||||
Detail: fmt.Sprintf("runtime cost could not be calculated for messageExpression: %q", exprSrc),
|
||||
}
|
||||
}
|
||||
rtCost := evalDetails.ActualCost()
|
||||
if rtCost == nil {
|
||||
return "", -1, &cel.Error{
|
||||
Type: cel.ErrorTypeInternal,
|
||||
Detail: fmt.Sprintf("runtime cost could not be calculated for messageExpression: %q", exprSrc),
|
||||
}
|
||||
} else if *rtCost > math.MaxInt64 || int64(*rtCost) > remainingBudget {
|
||||
return "", -1, &cel.Error{
|
||||
Type: cel.ErrorTypeInvalid,
|
||||
Detail: "messageExpression evaluation failed due to running out of cost budget, no further validation rules will be run",
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if strings.HasPrefix(err.Error(), "operation cancelled: actual cost limit exceeded") {
|
||||
return "", -1, &cel.Error{
|
||||
Type: cel.ErrorTypeInvalid,
|
||||
Detail: fmt.Sprintf("no further validation rules will be run due to call cost exceeds limit for messageExpression: %q", exprSrc),
|
||||
}
|
||||
}
|
||||
return "", remainingBudget - int64(*rtCost), &cel.Error{
|
||||
Detail: fmt.Sprintf("messageExpression evaluation failed due to: %v", err.Error()),
|
||||
}
|
||||
}
|
||||
messageStr, ok := evalResult.Value().(string)
|
||||
if !ok {
|
||||
return "", remainingBudget - int64(*rtCost), &cel.Error{
|
||||
Detail: "messageExpression failed to convert to string",
|
||||
}
|
||||
}
|
||||
trimmedMsgStr := strings.TrimSpace(messageStr)
|
||||
if len(trimmedMsgStr) > celconfig.MaxEvaluatedMessageExpressionSizeBytes {
|
||||
return "", remainingBudget - int64(*rtCost), &cel.Error{
|
||||
Detail: fmt.Sprintf("messageExpression beyond allowable length of %d", celconfig.MaxEvaluatedMessageExpressionSizeBytes),
|
||||
}
|
||||
} else if hasNewlines(trimmedMsgStr) {
|
||||
return "", remainingBudget - int64(*rtCost), &cel.Error{
|
||||
Detail: "messageExpression should not contain line breaks",
|
||||
}
|
||||
} else if len(trimmedMsgStr) == 0 {
|
||||
return "", remainingBudget - int64(*rtCost), &cel.Error{
|
||||
Detail: "messageExpression should evaluate to a non-empty string",
|
||||
}
|
||||
}
|
||||
return trimmedMsgStr, remainingBudget - int64(*rtCost), nil
|
||||
}
|
||||
|
||||
var newlineMatcher = regexp.MustCompile(`[\n]+`)
|
||||
|
||||
func hasNewlines(s string) bool {
|
||||
return newlineMatcher.MatchString(s)
|
||||
}
|
||||
|
||||
func ruleMessageOrDefault(rule apiextensions.ValidationRule) string {
|
||||
if len(rule.Message) == 0 {
|
||||
return fmt.Sprintf("failed rule: %s", ruleErrorString(rule))
|
||||
} else {
|
||||
return strings.TrimSpace(rule.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func ruleErrorString(rule apiextensions.ValidationRule) string {
|
||||
if len(rule.Message) > 0 {
|
||||
return strings.TrimSpace(rule.Message)
|
||||
}
|
||||
return strings.TrimSpace(rule.Rule)
|
||||
}
|
||||
|
||||
type validationActivation struct {
|
||||
self, oldSelf ref.Val
|
||||
hasOldSelf bool
|
||||
}
|
||||
|
||||
func validationActivationWithOldSelf(sts *schema.Structural, obj, oldObj interface{}) (activation interpreter.Activation, optionalOldSelfActivation interpreter.Activation) {
|
||||
va := &validationActivation{
|
||||
self: UnstructuredToVal(obj, sts),
|
||||
}
|
||||
optionalVA := &validationActivation{
|
||||
self: va.self,
|
||||
hasOldSelf: true, // this means the oldSelf variable is defined for CEL to reference, not that it has a value
|
||||
oldSelf: types.OptionalNone,
|
||||
}
|
||||
|
||||
if oldObj != nil {
|
||||
va.oldSelf = UnstructuredToVal(oldObj, sts) // +k8s:verify-mutation:reason=clone
|
||||
va.hasOldSelf = true
|
||||
|
||||
optionalVA.oldSelf = types.OptionalOf(va.oldSelf) // +k8s:verify-mutation:reason=clone
|
||||
}
|
||||
|
||||
return va, optionalVA
|
||||
}
|
||||
|
||||
func validationActivationWithoutOldSelf(sts *schema.Structural, obj, _ interface{}) (interpreter.Activation, interpreter.Activation) {
|
||||
res := &validationActivation{
|
||||
self: UnstructuredToVal(obj, sts),
|
||||
}
|
||||
return res, res
|
||||
}
|
||||
|
||||
func (a *validationActivation) ResolveName(name string) (interface{}, bool) {
|
||||
switch name {
|
||||
case ScopedVarName:
|
||||
return a.self, true
|
||||
case OldScopedVarName:
|
||||
return a.oldSelf, a.hasOldSelf
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func (a *validationActivation) Parent() interpreter.Activation {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Validator) validateMap(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj map[string]interface{}, correlation ratchetingOptions, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
|
||||
remainingBudget = costBudget
|
||||
if remainingBudget < 0 {
|
||||
return errs, remainingBudget
|
||||
}
|
||||
if s == nil || obj == nil {
|
||||
return nil, remainingBudget
|
||||
}
|
||||
|
||||
correlatable := MapIsCorrelatable(sts.XMapType)
|
||||
|
||||
if s.AdditionalProperties != nil && sts.AdditionalProperties != nil && sts.AdditionalProperties.Structural != nil {
|
||||
for k, v := range obj {
|
||||
var oldV interface{}
|
||||
if correlatable {
|
||||
oldV = oldObj[k] // +k8s:verify-mutation:reason=clone
|
||||
}
|
||||
|
||||
var err field.ErrorList
|
||||
err, remainingBudget = s.AdditionalProperties.validate(ctx, fldPath.Key(k), sts.AdditionalProperties.Structural, v, oldV, correlation.key(k), remainingBudget)
|
||||
errs = append(errs, err...)
|
||||
if remainingBudget < 0 {
|
||||
return errs, remainingBudget
|
||||
}
|
||||
}
|
||||
}
|
||||
if s.Properties != nil && sts.Properties != nil {
|
||||
for k, v := range obj {
|
||||
stsProp, stsOk := sts.Properties[k]
|
||||
sub, ok := s.Properties[k]
|
||||
if ok && stsOk {
|
||||
var oldV interface{}
|
||||
if correlatable {
|
||||
oldV = oldObj[k] // +k8s:verify-mutation:reason=clone
|
||||
}
|
||||
|
||||
var err field.ErrorList
|
||||
err, remainingBudget = sub.validate(ctx, fldPath.Child(k), &stsProp, v, oldV, correlation.key(k), remainingBudget)
|
||||
errs = append(errs, err...)
|
||||
if remainingBudget < 0 {
|
||||
return errs, remainingBudget
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errs, remainingBudget
|
||||
}
|
||||
|
||||
func (s *Validator) validateArray(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj []interface{}, correlation ratchetingOptions, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
|
||||
remainingBudget = costBudget
|
||||
if remainingBudget < 0 {
|
||||
return errs, remainingBudget
|
||||
}
|
||||
|
||||
if s.Items != nil && sts.Items != nil {
|
||||
// only map-type lists support self-oldSelf correlation for cel rules. if this isn't a
|
||||
// map-type list, then makeMapList returns an implementation that always returns nil
|
||||
correlatableOldItems := makeMapList(sts, oldObj)
|
||||
for i := range obj {
|
||||
var err field.ErrorList
|
||||
err, remainingBudget = s.Items.validate(ctx, fldPath.Index(i), sts.Items, obj[i], correlatableOldItems.Get(obj[i]), correlation.index(i), remainingBudget)
|
||||
errs = append(errs, err...)
|
||||
if remainingBudget < 0 {
|
||||
return errs, remainingBudget
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errs, remainingBudget
|
||||
}
|
||||
|
||||
// MapIsCorrelatable returns true if the mapType can be used to correlate the data elements of a map after an update
|
||||
// with the data elements of the map from before the updated.
|
||||
func MapIsCorrelatable(mapType *string) bool {
|
||||
// if a third map type is introduced, assume it's not correlatable. granular is the default if unspecified.
|
||||
return mapType == nil || *mapType == "granular" || *mapType == "atomic"
|
||||
}
|
||||
|
||||
func hasXValidations(s *schema.Structural) bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
if len(s.XValidations) > 0 {
|
||||
return true
|
||||
}
|
||||
if hasXValidations(s.Items) {
|
||||
return true
|
||||
}
|
||||
if s.AdditionalProperties != nil && hasXValidations(s.AdditionalProperties.Structural) {
|
||||
return true
|
||||
}
|
||||
if s.Properties != nil {
|
||||
for _, prop := range s.Properties {
|
||||
if hasXValidations(&prop) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
32
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/values.go
generated
vendored
Normal file
32
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/values.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright 2021 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 cel
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
|
||||
celopenapi "k8s.io/apiserver/pkg/cel/common"
|
||||
)
|
||||
|
||||
// UnstructuredToVal converts a Kubernetes unstructured data element to a CEL Val.
|
||||
// The root schema of custom resource schema is expected contain type meta and object meta schemas.
|
||||
// If Embedded resources do not contain type meta and object meta schemas, they will be added automatically.
|
||||
func UnstructuredToVal(unstructured interface{}, schema *structuralschema.Structural) ref.Val {
|
||||
return celopenapi.UnstructuredToVal(unstructured, &model.Structural{Structural: schema})
|
||||
}
|
||||
82
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/complete.go
generated
vendored
Normal file
82
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/complete.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright 2019 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 schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// validateStructuralCompleteness checks that every specified field or array in s is also specified
|
||||
// outside of value validation.
|
||||
func validateStructuralCompleteness(s *Structural, fldPath *field.Path) field.ErrorList {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return validateValueValidationCompleteness(s.ValueValidation, s, fldPath, fldPath)
|
||||
}
|
||||
|
||||
func validateValueValidationCompleteness(v *ValueValidation, s *Structural, sPath, vPath *field.Path) field.ErrorList {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
if s == nil {
|
||||
return field.ErrorList{field.Required(sPath, fmt.Sprintf("because it is defined in %s", vPath.String()))}
|
||||
}
|
||||
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
allErrs = append(allErrs, validateNestedValueValidationCompleteness(v.Not, s, sPath, vPath.Child("not"))...)
|
||||
for i := range v.AllOf {
|
||||
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.AllOf[i], s, sPath, vPath.Child("allOf").Index(i))...)
|
||||
}
|
||||
for i := range v.AnyOf {
|
||||
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.AnyOf[i], s, sPath, vPath.Child("anyOf").Index(i))...)
|
||||
}
|
||||
for i := range v.OneOf {
|
||||
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.OneOf[i], s, sPath, vPath.Child("oneOf").Index(i))...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateNestedValueValidationCompleteness(v *NestedValueValidation, s *Structural, sPath, vPath *field.Path) field.ErrorList {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
if s == nil {
|
||||
return field.ErrorList{field.Required(sPath, fmt.Sprintf("because it is defined in %s", vPath.String()))}
|
||||
}
|
||||
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
allErrs = append(allErrs, validateValueValidationCompleteness(&v.ValueValidation, s, sPath, vPath)...)
|
||||
allErrs = append(allErrs, validateNestedValueValidationCompleteness(v.Items, s.Items, sPath.Child("items"), vPath.Child("items"))...)
|
||||
for k, vFld := range v.Properties {
|
||||
if sFld, ok := s.Properties[k]; !ok {
|
||||
allErrs = append(allErrs, field.Required(sPath.Child("properties").Key(k), fmt.Sprintf("because it is defined in %s", vPath.Child("properties").Key(k))))
|
||||
} else {
|
||||
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&vFld, &sFld, sPath.Child("properties").Key(k), vPath.Child("properties").Key(k))...)
|
||||
}
|
||||
}
|
||||
|
||||
// don't check additionalProperties as this is not allowed (and checked during validation)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
290
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert.go
generated
vendored
Normal file
290
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/convert.go
generated
vendored
Normal file
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
Copyright 2019 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 schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
)
|
||||
|
||||
// NewStructural converts an OpenAPI v3 schema into a structural schema. A pre-validated JSONSchemaProps will
|
||||
// not fail on NewStructural. This means that we require that:
|
||||
//
|
||||
// - items is not an array of schemas
|
||||
// - the following fields are not set:
|
||||
// - id
|
||||
// - schema
|
||||
// - $ref
|
||||
// - patternProperties
|
||||
// - dependencies
|
||||
// - additionalItems
|
||||
// - definitions.
|
||||
//
|
||||
// The follow fields are not preserved:
|
||||
// - externalDocs
|
||||
// - example.
|
||||
func NewStructural(s *apiextensions.JSONSchemaProps) (*Structural, error) {
|
||||
if s == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := validateUnsupportedFields(s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vv, err := newValueValidation(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g, err := newGenerics(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
x, err := newExtensions(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ss := &Structural{
|
||||
Generic: *g,
|
||||
Extensions: *x,
|
||||
ValueValidation: vv,
|
||||
}
|
||||
|
||||
if s.Items != nil {
|
||||
if len(s.Items.JSONSchemas) > 0 {
|
||||
// we validate that it is not an array
|
||||
return nil, fmt.Errorf("OpenAPIV3Schema 'items' must be a schema, but is an array")
|
||||
}
|
||||
item, err := NewStructural(s.Items.Schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ss.Items = item
|
||||
}
|
||||
|
||||
if len(s.Properties) > 0 {
|
||||
ss.Properties = make(map[string]Structural, len(s.Properties))
|
||||
for k, x := range s.Properties {
|
||||
fld, err := NewStructural(&x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ss.Properties[k] = *fld
|
||||
}
|
||||
}
|
||||
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
func newGenerics(s *apiextensions.JSONSchemaProps) (*Generic, error) {
|
||||
if s == nil {
|
||||
return nil, nil
|
||||
}
|
||||
g := &Generic{
|
||||
Type: s.Type,
|
||||
Description: s.Description,
|
||||
Title: s.Title,
|
||||
Nullable: s.Nullable,
|
||||
}
|
||||
if s.Default != nil {
|
||||
g.Default = JSON{interface{}(*s.Default)}
|
||||
}
|
||||
|
||||
if s.AdditionalProperties != nil {
|
||||
if s.AdditionalProperties.Schema != nil {
|
||||
ss, err := NewStructural(s.AdditionalProperties.Schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.AdditionalProperties = &StructuralOrBool{Structural: ss, Bool: true}
|
||||
} else {
|
||||
g.AdditionalProperties = &StructuralOrBool{Bool: s.AdditionalProperties.Allows}
|
||||
}
|
||||
}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func newValueValidation(s *apiextensions.JSONSchemaProps) (*ValueValidation, error) {
|
||||
if s == nil {
|
||||
return nil, nil
|
||||
}
|
||||
not, err := newNestedValueValidation(s.Not)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := &ValueValidation{
|
||||
Format: s.Format,
|
||||
Maximum: s.Maximum,
|
||||
ExclusiveMaximum: s.ExclusiveMaximum,
|
||||
Minimum: s.Minimum,
|
||||
ExclusiveMinimum: s.ExclusiveMinimum,
|
||||
MaxLength: s.MaxLength,
|
||||
MinLength: s.MinLength,
|
||||
Pattern: s.Pattern,
|
||||
MaxItems: s.MaxItems,
|
||||
MinItems: s.MinItems,
|
||||
UniqueItems: s.UniqueItems,
|
||||
MultipleOf: s.MultipleOf,
|
||||
MaxProperties: s.MaxProperties,
|
||||
MinProperties: s.MinProperties,
|
||||
Required: s.Required,
|
||||
Not: not,
|
||||
}
|
||||
|
||||
for _, e := range s.Enum {
|
||||
v.Enum = append(v.Enum, JSON{e})
|
||||
}
|
||||
|
||||
for _, x := range s.AllOf {
|
||||
clause, err := newNestedValueValidation(&x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.AllOf = append(v.AllOf, *clause)
|
||||
}
|
||||
|
||||
for _, x := range s.AnyOf {
|
||||
clause, err := newNestedValueValidation(&x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.AnyOf = append(v.AnyOf, *clause)
|
||||
}
|
||||
|
||||
for _, x := range s.OneOf {
|
||||
clause, err := newNestedValueValidation(&x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.OneOf = append(v.OneOf, *clause)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func newNestedValueValidation(s *apiextensions.JSONSchemaProps) (*NestedValueValidation, error) {
|
||||
if s == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := validateUnsupportedFields(s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vv, err := newValueValidation(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g, err := newGenerics(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
x, err := newExtensions(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := &NestedValueValidation{
|
||||
ValueValidation: *vv,
|
||||
ForbiddenGenerics: *g,
|
||||
ForbiddenExtensions: *x,
|
||||
}
|
||||
|
||||
if s.Items != nil {
|
||||
if len(s.Items.JSONSchemas) > 0 {
|
||||
// we validate that it is not an array
|
||||
return nil, fmt.Errorf("OpenAPIV3Schema 'items' must be a schema, but is an array")
|
||||
}
|
||||
nvv, err := newNestedValueValidation(s.Items.Schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.Items = nvv
|
||||
}
|
||||
if s.Properties != nil {
|
||||
v.Properties = make(map[string]NestedValueValidation, len(s.Properties))
|
||||
for k, x := range s.Properties {
|
||||
nvv, err := newNestedValueValidation(&x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.Properties[k] = *nvv
|
||||
}
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func newExtensions(s *apiextensions.JSONSchemaProps) (*Extensions, error) {
|
||||
if s == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ret := &Extensions{
|
||||
XEmbeddedResource: s.XEmbeddedResource,
|
||||
XIntOrString: s.XIntOrString,
|
||||
XListMapKeys: s.XListMapKeys,
|
||||
XListType: s.XListType,
|
||||
XMapType: s.XMapType,
|
||||
}
|
||||
if err := apiextensionsv1.Convert_apiextensions_ValidationRules_To_v1_ValidationRules(&s.XValidations, &ret.XValidations, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.XPreserveUnknownFields != nil {
|
||||
if !*s.XPreserveUnknownFields {
|
||||
return nil, fmt.Errorf("internal error: 'x-kubernetes-preserve-unknown-fields' must be true or undefined")
|
||||
}
|
||||
ret.XPreserveUnknownFields = true
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// validateUnsupportedFields checks that those fields rejected by validation are actually unset.
|
||||
func validateUnsupportedFields(s *apiextensions.JSONSchemaProps) error {
|
||||
if len(s.ID) > 0 {
|
||||
return fmt.Errorf("OpenAPIV3Schema 'id' is not supported")
|
||||
}
|
||||
if len(s.Schema) > 0 {
|
||||
return fmt.Errorf("OpenAPIV3Schema 'schema' is not supported")
|
||||
}
|
||||
if s.Ref != nil && len(*s.Ref) > 0 {
|
||||
return fmt.Errorf("OpenAPIV3Schema '$ref' is not supported")
|
||||
}
|
||||
if len(s.PatternProperties) > 0 {
|
||||
return fmt.Errorf("OpenAPIV3Schema 'patternProperties' is not supported")
|
||||
}
|
||||
if len(s.Dependencies) > 0 {
|
||||
return fmt.Errorf("OpenAPIV3Schema 'dependencies' is not supported")
|
||||
}
|
||||
if s.AdditionalItems != nil {
|
||||
return fmt.Errorf("OpenAPIV3Schema 'additionalItems' is not supported")
|
||||
}
|
||||
if len(s.Definitions) > 0 {
|
||||
return fmt.Errorf("OpenAPIV3Schema 'definitions' is not supported")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
69
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/algorithm.go
generated
vendored
Normal file
69
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/algorithm.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
Copyright 2019 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 defaulting
|
||||
|
||||
import (
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// isNonNullalbeNull returns true if the item is nil AND it's nullable
|
||||
func isNonNullableNull(x interface{}, s *structuralschema.Structural) bool {
|
||||
return x == nil && s != nil && s.Generic.Nullable == false
|
||||
}
|
||||
|
||||
// Default does defaulting of x depending on default values in s.
|
||||
// Default values from s are deep-copied.
|
||||
//
|
||||
// PruneNonNullableNullsWithoutDefaults has left the non-nullable nulls
|
||||
// that have a default here.
|
||||
func Default(x interface{}, s *structuralschema.Structural) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch x := x.(type) {
|
||||
case map[string]interface{}:
|
||||
for k, prop := range s.Properties {
|
||||
if prop.Default.Object == nil {
|
||||
continue
|
||||
}
|
||||
if _, found := x[k]; !found || isNonNullableNull(x[k], &prop) {
|
||||
x[k] = runtime.DeepCopyJSONValue(prop.Default.Object)
|
||||
}
|
||||
}
|
||||
for k := range x {
|
||||
if prop, found := s.Properties[k]; found {
|
||||
Default(x[k], &prop)
|
||||
} else if s.AdditionalProperties != nil {
|
||||
if isNonNullableNull(x[k], s.AdditionalProperties.Structural) {
|
||||
x[k] = runtime.DeepCopyJSONValue(s.AdditionalProperties.Structural.Default.Object)
|
||||
}
|
||||
Default(x[k], s.AdditionalProperties.Structural)
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for i := range x {
|
||||
if isNonNullableNull(x[i], s.Items) {
|
||||
x[i] = runtime.DeepCopyJSONValue(s.Items.Default.Object)
|
||||
}
|
||||
Default(x[i], s.Items)
|
||||
}
|
||||
default:
|
||||
// scalars, do nothing
|
||||
}
|
||||
}
|
||||
91
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/prune.go
generated
vendored
Normal file
91
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/prune.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2019 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 defaulting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
structuralobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// PruneDefaults prunes default values according to the schema and according to
|
||||
// the ObjectMeta definition of the running server. It mutates the passed schema.
|
||||
func PruneDefaults(s *structuralschema.Structural) error {
|
||||
p := pruner{s}
|
||||
_, err := p.pruneDefaults(s, NewRootObjectFunc())
|
||||
return err
|
||||
}
|
||||
|
||||
type pruner struct {
|
||||
rootSchema *structuralschema.Structural
|
||||
}
|
||||
|
||||
func (p *pruner) pruneDefaults(s *structuralschema.Structural, f SurroundingObjectFunc) (changed bool, err error) {
|
||||
if s == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if s.Default.Object != nil {
|
||||
orig := runtime.DeepCopyJSONValue(s.Default.Object)
|
||||
|
||||
obj, acc, err := f(s.Default.Object)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to prune default value: %v", err)
|
||||
}
|
||||
if err := structuralobjectmeta.Coerce(nil, obj, p.rootSchema, true, true); err != nil {
|
||||
return false, fmt.Errorf("failed to prune default value: %v", err)
|
||||
}
|
||||
pruning.Prune(obj, p.rootSchema, true)
|
||||
s.Default.Object, _, err = acc(obj)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to prune default value: %v", err)
|
||||
}
|
||||
|
||||
changed = changed || !reflect.DeepEqual(orig, s.Default.Object)
|
||||
}
|
||||
|
||||
if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
|
||||
c, err := p.pruneDefaults(s.AdditionalProperties.Structural, f.Child("*"))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
changed = changed || c
|
||||
}
|
||||
if s.Items != nil {
|
||||
c, err := p.pruneDefaults(s.Items, f.Index())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
changed = changed || c
|
||||
}
|
||||
for k, subSchema := range s.Properties {
|
||||
c, err := p.pruneDefaults(&subSchema, f.Child(k))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if c {
|
||||
s.Properties[k] = subSchema
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
return changed, nil
|
||||
}
|
||||
66
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/prunenulls.go
generated
vendored
Normal file
66
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/prunenulls.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2020 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 defaulting
|
||||
|
||||
import structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
|
||||
func isNonNullableNonDefaultableNull(x interface{}, s *structuralschema.Structural) bool {
|
||||
return x == nil && s != nil && s.Generic.Nullable == false && s.Default.Object == nil
|
||||
}
|
||||
|
||||
func getSchemaForField(field string, s *structuralschema.Structural) *structuralschema.Structural {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
schema, ok := s.Properties[field]
|
||||
if ok {
|
||||
return &schema
|
||||
}
|
||||
if s.AdditionalProperties != nil {
|
||||
return s.AdditionalProperties.Structural
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PruneNonNullableNullsWithoutDefaults removes non-nullable
|
||||
// non-defaultable null values from object.
|
||||
//
|
||||
// Non-nullable nulls that have a default are left alone here and will
|
||||
// be defaulted later.
|
||||
func PruneNonNullableNullsWithoutDefaults(x interface{}, s *structuralschema.Structural) {
|
||||
switch x := x.(type) {
|
||||
case map[string]interface{}:
|
||||
for k, v := range x {
|
||||
schema := getSchemaForField(k, s)
|
||||
if isNonNullableNonDefaultableNull(v, schema) {
|
||||
delete(x, k)
|
||||
} else {
|
||||
PruneNonNullableNullsWithoutDefaults(v, schema)
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
var schema *structuralschema.Structural
|
||||
if s != nil {
|
||||
schema = s.Items
|
||||
}
|
||||
for i := range x {
|
||||
PruneNonNullableNullsWithoutDefaults(x[i], schema)
|
||||
}
|
||||
default:
|
||||
// scalars, do nothing
|
||||
}
|
||||
}
|
||||
147
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/surroundingobject.go
generated
vendored
Normal file
147
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/surroundingobject.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
Copyright 2019 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 defaulting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// AccessorFunc returns a node x in obj on a fixed (implicitly encoded) JSON path
|
||||
// if that path exists in obj (found==true). If it does not exist, found is false.
|
||||
// If on the path the type of a field is wrong, an error is returned.
|
||||
type AccessorFunc func(obj map[string]interface{}) (x interface{}, found bool, err error)
|
||||
|
||||
// SurroundingObjectFunc is a surrounding object builder with a given x at a leaf.
|
||||
// Which leave is determined by the series of Index() and Child(k) calls.
|
||||
// It also returns the inverse of the builder, namely the accessor that extracts x
|
||||
// from the test object.
|
||||
//
|
||||
// With obj, acc, _ := someSurroundingObjectFunc(x) we get:
|
||||
//
|
||||
// acc(obj) == x
|
||||
// reflect.DeepEqual(acc(DeepCopy(obj), x) == x
|
||||
//
|
||||
// where x is the original instance for slices and maps.
|
||||
//
|
||||
// If after computation of acc the node holding x in obj is mutated (e.g. pruned),
|
||||
// the accessor will return that mutated node value (e.g. the pruned x).
|
||||
//
|
||||
// Example (ignoring the last two return values):
|
||||
//
|
||||
// NewRootObjectFunc()(x) == x
|
||||
// NewRootObjectFunc().Index()(x) == [x]
|
||||
// NewRootObjectFunc().Index().Child("foo") == [{"foo": x}]
|
||||
// NewRootObjectFunc().Index().Child("foo").Child("bar") == [{"foo": {"bar":x}}]
|
||||
// NewRootObjectFunc().Index().Child("foo").Child("bar").Index() == [{"foo": {"bar":[x]}}]
|
||||
//
|
||||
// and:
|
||||
//
|
||||
// NewRootObjectFunc(), then acc(x) == x
|
||||
// NewRootObjectFunc().Index(), then acc([x]) == x
|
||||
// NewRootObjectFunc().Index().Child("foo"), then acc([{"foo": x}]) == x
|
||||
// NewRootObjectFunc().Index().Child("foo").Child("bar"), then acc([{"foo": {"bar":x}}]) == x
|
||||
// NewRootObjectFunc().Index().Child("foo").Child("bar").Index(), then acc([{"foo": {"bar":[x]}}]) == x
|
||||
type SurroundingObjectFunc func(focus interface{}) (map[string]interface{}, AccessorFunc, error)
|
||||
|
||||
// NewRootObjectFunc returns the identity function. The passed focus value
|
||||
// must be an object.
|
||||
func NewRootObjectFunc() SurroundingObjectFunc {
|
||||
return func(x interface{}) (map[string]interface{}, AccessorFunc, error) {
|
||||
obj, ok := x.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("object root default value must be of object type")
|
||||
}
|
||||
return obj, func(root map[string]interface{}) (interface{}, bool, error) {
|
||||
return root, true, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTypeMeta returns a closure with the TypeMeta fields set if they are defined.
|
||||
// This mutates f(x).
|
||||
func (f SurroundingObjectFunc) WithTypeMeta(meta metav1.TypeMeta) SurroundingObjectFunc {
|
||||
return func(x interface{}) (map[string]interface{}, AccessorFunc, error) {
|
||||
obj, acc, err := f(x)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if obj == nil {
|
||||
obj = map[string]interface{}{}
|
||||
}
|
||||
if _, found := obj["kind"]; !found {
|
||||
obj["kind"] = meta.Kind
|
||||
}
|
||||
if _, found := obj["apiVersion"]; !found {
|
||||
obj["apiVersion"] = meta.APIVersion
|
||||
}
|
||||
return obj, acc, err
|
||||
}
|
||||
}
|
||||
|
||||
// Child returns a function x => f({k: x}) and the corresponding accessor.
|
||||
func (f SurroundingObjectFunc) Child(k string) SurroundingObjectFunc {
|
||||
return func(x interface{}) (map[string]interface{}, AccessorFunc, error) {
|
||||
obj, acc, err := f(map[string]interface{}{k: x})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return obj, func(obj map[string]interface{}) (interface{}, bool, error) {
|
||||
x, found, err := acc(obj)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf(".%s%v", k, err)
|
||||
}
|
||||
if !found {
|
||||
return nil, false, nil
|
||||
}
|
||||
if x, ok := x.(map[string]interface{}); !ok {
|
||||
return nil, false, fmt.Errorf(".%s must be of object type", k)
|
||||
} else if v, found := x[k]; !found {
|
||||
return nil, false, nil
|
||||
} else {
|
||||
return v, true, nil
|
||||
}
|
||||
}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Index returns a function x => f([x]) and the corresponding accessor.
|
||||
func (f SurroundingObjectFunc) Index() SurroundingObjectFunc {
|
||||
return func(focus interface{}) (map[string]interface{}, AccessorFunc, error) {
|
||||
obj, acc, err := f([]interface{}{focus})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return obj, func(obj map[string]interface{}) (interface{}, bool, error) {
|
||||
x, found, err := acc(obj)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("[]%v", err)
|
||||
}
|
||||
if !found {
|
||||
return nil, false, nil
|
||||
}
|
||||
if x, ok := x.([]interface{}); !ok {
|
||||
return nil, false, fmt.Errorf("[] must be of array type")
|
||||
} else if len(x) == 0 {
|
||||
return nil, false, nil
|
||||
} else {
|
||||
return x[0], true, nil
|
||||
}
|
||||
}, err
|
||||
}
|
||||
}
|
||||
199
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/validation.go
generated
vendored
Normal file
199
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/validation.go
generated
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
Copyright 2019 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 defaulting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
|
||||
schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning"
|
||||
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
|
||||
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
)
|
||||
|
||||
// ValidateDefaults checks that default values validate and are properly pruned.
|
||||
// context is passed for supporting context cancellation during cel validation
|
||||
func ValidateDefaults(ctx context.Context, pth *field.Path, s *structuralschema.Structural, isResourceRoot, requirePrunedDefaults bool) (field.ErrorList, error) {
|
||||
f := NewRootObjectFunc().WithTypeMeta(metav1.TypeMeta{APIVersion: "validation/v1", Kind: "Validation"})
|
||||
|
||||
if isResourceRoot {
|
||||
if s == nil {
|
||||
s = &structuralschema.Structural{}
|
||||
}
|
||||
if !s.XEmbeddedResource {
|
||||
clone := *s
|
||||
clone.XEmbeddedResource = true
|
||||
s = &clone
|
||||
}
|
||||
}
|
||||
|
||||
allErr, error, _ := validate(ctx, pth, s, s, f, false, requirePrunedDefaults, celconfig.RuntimeCELCostBudget)
|
||||
return allErr, error
|
||||
}
|
||||
|
||||
// validate is the recursive step func for the validation. insideMeta is true if s specifies
|
||||
// TypeMeta or ObjectMeta. The SurroundingObjectFunc f is used to validate defaults of
|
||||
// TypeMeta or ObjectMeta fields.
|
||||
// context is passed for supporting context cancellation during cel validation
|
||||
func validate(ctx context.Context, pth *field.Path, s *structuralschema.Structural, rootSchema *structuralschema.Structural, f SurroundingObjectFunc, insideMeta, requirePrunedDefaults bool, costBudget int64) (allErrs field.ErrorList, error error, remainingCost int64) {
|
||||
remainingCost = costBudget
|
||||
if s == nil {
|
||||
return nil, nil, remainingCost
|
||||
}
|
||||
|
||||
if s.XEmbeddedResource {
|
||||
insideMeta = false
|
||||
f = NewRootObjectFunc().WithTypeMeta(metav1.TypeMeta{APIVersion: "validation/v1", Kind: "Validation"})
|
||||
rootSchema = s
|
||||
}
|
||||
|
||||
isResourceRoot := s == rootSchema
|
||||
|
||||
if s.Default.Object != nil {
|
||||
validator := apiservervalidation.NewSchemaValidatorFromOpenAPI(s.ToKubeOpenAPI())
|
||||
|
||||
if insideMeta {
|
||||
obj, _, err := f(runtime.DeepCopyJSONValue(s.Default.Object))
|
||||
if err != nil {
|
||||
// this should never happen. f(s.Default.Object) only gives an error if f is the
|
||||
// root object func, but the default value is not a map. But then we wouldn't be
|
||||
// in this case.
|
||||
return nil, fmt.Errorf("failed to validate default value inside metadata: %v", err), remainingCost
|
||||
}
|
||||
|
||||
// check ObjectMeta/TypeMeta and everything else
|
||||
if err := schemaobjectmeta.Coerce(nil, obj, rootSchema, true, false); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, fmt.Sprintf("must result in valid metadata: %v", err)))
|
||||
} else if errs := schemaobjectmeta.Validate(nil, obj, rootSchema, true); len(errs) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, fmt.Sprintf("must result in valid metadata: %v", errs.ToAggregate())))
|
||||
} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
|
||||
allErrs = append(allErrs, errs...)
|
||||
} else if celValidator := cel.NewValidator(s, isResourceRoot, celconfig.PerCallLimit); celValidator != nil {
|
||||
celErrs, rmCost := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, s.Default.Object, remainingCost)
|
||||
allErrs = append(allErrs, celErrs...)
|
||||
|
||||
if len(celErrs) == 0 && utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CRDValidationRatcheting) {
|
||||
// If ratcheting is enabled some CEL rules may use optionalOldSelf
|
||||
// For such rules the above validation is not sufficient for
|
||||
// determining if the default value is a valid value to introduce
|
||||
// via create or uncorrelated update.
|
||||
//
|
||||
// Validate an update from nil to the default value to ensure
|
||||
// that the default value pass
|
||||
celErrs, rmCostWithoutOldObject := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, nil, remainingCost)
|
||||
allErrs = append(allErrs, celErrs...)
|
||||
|
||||
// capture the cost of both types of runs and take whichever
|
||||
// leaves less remaining cost
|
||||
if rmCostWithoutOldObject < rmCost {
|
||||
rmCost = rmCostWithoutOldObject
|
||||
}
|
||||
}
|
||||
|
||||
remainingCost = rmCost
|
||||
if remainingCost < 0 {
|
||||
return allErrs, nil, remainingCost
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// check whether default is pruned
|
||||
if requirePrunedDefaults {
|
||||
pruned := runtime.DeepCopyJSONValue(s.Default.Object)
|
||||
pruning.Prune(pruned, s, s.XEmbeddedResource)
|
||||
if !reflect.DeepEqual(pruned, s.Default.Object) {
|
||||
allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, "must not have unknown fields"))
|
||||
}
|
||||
}
|
||||
|
||||
// check ObjectMeta/TypeMeta and everything else
|
||||
if err := schemaobjectmeta.Coerce(pth.Child("default"), s.Default.Object, s, s.XEmbeddedResource, false); err != nil {
|
||||
allErrs = append(allErrs, err)
|
||||
} else if errs := schemaobjectmeta.Validate(pth.Child("default"), s.Default.Object, s, s.XEmbeddedResource); len(errs) > 0 {
|
||||
allErrs = append(allErrs, errs...)
|
||||
} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
|
||||
allErrs = append(allErrs, errs...)
|
||||
} else if celValidator := cel.NewValidator(s, isResourceRoot, celconfig.PerCallLimit); celValidator != nil {
|
||||
celErrs, rmCost := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, s.Default.Object, remainingCost)
|
||||
allErrs = append(allErrs, celErrs...)
|
||||
|
||||
if len(celErrs) == 0 && utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CRDValidationRatcheting) {
|
||||
// If ratcheting is enabled some CEL rules may use optionalOldSelf
|
||||
// For such rules the above validation is not sufficient for
|
||||
// determining if the default value is a valid value to introduce
|
||||
// via create or uncorrelated update.
|
||||
//
|
||||
// Validate an update from nil to the default value to ensure
|
||||
// that the default value pass
|
||||
celErrs, rmCostWithoutOldObject := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, nil, remainingCost)
|
||||
allErrs = append(allErrs, celErrs...)
|
||||
|
||||
// capture the cost of both types of runs and take whichever
|
||||
// leaves less remaining cost
|
||||
if rmCostWithoutOldObject < rmCost {
|
||||
rmCost = rmCostWithoutOldObject
|
||||
}
|
||||
}
|
||||
|
||||
remainingCost = rmCost
|
||||
if remainingCost < 0 {
|
||||
return allErrs, nil, remainingCost
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// do not follow additionalProperties because defaults are forbidden there
|
||||
|
||||
if s.Items != nil {
|
||||
errs, err, rCost := validate(ctx, pth.Child("items"), s.Items, rootSchema, f.Index(), insideMeta, requirePrunedDefaults, remainingCost)
|
||||
remainingCost = rCost
|
||||
allErrs = append(allErrs, errs...)
|
||||
if err != nil {
|
||||
return nil, err, remainingCost
|
||||
}
|
||||
if remainingCost < 0 {
|
||||
return allErrs, nil, remainingCost
|
||||
}
|
||||
}
|
||||
|
||||
for k, subSchema := range s.Properties {
|
||||
subInsideMeta := insideMeta
|
||||
if s.XEmbeddedResource && (k == "metadata" || k == "apiVersion" || k == "kind") {
|
||||
subInsideMeta = true
|
||||
}
|
||||
errs, err, rCost := validate(ctx, pth.Child("properties").Key(k), &subSchema, rootSchema, f.Child(k), subInsideMeta, requirePrunedDefaults, remainingCost)
|
||||
remainingCost = rCost
|
||||
allErrs = append(allErrs, errs...)
|
||||
if err != nil {
|
||||
return nil, err, remainingCost
|
||||
}
|
||||
if remainingCost < 0 {
|
||||
return allErrs, nil, remainingCost
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs, nil, remainingCost
|
||||
}
|
||||
154
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/kubeopenapi.go
generated
vendored
Normal file
154
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/kubeopenapi.go
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
Copyright 2020 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 schema
|
||||
|
||||
import (
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
)
|
||||
|
||||
// ToKubeOpenAPI converts a structural schema to go-openapi schema. It is faithful and roundtrippable.
|
||||
func (s *Structural) ToKubeOpenAPI() *spec.Schema {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := &spec.Schema{}
|
||||
|
||||
if s.Items != nil {
|
||||
ret.Items = &spec.SchemaOrArray{Schema: s.Items.ToKubeOpenAPI()}
|
||||
}
|
||||
if s.Properties != nil {
|
||||
ret.Properties = make(map[string]spec.Schema, len(s.Properties))
|
||||
for k, v := range s.Properties {
|
||||
ret.Properties[k] = *v.ToKubeOpenAPI()
|
||||
}
|
||||
}
|
||||
s.Generic.toKubeOpenAPI(ret)
|
||||
s.Extensions.toKubeOpenAPI(ret)
|
||||
s.ValueValidation.toKubeOpenAPI(ret)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (g *Generic) toKubeOpenAPI(ret *spec.Schema) {
|
||||
if g == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(g.Type) != 0 {
|
||||
ret.Type = spec.StringOrArray{g.Type}
|
||||
}
|
||||
ret.Nullable = g.Nullable
|
||||
if g.AdditionalProperties != nil {
|
||||
ret.AdditionalProperties = &spec.SchemaOrBool{
|
||||
Allows: g.AdditionalProperties.Bool,
|
||||
Schema: g.AdditionalProperties.Structural.ToKubeOpenAPI(),
|
||||
}
|
||||
}
|
||||
ret.Description = g.Description
|
||||
ret.Title = g.Title
|
||||
ret.Default = g.Default.Object
|
||||
}
|
||||
|
||||
func (x *Extensions) toKubeOpenAPI(ret *spec.Schema) {
|
||||
if x == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if x.XPreserveUnknownFields {
|
||||
ret.VendorExtensible.AddExtension("x-kubernetes-preserve-unknown-fields", true)
|
||||
}
|
||||
if x.XEmbeddedResource {
|
||||
ret.VendorExtensible.AddExtension("x-kubernetes-embedded-resource", true)
|
||||
}
|
||||
if x.XIntOrString {
|
||||
ret.VendorExtensible.AddExtension("x-kubernetes-int-or-string", true)
|
||||
}
|
||||
if len(x.XListMapKeys) > 0 {
|
||||
ret.VendorExtensible.AddExtension("x-kubernetes-list-map-keys", x.XListMapKeys)
|
||||
}
|
||||
if x.XListType != nil {
|
||||
ret.VendorExtensible.AddExtension("x-kubernetes-list-type", *x.XListType)
|
||||
}
|
||||
if x.XMapType != nil {
|
||||
ret.VendorExtensible.AddExtension("x-kubernetes-map-type", *x.XMapType)
|
||||
}
|
||||
if len(x.XValidations) > 0 {
|
||||
ret.VendorExtensible.AddExtension("x-kubernetes-validations", x.XValidations)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *ValueValidation) toKubeOpenAPI(ret *spec.Schema) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ret.Format = v.Format
|
||||
ret.Maximum = v.Maximum
|
||||
ret.ExclusiveMaximum = v.ExclusiveMaximum
|
||||
ret.Minimum = v.Minimum
|
||||
ret.ExclusiveMinimum = v.ExclusiveMinimum
|
||||
ret.MaxLength = v.MaxLength
|
||||
ret.MinLength = v.MinLength
|
||||
ret.Pattern = v.Pattern
|
||||
ret.MaxItems = v.MaxItems
|
||||
ret.MinItems = v.MinItems
|
||||
ret.UniqueItems = v.UniqueItems
|
||||
ret.MultipleOf = v.MultipleOf
|
||||
if v.Enum != nil {
|
||||
ret.Enum = make([]interface{}, 0, len(v.Enum))
|
||||
for i := range v.Enum {
|
||||
ret.Enum = append(ret.Enum, v.Enum[i].Object)
|
||||
}
|
||||
}
|
||||
ret.MaxProperties = v.MaxProperties
|
||||
ret.MinProperties = v.MinProperties
|
||||
ret.Required = v.Required
|
||||
for i := range v.AllOf {
|
||||
ret.AllOf = append(ret.AllOf, *v.AllOf[i].toKubeOpenAPI())
|
||||
}
|
||||
for i := range v.AnyOf {
|
||||
ret.AnyOf = append(ret.AnyOf, *v.AnyOf[i].toKubeOpenAPI())
|
||||
}
|
||||
for i := range v.OneOf {
|
||||
ret.OneOf = append(ret.OneOf, *v.OneOf[i].toKubeOpenAPI())
|
||||
}
|
||||
ret.Not = v.Not.toKubeOpenAPI()
|
||||
}
|
||||
|
||||
func (vv *NestedValueValidation) toKubeOpenAPI() *spec.Schema {
|
||||
if vv == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := &spec.Schema{}
|
||||
|
||||
vv.ValueValidation.toKubeOpenAPI(ret)
|
||||
if vv.Items != nil {
|
||||
ret.Items = &spec.SchemaOrArray{Schema: vv.Items.toKubeOpenAPI()}
|
||||
}
|
||||
if vv.Properties != nil {
|
||||
ret.Properties = make(map[string]spec.Schema, len(vv.Properties))
|
||||
for k, v := range vv.Properties {
|
||||
ret.Properties[k] = *v.toKubeOpenAPI()
|
||||
}
|
||||
}
|
||||
vv.ForbiddenGenerics.toKubeOpenAPI(ret) // normally empty. Exception: int-or-string
|
||||
vv.ForbiddenExtensions.toKubeOpenAPI(ret) // shouldn't do anything
|
||||
|
||||
return ret
|
||||
}
|
||||
147
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/algorithm.go
generated
vendored
Normal file
147
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/algorithm.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
Copyright 2019 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 objectmeta
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// CoerceOptions gives the ability to ReturnUnknownFieldPaths for fields
|
||||
// unrecognized by the schema or DropInvalidFields for fields that are a part
|
||||
// of the schema, but are malformed.
|
||||
type CoerceOptions struct {
|
||||
// DropInvalidFields discards malformed serialized metadata fields that
|
||||
// cannot be successfully decoded to the corresponding ObjectMeta field.
|
||||
// This only applies to fields that are recognized as part of the schema,
|
||||
// but of an invalid type (i.e. cause an error when unmarshaling, rather
|
||||
// than being dropped or causing a strictErr).
|
||||
DropInvalidFields bool
|
||||
// ReturnUnknownFieldPaths will return the paths to fields that are not
|
||||
// recognized as part of the schema.
|
||||
ReturnUnknownFieldPaths bool
|
||||
}
|
||||
|
||||
// Coerce checks types of embedded ObjectMeta and TypeMeta and prunes unknown fields inside the former.
|
||||
// It does coerce ObjectMeta and TypeMeta at the root if isResourceRoot is true.
|
||||
// If opts.ReturnUnknownFieldPaths is true, it will return the paths of any fields that are not a part of the
|
||||
// schema that are dropped when unmarshaling.
|
||||
// If opts.DropInvalidFields is true, fields of wrong type will be dropped.
|
||||
func CoerceWithOptions(pth *field.Path, obj interface{}, s *structuralschema.Structural, isResourceRoot bool, opts CoerceOptions) (*field.Error, []string) {
|
||||
if isResourceRoot {
|
||||
if s == nil {
|
||||
s = &structuralschema.Structural{}
|
||||
}
|
||||
if !s.XEmbeddedResource {
|
||||
clone := *s
|
||||
clone.XEmbeddedResource = true
|
||||
s = &clone
|
||||
}
|
||||
}
|
||||
c := coercer{DropInvalidFields: opts.DropInvalidFields, ReturnUnknownFieldPaths: opts.ReturnUnknownFieldPaths}
|
||||
schemaOpts := &structuralschema.UnknownFieldPathOptions{
|
||||
TrackUnknownFieldPaths: opts.ReturnUnknownFieldPaths,
|
||||
}
|
||||
fieldErr := c.coerce(pth, obj, s, schemaOpts)
|
||||
sort.Strings(schemaOpts.UnknownFieldPaths)
|
||||
return fieldErr, schemaOpts.UnknownFieldPaths
|
||||
}
|
||||
|
||||
// Coerce calls CoerceWithOptions without returning unknown field paths.
|
||||
func Coerce(pth *field.Path, obj interface{}, s *structuralschema.Structural, isResourceRoot, dropInvalidFields bool) *field.Error {
|
||||
fieldErr, _ := CoerceWithOptions(pth, obj, s, isResourceRoot, CoerceOptions{DropInvalidFields: dropInvalidFields})
|
||||
return fieldErr
|
||||
}
|
||||
|
||||
type coercer struct {
|
||||
DropInvalidFields bool
|
||||
ReturnUnknownFieldPaths bool
|
||||
}
|
||||
|
||||
func (c *coercer) coerce(pth *field.Path, x interface{}, s *structuralschema.Structural, opts *structuralschema.UnknownFieldPathOptions) *field.Error {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
origPathLen := len(opts.ParentPath)
|
||||
defer func() {
|
||||
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||
}()
|
||||
switch x := x.(type) {
|
||||
case map[string]interface{}:
|
||||
for k, v := range x {
|
||||
if s.XEmbeddedResource {
|
||||
switch k {
|
||||
case "apiVersion", "kind":
|
||||
if _, ok := v.(string); !ok && c.DropInvalidFields {
|
||||
delete(x, k)
|
||||
} else if !ok {
|
||||
return field.Invalid(pth.Child(k), v, "must be a string")
|
||||
}
|
||||
case "metadata":
|
||||
meta, found, unknownFields, err := GetObjectMetaWithOptions(x, ObjectMetaOptions{
|
||||
DropMalformedFields: c.DropInvalidFields,
|
||||
ReturnUnknownFieldPaths: c.ReturnUnknownFieldPaths,
|
||||
ParentPath: pth,
|
||||
})
|
||||
opts.UnknownFieldPaths = append(opts.UnknownFieldPaths, unknownFields...)
|
||||
if err != nil {
|
||||
if !c.DropInvalidFields {
|
||||
return field.Invalid(pth.Child("metadata"), v, err.Error())
|
||||
}
|
||||
// pass through on error if DropInvalidFields is true
|
||||
} else if found {
|
||||
if err := SetObjectMeta(x, meta); err != nil {
|
||||
return field.Invalid(pth.Child("metadata"), v, err.Error())
|
||||
}
|
||||
if meta.CreationTimestamp.IsZero() {
|
||||
unstructured.RemoveNestedField(x, "metadata", "creationTimestamp")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
prop, ok := s.Properties[k]
|
||||
if ok {
|
||||
opts.AppendKey(k)
|
||||
if err := c.coerce(pth.Child(k), v, &prop, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||
} else if s.AdditionalProperties != nil {
|
||||
opts.AppendKey(k)
|
||||
if err := c.coerce(pth.Key(k), v, s.AdditionalProperties.Structural, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for i, v := range x {
|
||||
opts.AppendIndex(i)
|
||||
if err := c.coerce(pth.Index(i), v, s.Items, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||
}
|
||||
default:
|
||||
// scalars, do nothing
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
151
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/coerce.go
generated
vendored
Normal file
151
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/coerce.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Copyright 2019 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 objectmeta
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utiljson "k8s.io/apimachinery/pkg/util/json"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
kjson "sigs.k8s.io/json"
|
||||
)
|
||||
|
||||
// GetObjectMeta calls GetObjectMetaWithOptions without returning unknown field paths.
|
||||
func GetObjectMeta(obj map[string]interface{}, dropMalformedFields bool) (*metav1.ObjectMeta, bool, error) {
|
||||
meta, found, _, err := GetObjectMetaWithOptions(obj, ObjectMetaOptions{
|
||||
DropMalformedFields: dropMalformedFields,
|
||||
})
|
||||
return meta, found, err
|
||||
}
|
||||
|
||||
// ObjectMetaOptions provides the options for how GetObjectMeta should retrieve the object meta.
|
||||
type ObjectMetaOptions struct {
|
||||
// DropMalformedFields discards malformed serialized metadata fields that
|
||||
// cannot be successfully decoded to the corresponding ObjectMeta field.
|
||||
// This only applies to fields that are recognized as part of the schema,
|
||||
// but of an invalid type (i.e. cause an error when unmarshaling, rather
|
||||
// than being dropped or causing a strictErr).
|
||||
DropMalformedFields bool
|
||||
// ReturnUnknownFieldPaths will return the paths to fields that are not
|
||||
// recognized as part of the schema.
|
||||
ReturnUnknownFieldPaths bool
|
||||
// ParentPath provides the current path up to the given ObjectMeta.
|
||||
// If nil, the metadata is assumed to be at the root of the object.
|
||||
ParentPath *field.Path
|
||||
}
|
||||
|
||||
// GetObjectMetaWithOptions does conversion of JSON to ObjectMeta.
|
||||
// It first tries json.Unmarshal into a metav1.ObjectMeta
|
||||
// type. If that does not work and opts.DropMalformedFields is true, it does field-by-field best-effort conversion
|
||||
// throwing away fields which lead to errors.
|
||||
// If opts.ReturnedUnknownFields is true, it will UnmarshalStrict instead, returning the paths of any unknown fields
|
||||
// it encounters (i.e. paths returned as strict errs from UnmarshalStrict)
|
||||
func GetObjectMetaWithOptions(obj map[string]interface{}, opts ObjectMetaOptions) (*metav1.ObjectMeta, bool, []string, error) {
|
||||
metadata, found := obj["metadata"]
|
||||
if !found {
|
||||
return nil, false, nil, nil
|
||||
}
|
||||
|
||||
// round-trip through JSON first, hoping that unmarshalling just works
|
||||
objectMeta := &metav1.ObjectMeta{}
|
||||
metadataBytes, err := utiljson.Marshal(metadata)
|
||||
if err != nil {
|
||||
return nil, false, nil, err
|
||||
}
|
||||
var unmarshalErr error
|
||||
if opts.ReturnUnknownFieldPaths {
|
||||
var strictErrs []error
|
||||
strictErrs, unmarshalErr = kjson.UnmarshalStrict(metadataBytes, objectMeta)
|
||||
if unmarshalErr == nil {
|
||||
if len(strictErrs) > 0 {
|
||||
unknownPaths := []string{}
|
||||
prefix := opts.ParentPath.Child("metadata").String()
|
||||
for _, err := range strictErrs {
|
||||
if fieldPathErr, ok := err.(kjson.FieldError); ok {
|
||||
unknownPaths = append(unknownPaths, prefix+"."+fieldPathErr.FieldPath())
|
||||
}
|
||||
}
|
||||
return objectMeta, true, unknownPaths, nil
|
||||
}
|
||||
return objectMeta, true, nil, nil
|
||||
}
|
||||
} else {
|
||||
if unmarshalErr = utiljson.Unmarshal(metadataBytes, objectMeta); unmarshalErr == nil {
|
||||
// if successful, return
|
||||
return objectMeta, true, nil, nil
|
||||
}
|
||||
}
|
||||
if !opts.DropMalformedFields {
|
||||
// if we're not trying to drop malformed fields, return the error
|
||||
return nil, true, nil, unmarshalErr
|
||||
}
|
||||
|
||||
metadataMap, ok := metadata.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, false, nil, fmt.Errorf("invalid metadata: expected object, got %T", metadata)
|
||||
}
|
||||
|
||||
// Go field by field accumulating into the metadata object.
|
||||
// This takes advantage of the fact that you can repeatedly unmarshal individual fields into a single struct,
|
||||
// each iteration preserving the old key-values.
|
||||
accumulatedObjectMeta := &metav1.ObjectMeta{}
|
||||
testObjectMeta := &metav1.ObjectMeta{}
|
||||
var unknownFields []string
|
||||
for k, v := range metadataMap {
|
||||
// serialize a single field
|
||||
if singleFieldBytes, err := utiljson.Marshal(map[string]interface{}{k: v}); err == nil {
|
||||
// do a test unmarshal
|
||||
if utiljson.Unmarshal(singleFieldBytes, testObjectMeta) == nil {
|
||||
// if that succeeds, unmarshal for real
|
||||
if opts.ReturnUnknownFieldPaths {
|
||||
strictErrs, _ := kjson.UnmarshalStrict(singleFieldBytes, accumulatedObjectMeta)
|
||||
if len(strictErrs) > 0 {
|
||||
prefix := opts.ParentPath.Child("metadata").String()
|
||||
for _, err := range strictErrs {
|
||||
if fieldPathErr, ok := err.(kjson.FieldError); ok {
|
||||
unknownFields = append(unknownFields, prefix+"."+fieldPathErr.FieldPath())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
utiljson.Unmarshal(singleFieldBytes, accumulatedObjectMeta)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accumulatedObjectMeta, true, unknownFields, nil
|
||||
}
|
||||
|
||||
// SetObjectMeta writes back ObjectMeta into a JSON data structure.
|
||||
func SetObjectMeta(obj map[string]interface{}, objectMeta *metav1.ObjectMeta) error {
|
||||
if objectMeta == nil {
|
||||
unstructured.RemoveNestedField(obj, "metadata")
|
||||
return nil
|
||||
}
|
||||
|
||||
metadata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(objectMeta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj["metadata"] = metadata
|
||||
return nil
|
||||
}
|
||||
121
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/validation.go
generated
vendored
Normal file
121
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/validation.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
Copyright 2019 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 objectmeta
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
metavalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
"k8s.io/apimachinery/pkg/api/validation/path"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// Validate validates embedded ObjectMeta and TypeMeta.
|
||||
// It also validate those at the root if isResourceRoot is true.
|
||||
func Validate(pth *field.Path, obj interface{}, s *structuralschema.Structural, isResourceRoot bool) field.ErrorList {
|
||||
if isResourceRoot {
|
||||
if s == nil {
|
||||
s = &structuralschema.Structural{}
|
||||
}
|
||||
if !s.XEmbeddedResource {
|
||||
clone := *s
|
||||
clone.XEmbeddedResource = true
|
||||
s = &clone
|
||||
}
|
||||
}
|
||||
return validate(pth, obj, s)
|
||||
}
|
||||
|
||||
func validate(pth *field.Path, x interface{}, s *structuralschema.Structural) field.ErrorList {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var allErrs field.ErrorList
|
||||
|
||||
switch x := x.(type) {
|
||||
case map[string]interface{}:
|
||||
if s.XEmbeddedResource {
|
||||
allErrs = append(allErrs, validateEmbeddedResource(pth, x, s)...)
|
||||
}
|
||||
|
||||
for k, v := range x {
|
||||
prop, ok := s.Properties[k]
|
||||
if ok {
|
||||
allErrs = append(allErrs, validate(pth.Child(k), v, &prop)...)
|
||||
} else if s.AdditionalProperties != nil {
|
||||
allErrs = append(allErrs, validate(pth.Key(k), v, s.AdditionalProperties.Structural)...)
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for i, v := range x {
|
||||
allErrs = append(allErrs, validate(pth.Index(i), v, s.Items)...)
|
||||
}
|
||||
default:
|
||||
// scalars, do nothing
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateEmbeddedResource(pth *field.Path, x map[string]interface{}, s *structuralschema.Structural) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
|
||||
// require apiVersion and kind, but not metadata
|
||||
if _, found := x["apiVersion"]; !found {
|
||||
allErrs = append(allErrs, field.Required(pth.Child("apiVersion"), "must not be empty"))
|
||||
}
|
||||
if _, found := x["kind"]; !found {
|
||||
allErrs = append(allErrs, field.Required(pth.Child("kind"), "must not be empty"))
|
||||
}
|
||||
|
||||
for k, v := range x {
|
||||
switch k {
|
||||
case "apiVersion":
|
||||
if apiVersion, ok := v.(string); !ok {
|
||||
allErrs = append(allErrs, field.Invalid(pth.Child("apiVersion"), v, "must be a string"))
|
||||
} else if len(apiVersion) == 0 {
|
||||
allErrs = append(allErrs, field.Invalid(pth.Child("apiVersion"), apiVersion, "must not be empty"))
|
||||
} else if _, err := schema.ParseGroupVersion(apiVersion); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(pth.Child("apiVersion"), apiVersion, err.Error()))
|
||||
}
|
||||
case "kind":
|
||||
if kind, ok := v.(string); !ok {
|
||||
allErrs = append(allErrs, field.Invalid(pth.Child("kind"), v, "must be a string"))
|
||||
} else if len(kind) == 0 {
|
||||
allErrs = append(allErrs, field.Invalid(pth.Child("kind"), kind, "must not be empty"))
|
||||
} else if errs := utilvalidation.IsDNS1035Label(strings.ToLower(kind)); len(errs) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(pth.Child("kind"), kind, "may have mixed case, but should otherwise match: "+strings.Join(errs, ",")))
|
||||
}
|
||||
case "metadata":
|
||||
meta, _, err := GetObjectMeta(x, false)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(pth.Child("metadata"), v, err.Error()))
|
||||
} else {
|
||||
if len(meta.Name) == 0 {
|
||||
meta.Name = "fakename" // we have to set something to avoid an error
|
||||
}
|
||||
allErrs = append(allErrs, metavalidation.ValidateObjectMeta(meta, len(meta.Namespace) > 0, path.ValidatePathSegmentName, pth.Child("metadata"))...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
67
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/options.go
generated
vendored
Normal file
67
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/options.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2022 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 schema
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UnknownFieldPathOptions allow for tracking paths to unknown fields.
|
||||
type UnknownFieldPathOptions struct {
|
||||
// TrackUnknownFieldPaths determines whether or not unknown field
|
||||
// paths should be stored or not.
|
||||
TrackUnknownFieldPaths bool
|
||||
// ParentPath builds the path to unknown fields as the object
|
||||
// is recursively traversed.
|
||||
ParentPath []string
|
||||
// UnknownFieldPaths is the list of all unknown fields identified.
|
||||
UnknownFieldPaths []string
|
||||
}
|
||||
|
||||
// RecordUnknownFields adds a path to an unknown field to the
|
||||
// record of UnknownFieldPaths, if TrackUnknownFieldPaths is true
|
||||
func (o *UnknownFieldPathOptions) RecordUnknownField(field string) {
|
||||
if !o.TrackUnknownFieldPaths {
|
||||
return
|
||||
}
|
||||
l := len(o.ParentPath)
|
||||
o.AppendKey(field)
|
||||
o.UnknownFieldPaths = append(o.UnknownFieldPaths, strings.Join(o.ParentPath, ""))
|
||||
o.ParentPath = o.ParentPath[:l]
|
||||
}
|
||||
|
||||
// AppendKey adds a key (i.e. field) to the current parent
|
||||
// path, if TrackUnknownFieldPaths is true.
|
||||
func (o *UnknownFieldPathOptions) AppendKey(key string) {
|
||||
if !o.TrackUnknownFieldPaths {
|
||||
return
|
||||
}
|
||||
if len(o.ParentPath) > 0 {
|
||||
o.ParentPath = append(o.ParentPath, ".")
|
||||
}
|
||||
o.ParentPath = append(o.ParentPath, key)
|
||||
}
|
||||
|
||||
// AppendIndex adds an index to the most recent field of
|
||||
// the current parent path, if TrackUnknownFieldPaths is true.
|
||||
func (o *UnknownFieldPathOptions) AppendIndex(index int) {
|
||||
if !o.TrackUnknownFieldPaths {
|
||||
return
|
||||
}
|
||||
o.ParentPath = append(o.ParentPath, "[", strconv.Itoa(index), "]")
|
||||
}
|
||||
149
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning/algorithm.go
generated
vendored
Normal file
149
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning/algorithm.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
Copyright 2019 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 pruning
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
)
|
||||
|
||||
// PruneWithOptions removes object fields in obj which are not specified in s. It skips TypeMeta
|
||||
// and ObjectMeta fields if XEmbeddedResource is set to true, or for the root if isResourceRoot=true,
|
||||
// i.e. it does not prune unknown metadata fields.
|
||||
// It returns the set of fields that it prunes if opts.TrackUnknownFieldPaths is true
|
||||
func PruneWithOptions(obj interface{}, s *structuralschema.Structural, isResourceRoot bool, opts structuralschema.UnknownFieldPathOptions) []string {
|
||||
if isResourceRoot {
|
||||
if s == nil {
|
||||
s = &structuralschema.Structural{}
|
||||
}
|
||||
if !s.XEmbeddedResource {
|
||||
clone := *s
|
||||
clone.XEmbeddedResource = true
|
||||
s = &clone
|
||||
}
|
||||
}
|
||||
prune(obj, s, &opts)
|
||||
sort.Strings(opts.UnknownFieldPaths)
|
||||
return opts.UnknownFieldPaths
|
||||
}
|
||||
|
||||
// Prune is equivalent to
|
||||
// PruneWithOptions(obj, s, isResourceRoot, structuralschema.UnknownFieldPathOptions{})
|
||||
func Prune(obj interface{}, s *structuralschema.Structural, isResourceRoot bool) {
|
||||
PruneWithOptions(obj, s, isResourceRoot, structuralschema.UnknownFieldPathOptions{})
|
||||
}
|
||||
|
||||
var metaFields = map[string]bool{
|
||||
"apiVersion": true,
|
||||
"kind": true,
|
||||
"metadata": true,
|
||||
}
|
||||
|
||||
func prune(x interface{}, s *structuralschema.Structural, opts *structuralschema.UnknownFieldPathOptions) {
|
||||
if s != nil && s.XPreserveUnknownFields {
|
||||
skipPrune(x, s, opts)
|
||||
return
|
||||
}
|
||||
|
||||
origPathLen := len(opts.ParentPath)
|
||||
defer func() {
|
||||
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||
}()
|
||||
switch x := x.(type) {
|
||||
case map[string]interface{}:
|
||||
if s == nil {
|
||||
for k := range x {
|
||||
opts.RecordUnknownField(k)
|
||||
delete(x, k)
|
||||
}
|
||||
return
|
||||
}
|
||||
for k, v := range x {
|
||||
if s.XEmbeddedResource && metaFields[k] {
|
||||
continue
|
||||
}
|
||||
prop, ok := s.Properties[k]
|
||||
if ok {
|
||||
opts.AppendKey(k)
|
||||
prune(v, &prop, opts)
|
||||
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||
} else if s.AdditionalProperties != nil {
|
||||
opts.AppendKey(k)
|
||||
prune(v, s.AdditionalProperties.Structural, opts)
|
||||
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||
} else {
|
||||
if !metaFields[k] || len(opts.ParentPath) > 0 {
|
||||
opts.RecordUnknownField(k)
|
||||
}
|
||||
delete(x, k)
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
if s == nil {
|
||||
for i, v := range x {
|
||||
opts.AppendIndex(i)
|
||||
prune(v, nil, opts)
|
||||
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||
}
|
||||
return
|
||||
}
|
||||
for i, v := range x {
|
||||
opts.AppendIndex(i)
|
||||
prune(v, s.Items, opts)
|
||||
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||
}
|
||||
default:
|
||||
// scalars, do nothing
|
||||
}
|
||||
}
|
||||
|
||||
func skipPrune(x interface{}, s *structuralschema.Structural, opts *structuralschema.UnknownFieldPathOptions) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
origPathLen := len(opts.ParentPath)
|
||||
defer func() {
|
||||
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||
}()
|
||||
|
||||
switch x := x.(type) {
|
||||
case map[string]interface{}:
|
||||
for k, v := range x {
|
||||
if s.XEmbeddedResource && metaFields[k] {
|
||||
continue
|
||||
}
|
||||
if prop, ok := s.Properties[k]; ok {
|
||||
opts.AppendKey(k)
|
||||
prune(v, &prop, opts)
|
||||
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||
} else if s.AdditionalProperties != nil {
|
||||
opts.AppendKey(k)
|
||||
prune(v, s.AdditionalProperties.Structural, opts)
|
||||
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for i, v := range x {
|
||||
opts.AppendIndex(i)
|
||||
skipPrune(v, s.Items, opts)
|
||||
opts.ParentPath = opts.ParentPath[:origPathLen]
|
||||
}
|
||||
default:
|
||||
// scalars, do nothing
|
||||
}
|
||||
}
|
||||
48
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/skeleton.go
generated
vendored
Normal file
48
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/skeleton.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 2019 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 schema
|
||||
|
||||
// StripValueValidations returns a copy without value validations.
|
||||
func (s *Structural) StripValueValidations() *Structural {
|
||||
s = s.DeepCopy()
|
||||
v := Visitor{
|
||||
Structural: func(s *Structural) bool {
|
||||
changed := false
|
||||
if s.ValueValidation != nil {
|
||||
s.ValueValidation = nil
|
||||
changed = true
|
||||
}
|
||||
return changed
|
||||
},
|
||||
}
|
||||
v.Visit(s)
|
||||
return s
|
||||
}
|
||||
|
||||
// StripNullable returns a copy without nullable.
|
||||
func (s *Structural) StripNullable() *Structural {
|
||||
s = s.DeepCopy()
|
||||
v := Visitor{
|
||||
Structural: func(s *Structural) bool {
|
||||
changed := s.Nullable
|
||||
s.Nullable = false
|
||||
return changed
|
||||
},
|
||||
}
|
||||
v.Visit(s)
|
||||
return s
|
||||
}
|
||||
202
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/structural.go
generated
vendored
Normal file
202
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/structural.go
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
Copyright 2019 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 schema
|
||||
|
||||
import (
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// Structural represents a structural schema.
|
||||
type Structural struct {
|
||||
Items *Structural
|
||||
Properties map[string]Structural
|
||||
|
||||
Generic
|
||||
Extensions
|
||||
|
||||
ValueValidation *ValueValidation
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// StructuralOrBool is either a structural schema or a boolean.
|
||||
type StructuralOrBool struct {
|
||||
Structural *Structural
|
||||
Bool bool
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// Generic contains the generic schema fields not allowed in value validation.
|
||||
type Generic struct {
|
||||
Description string
|
||||
// type specifies the type of a value.
|
||||
// It can be object, array, number, integer, boolean, string.
|
||||
// It is optional only if x-kubernetes-preserve-unknown-fields
|
||||
// or x-kubernetes-int-or-string is true.
|
||||
Type string
|
||||
Title string
|
||||
Default JSON
|
||||
AdditionalProperties *StructuralOrBool
|
||||
Nullable bool
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// Extensions contains the Kubernetes OpenAPI v3 vendor extensions.
|
||||
type Extensions struct {
|
||||
// x-kubernetes-preserve-unknown-fields stops the API server
|
||||
// decoding step from pruning fields which are not specified
|
||||
// in the validation schema. This affects fields recursively,
|
||||
// but switches back to normal pruning behaviour if nested
|
||||
// properties or additionalProperties are specified in the schema.
|
||||
// False means that the pruning behaviour is inherited from the parent.
|
||||
// False does not mean to activate pruning.
|
||||
XPreserveUnknownFields bool
|
||||
|
||||
// x-kubernetes-embedded-resource defines that the value is an
|
||||
// embedded Kubernetes runtime.Object, with TypeMeta and
|
||||
// ObjectMeta. The type must be object. It is allowed to further
|
||||
// restrict the embedded object. Both ObjectMeta and TypeMeta
|
||||
// are validated automatically. x-kubernetes-preserve-unknown-fields
|
||||
// must be true.
|
||||
XEmbeddedResource bool
|
||||
|
||||
// x-kubernetes-int-or-string specifies that this value is
|
||||
// either an integer or a string. If this is true, an empty
|
||||
// type is allowed and type as child of anyOf is permitted
|
||||
// if following one of the following patterns:
|
||||
//
|
||||
// 1) anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// 2) allOf:
|
||||
// - anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// - ... zero or more
|
||||
XIntOrString bool
|
||||
|
||||
// x-kubernetes-list-map-keys annotates lists with the x-kubernetes-list-type `map` by specifying the keys used
|
||||
// as the index of the map.
|
||||
//
|
||||
// This tag MUST only be used on lists that have the "x-kubernetes-list-type"
|
||||
// extension set to "map". Also, the values specified for this attribute must
|
||||
// be a scalar typed field of the child structure (no nesting is supported).
|
||||
XListMapKeys []string
|
||||
|
||||
// x-kubernetes-list-type annotates a list to further describe its topology.
|
||||
// This extension must only be used on lists and may have 3 possible values:
|
||||
//
|
||||
// 1) `atomic`: the list is treated as a single entity, like a scalar.
|
||||
// Atomic lists will be entirely replaced when updated. This extension
|
||||
// may be used on any type of list (struct, scalar, ...).
|
||||
// 2) `set`:
|
||||
// Sets are lists that must not have multiple items with the same value. Each
|
||||
// value must be a scalar (or another atomic type).
|
||||
// 3) `map`:
|
||||
// These lists are like maps in that their elements have a non-index key
|
||||
// used to identify them. Order is preserved upon merge. The map tag
|
||||
// must only be used on a list with elements of type object.
|
||||
XListType *string
|
||||
|
||||
// x-kubernetes-map-type annotates an object to further describe its topology.
|
||||
// This extension must only be used when type is object and may have 2 possible values:
|
||||
//
|
||||
// 1) `granular`:
|
||||
// These maps are actual maps (key-value pairs) and each fields are independent
|
||||
// from each other (they can each be manipulated by separate actors). This is
|
||||
// the default behaviour for all maps.
|
||||
// 2) `atomic`: the list is treated as a single entity, like a scalar.
|
||||
// Atomic maps will be entirely replaced when updated.
|
||||
// +optional
|
||||
XMapType *string
|
||||
|
||||
// x-kubernetes-validations describes a list of validation rules for expression validation.
|
||||
// Use the v1 struct since this gets serialized as an extension.
|
||||
XValidations apiextensionsv1.ValidationRules
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// ValueValidation contains all schema fields not contributing to the structure of the schema.
|
||||
type ValueValidation struct {
|
||||
Format string
|
||||
Maximum *float64
|
||||
ExclusiveMaximum bool
|
||||
Minimum *float64
|
||||
ExclusiveMinimum bool
|
||||
MaxLength *int64
|
||||
MinLength *int64
|
||||
Pattern string
|
||||
MaxItems *int64
|
||||
MinItems *int64
|
||||
UniqueItems bool
|
||||
MultipleOf *float64
|
||||
Enum []JSON
|
||||
MaxProperties *int64
|
||||
MinProperties *int64
|
||||
Required []string
|
||||
AllOf []NestedValueValidation
|
||||
OneOf []NestedValueValidation
|
||||
AnyOf []NestedValueValidation
|
||||
Not *NestedValueValidation
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// NestedValueValidation contains value validations, items and properties usable when nested
|
||||
// under a logical junctor, and catch all structs for generic and vendor extensions schema fields.
|
||||
type NestedValueValidation struct {
|
||||
ValueValidation
|
||||
|
||||
Items *NestedValueValidation
|
||||
Properties map[string]NestedValueValidation
|
||||
|
||||
// Anything set in the following will make the scheme
|
||||
// non-structural, with the exception of these two patterns if
|
||||
// x-kubernetes-int-or-string is true:
|
||||
//
|
||||
// 1) anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// 2) allOf:
|
||||
// - anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// - ... zero or more
|
||||
ForbiddenGenerics Generic
|
||||
ForbiddenExtensions Extensions
|
||||
}
|
||||
|
||||
// JSON wraps an arbitrary JSON value to be able to implement deepcopy.
|
||||
type JSON struct {
|
||||
Object interface{}
|
||||
}
|
||||
|
||||
// DeepCopy creates a deep copy of the wrapped JSON value.
|
||||
func (j JSON) DeepCopy() JSON {
|
||||
return JSON{runtime.DeepCopyJSONValue(j.Object)}
|
||||
}
|
||||
|
||||
// DeepCopyInto creates a deep copy of the wrapped JSON value and stores it in into.
|
||||
func (j JSON) DeepCopyInto(into *JSON) {
|
||||
into.Object = runtime.DeepCopyJSONValue(j.Object)
|
||||
}
|
||||
66
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/unfold.go
generated
vendored
Normal file
66
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/unfold.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2019 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 schema
|
||||
|
||||
// Unfold expands vendor extensions of a structural schema.
|
||||
// It mutates the receiver.
|
||||
func (s *Structural) Unfold() *Structural {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mapper := Visitor{
|
||||
Structural: func(s *Structural) bool {
|
||||
if !s.XIntOrString {
|
||||
return false
|
||||
}
|
||||
|
||||
skipAnyOf := isIntOrStringAnyOfPattern(s)
|
||||
skipFirstAllOfAnyOf := isIntOrStringAllOfPattern(s)
|
||||
if skipAnyOf || skipFirstAllOfAnyOf {
|
||||
return false
|
||||
}
|
||||
|
||||
if s.ValueValidation == nil {
|
||||
s.ValueValidation = &ValueValidation{}
|
||||
}
|
||||
if s.ValueValidation.AnyOf == nil {
|
||||
s.ValueValidation.AnyOf = []NestedValueValidation{
|
||||
{ForbiddenGenerics: Generic{Type: "integer"}},
|
||||
{ForbiddenGenerics: Generic{Type: "string"}},
|
||||
}
|
||||
} else {
|
||||
s.ValueValidation.AllOf = append([]NestedValueValidation{
|
||||
{
|
||||
ValueValidation: ValueValidation{
|
||||
AnyOf: []NestedValueValidation{
|
||||
{ForbiddenGenerics: Generic{Type: "integer"}},
|
||||
{ForbiddenGenerics: Generic{Type: "string"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, s.ValueValidation.AllOf...)
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
NestedValueValidation: nil, // x-kubernetes-int-or-string cannot be set in nested value validation
|
||||
}
|
||||
mapper.Visit(s)
|
||||
|
||||
return s
|
||||
}
|
||||
337
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation.go
generated
vendored
Normal file
337
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation.go
generated
vendored
Normal file
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
Copyright 2019 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 schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
var intOrStringAnyOf = []NestedValueValidation{
|
||||
{ForbiddenGenerics: Generic{
|
||||
Type: "integer",
|
||||
}},
|
||||
{ForbiddenGenerics: Generic{
|
||||
Type: "string",
|
||||
}},
|
||||
}
|
||||
|
||||
type level int
|
||||
|
||||
const (
|
||||
rootLevel level = iota
|
||||
itemLevel
|
||||
fieldLevel
|
||||
)
|
||||
|
||||
// ValidateStructural checks that s is a structural schema with the invariants:
|
||||
//
|
||||
// * structurality: both `ForbiddenGenerics` and `ForbiddenExtensions` only have zero values, with the two exceptions for IntOrString.
|
||||
// * RawExtension: for every schema with `x-kubernetes-embedded-resource: true`, `x-kubernetes-preserve-unknown-fields: true` and `type: object` are set
|
||||
// * IntOrString: for `x-kubernetes-int-or-string: true` either `type` is empty under `anyOf` and `allOf` or the schema structure is one of these:
|
||||
//
|
||||
// 1. anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// 2. allOf:
|
||||
// - anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// - ... zero or more
|
||||
//
|
||||
// * every specified field or array in s is also specified outside of value validation.
|
||||
// * metadata at the root can only restrict the name and generateName, and not be specified at all in nested contexts.
|
||||
// * additionalProperties at the root is not allowed.
|
||||
func ValidateStructural(fldPath *field.Path, s *Structural) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
allErrs = append(allErrs, validateStructuralInvariants(s, rootLevel, fldPath)...)
|
||||
allErrs = append(allErrs, validateStructuralCompleteness(s, fldPath)...)
|
||||
|
||||
// sort error messages. Otherwise, the errors slice will change every time due to
|
||||
// maps in the types and randomized iteration.
|
||||
sort.Slice(allErrs, func(i, j int) bool {
|
||||
return allErrs[i].Error() < allErrs[j].Error()
|
||||
})
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateStructuralInvariants checks the invariants of a structural schema.
|
||||
func validateStructuralInvariants(s *Structural, lvl level, fldPath *field.Path) field.ErrorList {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if s.Type == "array" && s.Items == nil {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("items"), "must be specified"))
|
||||
}
|
||||
allErrs = append(allErrs, validateStructuralInvariants(s.Items, itemLevel, fldPath.Child("items"))...)
|
||||
|
||||
for k, v := range s.Properties {
|
||||
allErrs = append(allErrs, validateStructuralInvariants(&v, fieldLevel, fldPath.Child("properties").Key(k))...)
|
||||
}
|
||||
allErrs = append(allErrs, validateGeneric(&s.Generic, lvl, fldPath)...)
|
||||
allErrs = append(allErrs, validateExtensions(&s.Extensions, fldPath)...)
|
||||
|
||||
// detect the two IntOrString exceptions:
|
||||
// 1) anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// 2) allOf:
|
||||
// - anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// - ... zero or more
|
||||
skipAnyOf := isIntOrStringAnyOfPattern(s)
|
||||
skipFirstAllOfAnyOf := isIntOrStringAllOfPattern(s)
|
||||
|
||||
allErrs = append(allErrs, validateValueValidation(s.ValueValidation, skipAnyOf, skipFirstAllOfAnyOf, lvl, fldPath)...)
|
||||
|
||||
checkMetadata := (lvl == rootLevel) || s.XEmbeddedResource
|
||||
|
||||
if s.XEmbeddedResource && s.Type != "object" {
|
||||
if len(s.Type) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must be object if x-kubernetes-embedded-resource is true"))
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), s.Type, "must be object if x-kubernetes-embedded-resource is true"))
|
||||
}
|
||||
} else if len(s.Type) == 0 && !s.Extensions.XIntOrString && !s.Extensions.XPreserveUnknownFields {
|
||||
switch lvl {
|
||||
case rootLevel:
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty at the root"))
|
||||
case itemLevel:
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty for specified array items"))
|
||||
case fieldLevel:
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty for specified object fields"))
|
||||
}
|
||||
}
|
||||
if s.XEmbeddedResource && s.AdditionalProperties != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must not be used if x-kubernetes-embedded-resource is set"))
|
||||
}
|
||||
|
||||
if lvl == rootLevel && len(s.Type) > 0 && s.Type != "object" {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), s.Type, "must be object at the root"))
|
||||
}
|
||||
|
||||
// restrict metadata schemas to name and generateName only
|
||||
if kind, found := s.Properties["kind"]; found && checkMetadata {
|
||||
if kind.Type != "string" {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("properties").Key("kind").Child("type"), kind.Type, "must be string"))
|
||||
}
|
||||
}
|
||||
if apiVersion, found := s.Properties["apiVersion"]; found && checkMetadata {
|
||||
if apiVersion.Type != "string" {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("properties").Key("apiVersion").Child("type"), apiVersion.Type, "must be string"))
|
||||
}
|
||||
}
|
||||
|
||||
if metadata, found := s.Properties["metadata"]; found {
|
||||
allErrs = append(allErrs, validateStructuralMetadataInvariants(&metadata, checkMetadata, lvl, fldPath.Child("properties").Key("metadata"))...)
|
||||
}
|
||||
|
||||
if s.XEmbeddedResource && !s.XPreserveUnknownFields && len(s.Properties) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("properties"), "must not be empty if x-kubernetes-embedded-resource is true without x-kubernetes-preserve-unknown-fields"))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateStructuralMetadataInvariants(s *Structural, checkMetadata bool, lvl level, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if checkMetadata && s.Type != "object" {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), s.Type, "must be object"))
|
||||
}
|
||||
|
||||
if lvl == rootLevel {
|
||||
// metadata is a shallow copy. We can mutate it.
|
||||
_, foundName := s.Properties["name"]
|
||||
_, foundGenerateName := s.Properties["generateName"]
|
||||
if foundName && foundGenerateName && len(s.Properties) == 2 {
|
||||
s.Properties = nil
|
||||
} else if (foundName || foundGenerateName) && len(s.Properties) == 1 {
|
||||
s.Properties = nil
|
||||
}
|
||||
s.Type = ""
|
||||
s.Default.Object = nil // this is checked in API validation (and also tested)
|
||||
if s.ValueValidation == nil {
|
||||
s.ValueValidation = &ValueValidation{}
|
||||
}
|
||||
if !reflect.DeepEqual(*s, Structural{ValueValidation: &ValueValidation{}}) {
|
||||
// TODO: this is actually a field.Invalid error, but we cannot do JSON serialization of metadata here to get a proper message
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath, "must not specify anything other than name and generateName, but metadata is implicitly specified"))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func isIntOrStringAnyOfPattern(s *Structural) bool {
|
||||
if s == nil || s.ValueValidation == nil {
|
||||
return false
|
||||
}
|
||||
return len(s.ValueValidation.AnyOf) == 2 && reflect.DeepEqual(s.ValueValidation.AnyOf, intOrStringAnyOf)
|
||||
}
|
||||
|
||||
func isIntOrStringAllOfPattern(s *Structural) bool {
|
||||
if s == nil || s.ValueValidation == nil {
|
||||
return false
|
||||
}
|
||||
return len(s.ValueValidation.AllOf) >= 1 && len(s.ValueValidation.AllOf[0].AnyOf) == 2 && reflect.DeepEqual(s.ValueValidation.AllOf[0].AnyOf, intOrStringAnyOf)
|
||||
}
|
||||
|
||||
// validateGeneric checks the generic fields of a structural schema.
|
||||
func validateGeneric(g *Generic, lvl level, fldPath *field.Path) field.ErrorList {
|
||||
if g == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if g.AdditionalProperties != nil {
|
||||
if lvl == rootLevel {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must not be used at the root"))
|
||||
}
|
||||
if g.AdditionalProperties.Structural != nil {
|
||||
allErrs = append(allErrs, validateStructuralInvariants(g.AdditionalProperties.Structural, fieldLevel, fldPath.Child("additionalProperties"))...)
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateExtensions checks Kubernetes vendor extensions of a structural schema.
|
||||
func validateExtensions(x *Extensions, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if x.XIntOrString && x.XPreserveUnknownFields {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("x-kubernetes-preserve-unknown-fields"), x.XPreserveUnknownFields, "must be false if x-kubernetes-int-or-string is true"))
|
||||
}
|
||||
if x.XIntOrString && x.XEmbeddedResource {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("x-kubernetes-embedded-resource"), x.XEmbeddedResource, "must be false if x-kubernetes-int-or-string is true"))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateValueValidation checks the value validation in a structural schema.
|
||||
func validateValueValidation(v *ValueValidation, skipAnyOf, skipFirstAllOfAnyOf bool, lvl level, fldPath *field.Path) field.ErrorList {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if !skipAnyOf {
|
||||
for i := range v.AnyOf {
|
||||
allErrs = append(allErrs, validateNestedValueValidation(&v.AnyOf[i], false, false, lvl, fldPath.Child("anyOf").Index(i))...)
|
||||
}
|
||||
}
|
||||
|
||||
for i := range v.AllOf {
|
||||
skipAnyOf := false
|
||||
if skipFirstAllOfAnyOf && i == 0 {
|
||||
skipAnyOf = true
|
||||
}
|
||||
allErrs = append(allErrs, validateNestedValueValidation(&v.AllOf[i], skipAnyOf, false, lvl, fldPath.Child("allOf").Index(i))...)
|
||||
}
|
||||
|
||||
for i := range v.OneOf {
|
||||
allErrs = append(allErrs, validateNestedValueValidation(&v.OneOf[i], false, false, lvl, fldPath.Child("oneOf").Index(i))...)
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, validateNestedValueValidation(v.Not, false, false, lvl, fldPath.Child("not"))...)
|
||||
|
||||
if len(v.Pattern) > 0 {
|
||||
if _, err := regexp.Compile(v.Pattern); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("pattern"), v.Pattern, fmt.Sprintf("must be a valid regular expression, but isn't: %v", err)))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateNestedValueValidation checks the nested value validation under a logic junctor in a structural schema.
|
||||
func validateNestedValueValidation(v *NestedValueValidation, skipAnyOf, skipAllOfAnyOf bool, lvl level, fldPath *field.Path) field.ErrorList {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
allErrs = append(allErrs, validateValueValidation(&v.ValueValidation, skipAnyOf, skipAllOfAnyOf, lvl, fldPath)...)
|
||||
allErrs = append(allErrs, validateNestedValueValidation(v.Items, false, false, lvl, fldPath.Child("items"))...)
|
||||
|
||||
for k, fld := range v.Properties {
|
||||
allErrs = append(allErrs, validateNestedValueValidation(&fld, false, false, fieldLevel, fldPath.Child("properties").Key(k))...)
|
||||
}
|
||||
|
||||
if len(v.ForbiddenGenerics.Type) > 0 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("type"), "must be empty to be structural"))
|
||||
}
|
||||
if v.ForbiddenGenerics.AdditionalProperties != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must be undefined to be structural"))
|
||||
}
|
||||
if v.ForbiddenGenerics.Default.Object != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("default"), "must be undefined to be structural"))
|
||||
}
|
||||
if len(v.ForbiddenGenerics.Title) > 0 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("title"), "must be empty to be structural"))
|
||||
}
|
||||
if len(v.ForbiddenGenerics.Description) > 0 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("description"), "must be empty to be structural"))
|
||||
}
|
||||
if v.ForbiddenGenerics.Nullable {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("nullable"), "must be false to be structural"))
|
||||
}
|
||||
|
||||
if v.ForbiddenExtensions.XPreserveUnknownFields {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-preserve-unknown-fields"), "must be false to be structural"))
|
||||
}
|
||||
if v.ForbiddenExtensions.XEmbeddedResource {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-embedded-resource"), "must be false to be structural"))
|
||||
}
|
||||
if v.ForbiddenExtensions.XIntOrString {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-int-or-string"), "must be false to be structural"))
|
||||
}
|
||||
if len(v.ForbiddenExtensions.XListMapKeys) > 0 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-list-map-keys"), "must be empty to be structural"))
|
||||
}
|
||||
if v.ForbiddenExtensions.XListType != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-list-type"), "must be undefined to be structural"))
|
||||
}
|
||||
if v.ForbiddenExtensions.XMapType != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-map-type"), "must be undefined to be structural"))
|
||||
}
|
||||
if len(v.ForbiddenExtensions.XValidations) > 0 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-validations"), "must be empty to be structural"))
|
||||
}
|
||||
|
||||
// forbid reasoning about metadata because it can lead to metadata restriction we don't want
|
||||
if _, found := v.Properties["metadata"]; found {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("properties").Key("metadata"), "must not be specified in a nested context"))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
106
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/visitor.go
generated
vendored
Normal file
106
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/visitor.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
Copyright 2019 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 schema
|
||||
|
||||
// Visitor recursively walks through a structural schema.
|
||||
type Visitor struct {
|
||||
// Structural is called on each Structural node in the schema, before recursing into
|
||||
// the subtrees. It is allowed to mutate s. Return true if something has been changed.
|
||||
// +optional
|
||||
Structural func(s *Structural) bool
|
||||
// NestedValueValidation is called on each NestedValueValidation node in the schema,
|
||||
// before recursing into subtrees. It is allowed to mutate vv. Return true if something
|
||||
// has been changed.
|
||||
// +optional
|
||||
NestedValueValidation func(vv *NestedValueValidation) bool
|
||||
}
|
||||
|
||||
// Visit recursively walks through the structural schema and calls the given callbacks
|
||||
// at each node of those types.
|
||||
func (m *Visitor) Visit(s *Structural) {
|
||||
m.visitStructural(s)
|
||||
}
|
||||
|
||||
func (m *Visitor) visitStructural(s *Structural) bool {
|
||||
ret := false
|
||||
if m.Structural != nil {
|
||||
ret = m.Structural(s)
|
||||
}
|
||||
|
||||
if s.Items != nil {
|
||||
m.visitStructural(s.Items)
|
||||
}
|
||||
for k, v := range s.Properties {
|
||||
if changed := m.visitStructural(&v); changed {
|
||||
ret = true
|
||||
s.Properties[k] = v
|
||||
}
|
||||
}
|
||||
if s.Generic.AdditionalProperties != nil && s.Generic.AdditionalProperties.Structural != nil {
|
||||
m.visitStructural(s.Generic.AdditionalProperties.Structural)
|
||||
}
|
||||
if s.ValueValidation != nil {
|
||||
for i := range s.ValueValidation.AllOf {
|
||||
m.visitNestedValueValidation(&s.ValueValidation.AllOf[i])
|
||||
}
|
||||
for i := range s.ValueValidation.AnyOf {
|
||||
m.visitNestedValueValidation(&s.ValueValidation.AnyOf[i])
|
||||
}
|
||||
for i := range s.ValueValidation.OneOf {
|
||||
m.visitNestedValueValidation(&s.ValueValidation.OneOf[i])
|
||||
}
|
||||
if s.ValueValidation.Not != nil {
|
||||
m.visitNestedValueValidation(s.ValueValidation.Not)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (m *Visitor) visitNestedValueValidation(vv *NestedValueValidation) bool {
|
||||
ret := false
|
||||
if m.NestedValueValidation != nil {
|
||||
ret = m.NestedValueValidation(vv)
|
||||
}
|
||||
|
||||
if vv.Items != nil {
|
||||
m.visitNestedValueValidation(vv.Items)
|
||||
}
|
||||
for k, v := range vv.Properties {
|
||||
if changed := m.visitNestedValueValidation(&v); changed {
|
||||
ret = true
|
||||
vv.Properties[k] = v
|
||||
}
|
||||
}
|
||||
if vv.ForbiddenGenerics.AdditionalProperties != nil && vv.ForbiddenGenerics.AdditionalProperties.Structural != nil {
|
||||
m.visitStructural(vv.ForbiddenGenerics.AdditionalProperties.Structural)
|
||||
}
|
||||
for i := range vv.ValueValidation.AllOf {
|
||||
m.visitNestedValueValidation(&vv.ValueValidation.AllOf[i])
|
||||
}
|
||||
for i := range vv.ValueValidation.AnyOf {
|
||||
m.visitNestedValueValidation(&vv.ValueValidation.AnyOf[i])
|
||||
}
|
||||
for i := range vv.ValueValidation.OneOf {
|
||||
m.visitNestedValueValidation(&vv.ValueValidation.OneOf[i])
|
||||
}
|
||||
if vv.ValueValidation.Not != nil {
|
||||
m.visitNestedValueValidation(vv.ValueValidation.Not)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
272
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/zz_generated.deepcopy.go
generated
vendored
Normal file
272
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/zz_generated.deepcopy.go
generated
vendored
Normal file
@@ -0,0 +1,272 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package schema
|
||||
|
||||
import (
|
||||
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Extensions) DeepCopyInto(out *Extensions) {
|
||||
*out = *in
|
||||
if in.XListMapKeys != nil {
|
||||
in, out := &in.XListMapKeys, &out.XListMapKeys
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.XListType != nil {
|
||||
in, out := &in.XListType, &out.XListType
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.XMapType != nil {
|
||||
in, out := &in.XMapType, &out.XMapType
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.XValidations != nil {
|
||||
in, out := &in.XValidations, &out.XValidations
|
||||
*out = make(v1.ValidationRules, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Extensions.
|
||||
func (in *Extensions) DeepCopy() *Extensions {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Extensions)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Generic) DeepCopyInto(out *Generic) {
|
||||
*out = *in
|
||||
out.Default = in.Default.DeepCopy()
|
||||
if in.AdditionalProperties != nil {
|
||||
in, out := &in.AdditionalProperties, &out.AdditionalProperties
|
||||
*out = new(StructuralOrBool)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Generic.
|
||||
func (in *Generic) DeepCopy() *Generic {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Generic)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NestedValueValidation) DeepCopyInto(out *NestedValueValidation) {
|
||||
*out = *in
|
||||
in.ValueValidation.DeepCopyInto(&out.ValueValidation)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = new(NestedValueValidation)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Properties != nil {
|
||||
in, out := &in.Properties, &out.Properties
|
||||
*out = make(map[string]NestedValueValidation, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
in.ForbiddenGenerics.DeepCopyInto(&out.ForbiddenGenerics)
|
||||
in.ForbiddenExtensions.DeepCopyInto(&out.ForbiddenExtensions)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NestedValueValidation.
|
||||
func (in *NestedValueValidation) DeepCopy() *NestedValueValidation {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NestedValueValidation)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Structural) DeepCopyInto(out *Structural) {
|
||||
*out = *in
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = new(Structural)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Properties != nil {
|
||||
in, out := &in.Properties, &out.Properties
|
||||
*out = make(map[string]Structural, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
in.Generic.DeepCopyInto(&out.Generic)
|
||||
in.Extensions.DeepCopyInto(&out.Extensions)
|
||||
if in.ValueValidation != nil {
|
||||
in, out := &in.ValueValidation, &out.ValueValidation
|
||||
*out = new(ValueValidation)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Structural.
|
||||
func (in *Structural) DeepCopy() *Structural {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Structural)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StructuralOrBool) DeepCopyInto(out *StructuralOrBool) {
|
||||
*out = *in
|
||||
if in.Structural != nil {
|
||||
in, out := &in.Structural, &out.Structural
|
||||
*out = new(Structural)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructuralOrBool.
|
||||
func (in *StructuralOrBool) DeepCopy() *StructuralOrBool {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StructuralOrBool)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ValueValidation) DeepCopyInto(out *ValueValidation) {
|
||||
*out = *in
|
||||
if in.Maximum != nil {
|
||||
in, out := &in.Maximum, &out.Maximum
|
||||
*out = new(float64)
|
||||
**out = **in
|
||||
}
|
||||
if in.Minimum != nil {
|
||||
in, out := &in.Minimum, &out.Minimum
|
||||
*out = new(float64)
|
||||
**out = **in
|
||||
}
|
||||
if in.MaxLength != nil {
|
||||
in, out := &in.MaxLength, &out.MaxLength
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
if in.MinLength != nil {
|
||||
in, out := &in.MinLength, &out.MinLength
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
if in.MaxItems != nil {
|
||||
in, out := &in.MaxItems, &out.MaxItems
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
if in.MinItems != nil {
|
||||
in, out := &in.MinItems, &out.MinItems
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
if in.MultipleOf != nil {
|
||||
in, out := &in.MultipleOf, &out.MultipleOf
|
||||
*out = new(float64)
|
||||
**out = **in
|
||||
}
|
||||
if in.Enum != nil {
|
||||
in, out := &in.Enum, &out.Enum
|
||||
*out = make([]JSON, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.MaxProperties != nil {
|
||||
in, out := &in.MaxProperties, &out.MaxProperties
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
if in.MinProperties != nil {
|
||||
in, out := &in.MinProperties, &out.MinProperties
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
if in.Required != nil {
|
||||
in, out := &in.Required, &out.Required
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.AllOf != nil {
|
||||
in, out := &in.AllOf, &out.AllOf
|
||||
*out = make([]NestedValueValidation, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.OneOf != nil {
|
||||
in, out := &in.OneOf, &out.OneOf
|
||||
*out = make([]NestedValueValidation, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.AnyOf != nil {
|
||||
in, out := &in.AnyOf, &out.AnyOf
|
||||
*out = make([]NestedValueValidation, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Not != nil {
|
||||
in, out := &in.Not, &out.Not
|
||||
*out = new(NestedValueValidation)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValueValidation.
|
||||
func (in *ValueValidation) DeepCopy() *ValueValidation {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ValueValidation)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user