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:
KubeSphere CI Bot
2024-09-06 11:05:52 +08:00
committed by GitHub
parent b5015ec7b9
commit 447a51f08b
8557 changed files with 546695 additions and 1146174 deletions

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

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

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

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

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