mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 14:07:14 +00:00
Merge pull request #129506 from JoelSpeed/fix-status-ratcheting
Fix CRD status subresource ratcheting
This commit is contained in:
commit
728a4d2a48
@ -22,11 +22,17 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
|
||||||
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
|
||||||
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
|
||||||
structurallisttype "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype"
|
structurallisttype "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype"
|
||||||
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
|
||||||
|
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
|
"k8s.io/apiserver/pkg/cel/common"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
)
|
)
|
||||||
|
|
||||||
type statusStrategy struct {
|
type statusStrategy struct {
|
||||||
@ -94,8 +100,17 @@ func (a statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Obj
|
|||||||
return field.ErrorList{field.Invalid(field.NewPath(""), old, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", old))}
|
return field.ErrorList{field.Invalid(field.NewPath(""), old, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", old))}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var options []validation.ValidationOption
|
||||||
|
var celOptions []cel.Option
|
||||||
|
var correlatedObject *common.CorrelatedObject
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CRDValidationRatcheting) {
|
||||||
|
correlatedObject = common.NewCorrelatedObject(uNew.Object, uOld.Object, &model.Structural{Structural: a.structuralSchema})
|
||||||
|
options = append(options, validation.WithRatcheting(correlatedObject.Key("status")))
|
||||||
|
celOptions = append(celOptions, cel.WithRatcheting(correlatedObject))
|
||||||
|
}
|
||||||
|
|
||||||
var errs field.ErrorList
|
var errs field.ErrorList
|
||||||
errs = append(errs, a.customResourceStrategy.validator.ValidateStatusUpdate(ctx, uNew, uOld, a.scale)...)
|
errs = append(errs, a.customResourceStrategy.validator.ValidateStatusUpdate(ctx, uNew, uOld, a.scale, options...)...)
|
||||||
|
|
||||||
// ratcheting validation of x-kubernetes-list-type value map and set
|
// ratcheting validation of x-kubernetes-list-type value map and set
|
||||||
if newErrs := structurallisttype.ValidateListSetsAndMaps(nil, a.structuralSchema, uNew.Object); len(newErrs) > 0 {
|
if newErrs := structurallisttype.ValidateListSetsAndMaps(nil, a.structuralSchema, uNew.Object); len(newErrs) > 0 {
|
||||||
@ -109,10 +124,15 @@ func (a statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Obj
|
|||||||
if has, err := hasBlockingErr(errs); has {
|
if has, err := hasBlockingErr(errs); has {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
} else {
|
} else {
|
||||||
err, _ := celValidator.Validate(ctx, nil, a.customResourceStrategy.structuralSchema, uNew.Object, uOld.Object, celconfig.RuntimeCELCostBudget)
|
err, _ := celValidator.Validate(ctx, nil, a.customResourceStrategy.structuralSchema, uNew.Object, uOld.Object, celconfig.RuntimeCELCostBudget, celOptions...)
|
||||||
errs = append(errs, err...)
|
errs = append(errs, err...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No-op if not attached to context
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CRDValidationRatcheting) {
|
||||||
|
validation.Metrics.ObserveRatchetingTime(*correlatedObject.Duration)
|
||||||
|
}
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ func validateKubeFinalizerName(stringValue string, fldPath *field.Path) []string
|
|||||||
return allWarnings
|
return allWarnings
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj, old *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj, old *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale, options ...apiextensionsvalidation.ValidationOption) field.ErrorList {
|
||||||
if errs := a.ValidateTypeMeta(ctx, obj); len(errs) > 0 {
|
if errs := a.ValidateTypeMeta(ctx, obj); len(errs) > 0 {
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
@ -105,7 +105,7 @@ func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj,
|
|||||||
|
|
||||||
allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(obj, old, field.NewPath("metadata"))...)
|
allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(obj, old, field.NewPath("metadata"))...)
|
||||||
if status, hasStatus := obj.UnstructuredContent()["status"]; hasStatus {
|
if status, hasStatus := obj.UnstructuredContent()["status"]; hasStatus {
|
||||||
allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(field.NewPath("status"), status, old.UnstructuredContent()["status"], a.statusSchemaValidator)...)
|
allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(field.NewPath("status"), status, old.UnstructuredContent()["status"], a.statusSchemaValidator, options...)...)
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, obj, scale)...)
|
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, obj, scale)...)
|
||||||
|
|
||||||
|
@ -81,6 +81,7 @@ type ratchetingTestContext struct {
|
|||||||
*testing.T
|
*testing.T
|
||||||
DynamicClient dynamic.Interface
|
DynamicClient dynamic.Interface
|
||||||
APIExtensionsClient clientset.Interface
|
APIExtensionsClient clientset.Interface
|
||||||
|
StatusSubresource bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ratchetingTestOperation interface {
|
type ratchetingTestOperation interface {
|
||||||
@ -164,7 +165,7 @@ func (a applyPatchOperation) Do(ctx *ratchetingTestContext) error {
|
|||||||
|
|
||||||
patch := &unstructured.Unstructured{}
|
patch := &unstructured.Unstructured{}
|
||||||
if obj, ok := a.patch.(map[string]interface{}); ok {
|
if obj, ok := a.patch.(map[string]interface{}); ok {
|
||||||
patch.Object = obj
|
patch.Object = runtime.DeepCopyJSON(obj)
|
||||||
} else if str, ok := a.patch.(string); ok {
|
} else if str, ok := a.patch.(string); ok {
|
||||||
str = FixTabsOrDie(str)
|
str = FixTabsOrDie(str)
|
||||||
if err := utilyaml.NewYAMLOrJSONDecoder(strings.NewReader(str), len(str)).Decode(&patch.Object); err != nil {
|
if err := utilyaml.NewYAMLOrJSONDecoder(strings.NewReader(str), len(str)).Decode(&patch.Object); err != nil {
|
||||||
@ -174,24 +175,31 @@ func (a applyPatchOperation) Do(ctx *ratchetingTestContext) error {
|
|||||||
return fmt.Errorf("invalid patch type: %T", a.patch)
|
return fmt.Errorf("invalid patch type: %T", a.patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.StatusSubresource {
|
||||||
|
patch.Object = map[string]interface{}{"status": patch.Object}
|
||||||
|
}
|
||||||
|
|
||||||
patch.SetKind(kind)
|
patch.SetKind(kind)
|
||||||
patch.SetAPIVersion(a.gvr.GroupVersion().String())
|
patch.SetAPIVersion(a.gvr.GroupVersion().String())
|
||||||
patch.SetName(a.name)
|
patch.SetName(a.name)
|
||||||
patch.SetNamespace("default")
|
patch.SetNamespace("default")
|
||||||
|
|
||||||
_, err := ctx.DynamicClient.
|
c := ctx.DynamicClient.Resource(a.gvr).Namespace(patch.GetNamespace())
|
||||||
Resource(a.gvr).
|
if ctx.StatusSubresource {
|
||||||
Namespace(patch.GetNamespace()).
|
if _, err := c.Get(context.TODO(), patch.GetName(), metav1.GetOptions{}); apierrors.IsNotFound(err) {
|
||||||
Apply(
|
// ApplyStatus will not automatically create an object, we must make sure it exists before we can
|
||||||
context.TODO(),
|
// apply the status to it.
|
||||||
patch.GetName(),
|
_, err := c.Create(context.TODO(), patch, metav1.CreateOptions{})
|
||||||
patch,
|
if err != nil {
|
||||||
metav1.ApplyOptions{
|
|
||||||
FieldManager: "manager",
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.ApplyStatus(context.TODO(), patch.GetName(), patch, metav1.ApplyOptions{FieldManager: "manager"})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := c.Apply(context.TODO(), patch.GetName(), patch, metav1.ApplyOptions{FieldManager: "manager"})
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a applyPatchOperation) Description() string {
|
func (a applyPatchOperation) Description() string {
|
||||||
@ -228,10 +236,20 @@ func (u updateMyCRDV1Beta1Schema) Do(ctx *ratchetingTestContext) error {
|
|||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.StatusSubresource {
|
||||||
|
sch = &apiextensionsv1.JSONSchemaProps{
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]apiextensionsv1.JSONSchemaProps{
|
||||||
|
"status": *sch,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, v := range myCRD.Spec.Versions {
|
for _, v := range myCRD.Spec.Versions {
|
||||||
if v.Name != myCRDV1Beta1.Version {
|
if v.Name != myCRDV1Beta1.Version {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Schema.OpenAPIV3Schema = sch
|
v.Schema.OpenAPIV3Schema = sch
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,8 +300,17 @@ type patchMyCRDV1Beta1Schema struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p patchMyCRDV1Beta1Schema) Do(ctx *ratchetingTestContext) error {
|
func (p patchMyCRDV1Beta1Schema) Do(ctx *ratchetingTestContext) error {
|
||||||
|
patch := p.patch
|
||||||
|
if ctx.StatusSubresource {
|
||||||
|
patch = map[string]interface{}{
|
||||||
|
"properties": map[string]interface{}{
|
||||||
|
"status": patch,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
patchJSON, err := json.Marshal(p.patch)
|
patchJSON, err := json.Marshal(patch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -315,7 +342,12 @@ func (p patchMyCRDV1Beta1Schema) Do(ctx *ratchetingTestContext) error {
|
|||||||
|
|
||||||
return updateMyCRDV1Beta1Schema{
|
return updateMyCRDV1Beta1Schema{
|
||||||
newSchema: &parsed,
|
newSchema: &parsed,
|
||||||
}.Do(ctx)
|
}.Do(&ratchetingTestContext{
|
||||||
|
T: ctx.T,
|
||||||
|
DynamicClient: ctx.DynamicClient,
|
||||||
|
APIExtensionsClient: ctx.APIExtensionsClient,
|
||||||
|
StatusSubresource: false, // We have already handled the status subresource.
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("could not find version %v in CRD %v", myCRDV1Beta1.Version, myCRD.Name)
|
return fmt.Errorf("could not find version %v in CRD %v", myCRDV1Beta1.Version, myCRD.Name)
|
||||||
@ -329,6 +361,7 @@ type ratchetingTestCase struct {
|
|||||||
Name string
|
Name string
|
||||||
Disabled bool
|
Disabled bool
|
||||||
Operations []ratchetingTestOperation
|
Operations []ratchetingTestOperation
|
||||||
|
SkipStatus bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTests(t *testing.T, cases []ratchetingTestCase) {
|
func runTests(t *testing.T, cases []ratchetingTestCase) {
|
||||||
@ -372,9 +405,15 @@ func runTests(t *testing.T, cases []ratchetingTestCase) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
Type: "object",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
Subresources: &apiextensionsv1.CustomResourceSubresources{
|
||||||
|
Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
Names: apiextensionsv1.CustomResourceDefinitionNames{
|
Names: apiextensionsv1.CustomResourceDefinitionNames{
|
||||||
Plural: resource,
|
Plural: resource,
|
||||||
@ -394,13 +433,7 @@ func runTests(t *testing.T, cases []ratchetingTestCase) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run(c.Name, func(t *testing.T) {
|
run := func(t *testing.T, ctx *ratchetingTestContext) {
|
||||||
ctx := &ratchetingTestContext{
|
|
||||||
T: t,
|
|
||||||
DynamicClient: dynamicClient,
|
|
||||||
APIExtensionsClient: apiExtensionClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, op := range c.Operations {
|
for i, op := range c.Operations {
|
||||||
t.Logf("Performing Operation: %v", op.Description())
|
t.Logf("Performing Operation: %v", op.Description())
|
||||||
if err := op.Do(ctx); err != nil {
|
if err := op.Do(ctx); err != nil {
|
||||||
@ -413,7 +446,26 @@ func runTests(t *testing.T, cases []ratchetingTestCase) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(c.Name, func(t *testing.T) {
|
||||||
|
run(t, &ratchetingTestContext{
|
||||||
|
T: t,
|
||||||
|
DynamicClient: dynamicClient,
|
||||||
|
APIExtensionsClient: apiExtensionClient,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if !c.SkipStatus {
|
||||||
|
t.Run("Status: "+c.Name, func(t *testing.T) {
|
||||||
|
run(t, &ratchetingTestContext{
|
||||||
|
T: t,
|
||||||
|
DynamicClient: dynamicClient,
|
||||||
|
APIExtensionsClient: apiExtensionClient,
|
||||||
|
StatusSubresource: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -443,23 +495,23 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
myCRDV1Beta1,
|
myCRDV1Beta1,
|
||||||
myCRDInstanceName,
|
myCRDInstanceName,
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"hasMinimum": 0,
|
"hasMinimum": int64(0),
|
||||||
"hasMaximum": 1000,
|
"hasMaximum": int64(1000),
|
||||||
"hasMinimumAndMaximum": 50,
|
"hasMinimumAndMaximum": int64(50),
|
||||||
}},
|
}},
|
||||||
patchMyCRDV1Beta1Schema{
|
patchMyCRDV1Beta1Schema{
|
||||||
"Add stricter minimums and maximums that violate the previous object",
|
"Add stricter minimums and maximums that violate the previous object",
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"properties": map[string]interface{}{
|
"properties": map[string]interface{}{
|
||||||
"hasMinimum": map[string]interface{}{
|
"hasMinimum": map[string]interface{}{
|
||||||
"minimum": 10,
|
"minimum": int64(10),
|
||||||
},
|
},
|
||||||
"hasMaximum": map[string]interface{}{
|
"hasMaximum": map[string]interface{}{
|
||||||
"maximum": 20,
|
"maximum": int64(20),
|
||||||
},
|
},
|
||||||
"hasMinimumAndMaximum": map[string]interface{}{
|
"hasMinimumAndMaximum": map[string]interface{}{
|
||||||
"minimum": 10,
|
"minimum": int64(10),
|
||||||
"maximum": 20,
|
"maximum": int64(20),
|
||||||
},
|
},
|
||||||
"noRestrictions": map[string]interface{}{
|
"noRestrictions": map[string]interface{}{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@ -471,33 +523,33 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
myCRDV1Beta1,
|
myCRDV1Beta1,
|
||||||
myCRDInstanceName,
|
myCRDInstanceName,
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"noRestrictions": 50,
|
"noRestrictions": int64(50),
|
||||||
}},
|
}},
|
||||||
expectError{
|
expectError{
|
||||||
applyPatchOperation{
|
applyPatchOperation{
|
||||||
"Change a single old field to be invalid",
|
"Change a single old field to be invalid",
|
||||||
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
||||||
"hasMinimum": 5,
|
"hasMinimum": int64(5),
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
expectError{
|
expectError{
|
||||||
applyPatchOperation{
|
applyPatchOperation{
|
||||||
"Change multiple old fields to be invalid",
|
"Change multiple old fields to be invalid",
|
||||||
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
||||||
"hasMinimum": 5,
|
"hasMinimum": int64(5),
|
||||||
"hasMaximum": 21,
|
"hasMaximum": int64(21),
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
applyPatchOperation{
|
applyPatchOperation{
|
||||||
"Change single old field to be valid",
|
"Change single old field to be valid",
|
||||||
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
||||||
"hasMinimum": 11,
|
"hasMinimum": int64(11),
|
||||||
}},
|
}},
|
||||||
applyPatchOperation{
|
applyPatchOperation{
|
||||||
"Change multiple old fields to be valid",
|
"Change multiple old fields to be valid",
|
||||||
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
||||||
"hasMaximum": 19,
|
"hasMaximum": int64(19),
|
||||||
"hasMinimumAndMaximum": 15,
|
"hasMinimumAndMaximum": int64(15),
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -576,8 +628,8 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"Create an instance",
|
"Create an instance",
|
||||||
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
||||||
"nums": map[string]interface{}{
|
"nums": map[string]interface{}{
|
||||||
"num1": 1,
|
"num1": int64(1),
|
||||||
"num2": 1000000,
|
"num2": int64(1000000),
|
||||||
},
|
},
|
||||||
"content": map[string]interface{}{
|
"content": map[string]interface{}{
|
||||||
"k1": "some content",
|
"k1": "some content",
|
||||||
@ -590,7 +642,7 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"properties": map[string]interface{}{
|
"properties": map[string]interface{}{
|
||||||
"nums": map[string]interface{}{
|
"nums": map[string]interface{}{
|
||||||
"additionalProperties": map[string]interface{}{
|
"additionalProperties": map[string]interface{}{
|
||||||
"minimum": 1000,
|
"minimum": int64(1000),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -599,16 +651,16 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"updating validating field num2 to another validating value, but rachet invalid field num1",
|
"updating validating field num2 to another validating value, but rachet invalid field num1",
|
||||||
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
||||||
"nums": map[string]interface{}{
|
"nums": map[string]interface{}{
|
||||||
"num1": 1,
|
"num1": int64(1),
|
||||||
"num2": 2000,
|
"num2": int64(2000),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
expectError{applyPatchOperation{
|
expectError{applyPatchOperation{
|
||||||
"update field num1 to different invalid value",
|
"update field num1 to different invalid value",
|
||||||
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
||||||
"nums": map[string]interface{}{
|
"nums": map[string]interface{}{
|
||||||
"num1": 2,
|
"num1": int64(2),
|
||||||
"num2": 2000,
|
"num2": int64(2000),
|
||||||
},
|
},
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
@ -646,8 +698,8 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"properties": map[string]interface{}{
|
"properties": map[string]interface{}{
|
||||||
"restricted": map[string]interface{}{
|
"restricted": map[string]interface{}{
|
||||||
"minProperties": 1,
|
"minProperties": int64(1),
|
||||||
"maxProperties": 1,
|
"maxProperties": int64(1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@ -679,7 +731,7 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"properties": map[string]interface{}{
|
"properties": map[string]interface{}{
|
||||||
"restricted": map[string]interface{}{
|
"restricted": map[string]interface{}{
|
||||||
"minProperties": 2,
|
"minProperties": int64(2),
|
||||||
"maxProperties": nil,
|
"maxProperties": nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -709,7 +761,7 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"properties": map[string]interface{}{
|
"properties": map[string]interface{}{
|
||||||
"restricted": map[string]interface{}{
|
"restricted": map[string]interface{}{
|
||||||
"minProperties": nil,
|
"minProperties": nil,
|
||||||
"maxProperties": 1,
|
"maxProperties": int64(1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@ -763,7 +815,7 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"properties": map[string]interface{}{
|
"properties": map[string]interface{}{
|
||||||
"array": map[string]interface{}{
|
"array": map[string]interface{}{
|
||||||
"minItems": 10,
|
"minItems": int64(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@ -825,7 +877,7 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"properties": map[string]interface{}{
|
"properties": map[string]interface{}{
|
||||||
"array": map[string]interface{}{
|
"array": map[string]interface{}{
|
||||||
"maxItems": 1,
|
"maxItems": int64(1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@ -884,10 +936,10 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"properties": map[string]interface{}{
|
"properties": map[string]interface{}{
|
||||||
"minField": map[string]interface{}{
|
"minField": map[string]interface{}{
|
||||||
"minLength": 10,
|
"minLength": int64(10),
|
||||||
},
|
},
|
||||||
"maxField": map[string]interface{}{
|
"maxField": map[string]interface{}{
|
||||||
"maxLength": 15,
|
"maxLength": int64(15),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@ -1084,17 +1136,17 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"field": []interface{}{
|
"field": []interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "nginx",
|
"name": "nginx",
|
||||||
"port": 443,
|
"port": int64(443),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "etcd",
|
"name": "etcd",
|
||||||
"port": 2379,
|
"port": int64(2379),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "kube-apiserver",
|
"name": "kube-apiserver",
|
||||||
"port": 6443,
|
"port": int64(6443),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1104,7 +1156,7 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"properties": map[string]interface{}{
|
"properties": map[string]interface{}{
|
||||||
"field": map[string]interface{}{
|
"field": map[string]interface{}{
|
||||||
"maxItems": 2,
|
"maxItems": int64(2),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@ -1114,17 +1166,17 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"field": []interface{}{
|
"field": []interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "kube-apiserver",
|
"name": "kube-apiserver",
|
||||||
"port": 6443,
|
"port": int64(6443),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "nginx",
|
"name": "nginx",
|
||||||
"port": 443,
|
"port": int64(443),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "etcd",
|
"name": "etcd",
|
||||||
"port": 2379,
|
"port": int64(2379),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1136,22 +1188,22 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"field": []interface{}{
|
"field": []interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "kube-apiserver",
|
"name": "kube-apiserver",
|
||||||
"port": 6443,
|
"port": int64(6443),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "nginx",
|
"name": "nginx",
|
||||||
"port": 443,
|
"port": int64(443),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "etcd",
|
"name": "etcd",
|
||||||
"port": 2379,
|
"port": int64(2379),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "dev",
|
"name": "dev",
|
||||||
"port": 8080,
|
"port": int64(8080),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1165,7 +1217,7 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"items": map[string]interface{}{
|
"items": map[string]interface{}{
|
||||||
"properties": map[string]interface{}{
|
"properties": map[string]interface{}{
|
||||||
"port": map[string]interface{}{
|
"port": map[string]interface{}{
|
||||||
"multipleOf": 2,
|
"multipleOf": int64(2),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1179,17 +1231,17 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"field": []interface{}{
|
"field": []interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "nginx",
|
"name": "nginx",
|
||||||
"port": 443,
|
"port": int64(443),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "etcd",
|
"name": "etcd",
|
||||||
"port": 2379,
|
"port": int64(2379),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "kube-apiserver",
|
"name": "kube-apiserver",
|
||||||
"port": 6443,
|
"port": int64(6443),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1201,22 +1253,22 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"field": []interface{}{
|
"field": []interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "nginx",
|
"name": "nginx",
|
||||||
"port": 443,
|
"port": int64(443),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "etcd",
|
"name": "etcd",
|
||||||
"port": 2379,
|
"port": int64(2379),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "kube-apiserver",
|
"name": "kube-apiserver",
|
||||||
"port": 6443,
|
"port": int64(6443),
|
||||||
"field": "this is a changed value for an an invalid but grandfathered key",
|
"field": "this is a changed value for an an invalid but grandfathered key",
|
||||||
},
|
},
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "dev",
|
"name": "dev",
|
||||||
"port": 8080,
|
"port": int64(8080),
|
||||||
"field": "value",
|
"field": "value",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1259,7 +1311,7 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"values": map[string]interface{}{
|
"values": map[string]interface{}{
|
||||||
"items": map[string]interface{}{
|
"items": map[string]interface{}{
|
||||||
"additionalProperties": map[string]interface{}{
|
"additionalProperties": map[string]interface{}{
|
||||||
"minLength": 6,
|
"minLength": int64(6),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1331,6 +1383,7 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "CEL Optional OldSelf",
|
Name: "CEL Optional OldSelf",
|
||||||
|
SkipStatus: true, // oldSelf can never be null for a status update.
|
||||||
Operations: []ratchetingTestOperation{
|
Operations: []ratchetingTestOperation{
|
||||||
updateMyCRDV1Beta1Schema{&apiextensionsv1.JSONSchemaProps{
|
updateMyCRDV1Beta1Schema{&apiextensionsv1.JSONSchemaProps{
|
||||||
Type: "object",
|
Type: "object",
|
||||||
@ -1461,15 +1514,15 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"field": map[string]interface{}{
|
"field": map[string]interface{}{
|
||||||
"object1": map[string]interface{}{
|
"object1": map[string]interface{}{
|
||||||
"stringField": "a string",
|
"stringField": "a string",
|
||||||
"intField": 5,
|
"intField": int64(5),
|
||||||
},
|
},
|
||||||
"object2": map[string]interface{}{
|
"object2": map[string]interface{}{
|
||||||
"stringField": "another string",
|
"stringField": "another string",
|
||||||
"intField": 15,
|
"intField": int64(15),
|
||||||
},
|
},
|
||||||
"object3": map[string]interface{}{
|
"object3": map[string]interface{}{
|
||||||
"stringField": "a third string",
|
"stringField": "a third string",
|
||||||
"intField": 7,
|
"intField": int64(7),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@ -1499,19 +1552,19 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"field": map[string]interface{}{
|
"field": map[string]interface{}{
|
||||||
"object1": map[string]interface{}{
|
"object1": map[string]interface{}{
|
||||||
"stringField": "a string",
|
"stringField": "a string",
|
||||||
"intField": 5,
|
"intField": int64(5),
|
||||||
},
|
},
|
||||||
"object2": map[string]interface{}{
|
"object2": map[string]interface{}{
|
||||||
"stringField": "another string",
|
"stringField": "another string",
|
||||||
"intField": 15,
|
"intField": int64(15),
|
||||||
},
|
},
|
||||||
"object3": map[string]interface{}{
|
"object3": map[string]interface{}{
|
||||||
"stringField": "a third string",
|
"stringField": "a third string",
|
||||||
"intField": 7,
|
"intField": int64(7),
|
||||||
},
|
},
|
||||||
"object4": map[string]interface{}{
|
"object4": map[string]interface{}{
|
||||||
"stringField": "k8s third string",
|
"stringField": "k8s third string",
|
||||||
"intField": 7,
|
"intField": int64(7),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@ -1521,12 +1574,12 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"field": map[string]interface{}{
|
"field": map[string]interface{}{
|
||||||
"object1": map[string]interface{}{
|
"object1": map[string]interface{}{
|
||||||
"stringField": "a string",
|
"stringField": "a string",
|
||||||
"intField": 15,
|
"intField": int64(15),
|
||||||
},
|
},
|
||||||
"object2": map[string]interface{}{
|
"object2": map[string]interface{}{
|
||||||
"stringField": "another string",
|
"stringField": "another string",
|
||||||
"intField": 10,
|
"intField": int64(10),
|
||||||
"otherIntField": 20,
|
"otherIntField": int64(20),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@ -1571,11 +1624,11 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"field": map[string]interface{}{
|
"field": map[string]interface{}{
|
||||||
"object1": map[string]interface{}{
|
"object1": map[string]interface{}{
|
||||||
"stringField": "a string", // invalid. even number length, no k8s prefix
|
"stringField": "a string", // invalid. even number length, no k8s prefix
|
||||||
"intField": 1000,
|
"intField": int64(1000),
|
||||||
},
|
},
|
||||||
"object4": map[string]interface{}{
|
"object4": map[string]interface{}{
|
||||||
"stringField": "k8s third string", // invalid. even number length. ratcheted
|
"stringField": "k8s third string", // invalid. even number length. ratcheted
|
||||||
"intField": 7000,
|
"intField": int64(7000),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@ -1586,11 +1639,11 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"field": map[string]interface{}{
|
"field": map[string]interface{}{
|
||||||
"object1": map[string]interface{}{
|
"object1": map[string]interface{}{
|
||||||
"stringField": "k8s third string",
|
"stringField": "k8s third string",
|
||||||
"intField": 1000,
|
"intField": int64(1000),
|
||||||
},
|
},
|
||||||
"object4": map[string]interface{}{
|
"object4": map[string]interface{}{
|
||||||
"stringField": "a string",
|
"stringField": "a string",
|
||||||
"intField": 7000,
|
"intField": int64(7000),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}},
|
}}},
|
||||||
@ -1600,11 +1653,11 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"field": map[string]interface{}{
|
"field": map[string]interface{}{
|
||||||
"object1": map[string]interface{}{
|
"object1": map[string]interface{}{
|
||||||
"stringField": "k8s a stringy",
|
"stringField": "k8s a stringy",
|
||||||
"intField": 1000,
|
"intField": int64(1000),
|
||||||
},
|
},
|
||||||
"object4": map[string]interface{}{
|
"object4": map[string]interface{}{
|
||||||
"stringField": "k8s third stringy",
|
"stringField": "k8s third stringy",
|
||||||
"intField": 7000,
|
"intField": int64(7000),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@ -1643,7 +1696,7 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"reate a list of numbers with duplicates using the old simple schema",
|
"reate a list of numbers with duplicates using the old simple schema",
|
||||||
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
||||||
"values": map[string]interface{}{
|
"values": map[string]interface{}{
|
||||||
"dups": []interface{}{1, 2, 2, 3, 1000, 2000},
|
"dups": []interface{}{int64(1), int64(2), int64(2), int64(3), int64(1000), int64(2000)},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
patchMyCRDV1Beta1Schema{
|
patchMyCRDV1Beta1Schema{
|
||||||
@ -1662,15 +1715,15 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"change original without removing duplicates",
|
"change original without removing duplicates",
|
||||||
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
||||||
"values": map[string]interface{}{
|
"values": map[string]interface{}{
|
||||||
"dups": []interface{}{1, 2, 2, 3, 1000, 2000, 3},
|
"dups": []interface{}{int64(1), int64(2), int64(2), int64(3), int64(1000), int64(2000), int64(3)},
|
||||||
},
|
},
|
||||||
}}},
|
}}},
|
||||||
expectError{applyPatchOperation{
|
expectError{applyPatchOperation{
|
||||||
"add another list with duplicates",
|
"add another list with duplicates",
|
||||||
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
||||||
"values": map[string]interface{}{
|
"values": map[string]interface{}{
|
||||||
"dups": []interface{}{1, 2, 2, 3, 1000, 2000},
|
"dups": []interface{}{int64(1), int64(2), int64(2), int64(3), int64(1000), int64(2000)},
|
||||||
"dups2": []interface{}{1, 2, 2, 3, 1000, 2000},
|
"dups2": []interface{}{int64(1), int64(2), int64(2), int64(3), int64(1000), int64(2000)},
|
||||||
},
|
},
|
||||||
}}},
|
}}},
|
||||||
// Can add a valid sibling field
|
// Can add a valid sibling field
|
||||||
@ -1680,8 +1733,8 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
"add a valid sibling field",
|
"add a valid sibling field",
|
||||||
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
myCRDV1Beta1, myCRDInstanceName, map[string]interface{}{
|
||||||
"values": map[string]interface{}{
|
"values": map[string]interface{}{
|
||||||
"dups": []interface{}{1, 2, 2, 3, 1000, 2000},
|
"dups": []interface{}{int64(1), int64(2), int64(2), int64(3), int64(1000), int64(2000)},
|
||||||
"otherField": []interface{}{1, 2, 3},
|
"otherField": []interface{}{int64(1), int64(2), int64(3)},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
// Can remove dups to make valid
|
// Can remove dups to make valid
|
||||||
@ -1694,8 +1747,8 @@ func TestRatchetingFunctionality(t *testing.T) {
|
|||||||
myCRDInstanceName,
|
myCRDInstanceName,
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"values": map[string]interface{}{
|
"values": map[string]interface{}{
|
||||||
"dups": []interface{}{1, 3, 1000, 2000},
|
"dups": []interface{}{int64(1), int64(3), int64(1000), int64(2000)},
|
||||||
"otherField": []interface{}{1, 2, 3},
|
"otherField": []interface{}{int64(1), int64(2), int64(3)},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user