Change:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! If the matter is security related, please disclose it privately via --A CRD with the following schema will fail server side validation while removing the description on metadata will make the CRD valid Either we allow description, or we make the error message clear. Create the example CRD above. The validation code is . If we want to allow setting description, I think we should clear the description field the same way as we do for before calling . /good-first-issue /area custom-resources /sig api-machinery Kubernetes version (use ): HEAD
This request has been marked as suitable for new contributors. Please ensure the request meets the requirements listed . If this request no longer meets these requirements, the label can be removed by commenting with the command. <detailsIn response to : <!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! --#### What happened: A CRD with the following schema while removing the description on metadata will make the CRD valid Either we allow description, or we make the error message clear. Create the example CRD above. The validation code is . If we want to allow setting description, I think we should clear the description field the same way as we do for before calling . /area custom-resources /sig api-machinery Kubernetes version (use ): HEAD Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
This is a duplicate of and which was previously discussed and declined
Thanks for the background! That makes sense. Maybe we could make the error message more clear "we don't allow overriding the native description of object metadata"
Improving the message is fine, though this applies to all metadata properties, not just description
/triage accepted
/assign
Hi, is this issue still fresh? Though it's closed but I see that there still exists something to do with improvement for error message of metadata properties validation. If it's available I will make a pr for this.
technically the error message was correct: "must not specify anything other than name and generateName, but metadata is implicitly specified". The tricky part is what "anything" means but since we had three issues about this in the past, I'd be happy to see the error message being improved in a way that is not too verbose. I was thinking of "must not specify anything other than type and properties, and must not specify anything other than name and generateName in the properties; but metadata is implicitly specified"
Yes I agree with that, and in additional I think we could split and specify invalid properties to a new error message like "must not specify any property other than name and generateName: description". And for invalid type and properties we could stay the same way like "must not specify anything other than type and properties, but metadata is implicitly specified".
sounds good to me. Just be aware that there are many fields a user could specify in a JSONSchemaProps other than and , please try not to make the code too complicated.

Discussion:
allErrs = append(allErrs, field.Invalid(fldPath.Child("properties").Key("apiVersion").Child("type"), apiVersion.Type, "must be string")) } } <del> if metadata, found := s.Properties["metadata"]; found && checkMetadata { if metadata.Type != "object" { allErrs = append(allErrs, field.Invalid(fldPath.Child("properties").Key("metadata").Child("type"), metadata.Type, "must be object")) } </del> <ins> 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")) </ins> } <del> if metadata, found := s.Properties["metadata"]; found && lvl == rootLevel { </del> <ins> if lvl == rootLevel { </ins> // metadata is a shallow copy. We can mutate it. <del> _, foundName := metadata.Properties["name"] _, foundGenerateName := metadata.Properties["generateName"] if foundName && foundGenerateName && len(metadata.Properties) == 2 { metadata.Properties = nil } else if (foundName || foundGenerateName) && len(metadata.Properties) == 1 { metadata.Properties = nil </del> <ins> _, 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 </ins> } <del> metadata.Type = "" metadata.Default.Object = nil // this is checked in API validation (and also tested) if metadata.ValueValidation == nil { metadata.ValueValidation = &ValueValidation{} </del> <ins> s.Type = "" s.Default.Object = nil // this is checked in API validation (and also tested) if s.ValueValidation == nil { s.ValueValidation = &ValueValidation{} </ins> } <del> if !reflect.DeepEqual(metadata, Structural{ValueValidation: &ValueValidation{}}) { </del> <ins> if !reflect.DeepEqual(*s, Structural{ValueValidation: &ValueValidation{}}) { </ins> // TODO: this is actually a field.Invalid error, but we cannot do JSON serialization of metadata here to get a proper message <del> allErrs = append(allErrs, field.Forbidden(fldPath.Child("properties").Key("metadata"), "must not specify anything other than name and generateName, but metadata is implicitly specified")) </del> <ins> allErrs = append(allErrs, field.Forbidden(fldPath, "must not specify anything other than name and generateName, but metadata is implicitly specified")) </ins> } } <del> 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")) } </del> return allErrs }

"reflect" "testing" <ins> metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" </ins> fuzz "github.com/google/gofuzz" ) <ins> func TestValidateStructuralMetadataInvariants(t *testing.T) { fuzzer := fuzz.New() fuzzer.Funcs( func(s *JSON, c fuzz.Continue) { if c.RandBool() { s.Object = float64(42.0) } }, func(s **StructuralOrBool, c fuzz.Continue) { if c.RandBool() { *s = &StructuralOrBool{} } }, func(s **Structural, c fuzz.Continue) { if c.RandBool() { *s = &Structural{} } }, func(s *Structural, c fuzz.Continue) { if c.RandBool() { *s = Structural{} } }, func(vv **NestedValueValidation, c fuzz.Continue) { if c.RandBool() { *vv = &NestedValueValidation{} } }, func(vv *NestedValueValidation, c fuzz.Continue) { if c.RandBool() { *vv = NestedValueValidation{} } }, ) fuzzer.NilChance(0) // check that type must be object typeNames := []string{"object", "array", "number", "integer", "boolean", "string"} for _, typeName := range typeNames { s := Structural{ Generic: Generic{ Type: typeName, }, } errs := validateStructuralMetadataInvariants(&s, true, rootLevel, nil) if len(errs) != 0 { t.Logf("errors returned: %v", errs) } if len(errs) != 0 && typeName == "object" { t.Errorf("unexpected forbidden field validation errors for: %#v", s) } if len(errs) == 0 && typeName != "object" { t.Errorf("expected forbidden field validation errors for: %#v", s) } } // check that anything other than name and generateName of ObjectMeta in metadata properties is forbidden tt := reflect.TypeOf(metav1.ObjectMeta{}) for i := 0; i < tt.NumField(); i++ { property := tt.Field(i).Name s := &Structural{ Generic: Generic{ Type: "object", }, Properties: map[string]Structural{ property: {}, }, } errs := validateStructuralMetadataInvariants(s, true, rootLevel, nil) if len(errs) != 0 { t.Logf("errors returned: %v", errs) } if len(errs) != 0 && (property == "name" || property == "generateName") { t.Errorf("unexpected forbidden field validation errors for: %#v", s) } if len(errs) == 0 && property != "name" && property != "generateName" { t.Errorf("expected forbidden field validation errors for: %#v", s) } } // check that anything other than type and properties in metadata is forbidden tt = reflect.TypeOf(Structural{}) for i := 0; i < tt.NumField(); i++ { s := Structural{} x := reflect.ValueOf(&s).Elem() fuzzer.Fuzz(x.Field(i).Addr().Interface()) s.Type = "object" s.Properties = map[string]Structural{ "name":         {}, "generateName": {}, } s.Default.Object = nil // this is checked in API validation, we don't need to test it here valid := reflect.DeepEqual(s, Structural{ Generic: Generic{ Type: "object", Default: JSON{ Object: nil, }, }, Properties: map[string]Structural{ "name":         {}, "generateName": {}, }, }) errs := validateStructuralMetadataInvariants(s.DeepCopy(), true, rootLevel, nil) if len(errs) != 0 { t.Logf("errors returned: %v", errs) } if len(errs) != 0 && valid { t.Errorf("unexpected forbidden field validation errors for: %#v", s) } if len(errs) == 0 && !valid { t.Errorf("expected forbidden field validation errors for: %#v", s) } } } </ins> func TestValidateNestedValueValidationComplete(t *testing.T) { fuzzer := fuzz.New() fuzzer.Funcs(