Change:
/kind bug What happened: When creating resource with boolean value in annotation, kubectl apply completes sucessfully without any errors, but resource has no annotations. What you expected to happen: Error similar to what returns How to reproduce it (as minimally and precisely as possible): Creates: Environment:
/sig cli
I believe it's by the implementation of . where maps bytes into corresponding resource object without any warning.
Annotation values must be strings. Something should generate an error, though
Issues go stale after 90d of inactivity. Mark the issue as fresh with . Stale issues rot after an additional 30d of inactivity and eventually close. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle stale
/remove-lifecycle stale
Issues go stale after 90d of inactivity. Mark the issue as fresh with . Stale issues rot after an additional 30d of inactivity and eventually close. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle stale
/remove-lifecycle stale
sadly, i'm afraid the annotations will keep typed for a long time. refactoring this will involve so many changes and we have to provide backward compatibility at the same time. it's almost impossible.
, thats fine, bug is more about , rather than annotation type on API side. correctly errors when it sees non-conforming annotations, but doesn't
Issues go stale after 90d of inactivity. Mark the issue as fresh with . Stale issues rot after an additional 30d of inactivity and eventually close. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle stale
/remove-lifecycle stale /assign code-sleuth
+1 There needs to be a way for to return an error and not apply the descriptors without any annotations. I just spent a week diagnosing an issue only to find out it was because my annotations all got removed from a simple error. It could have been prevented if it erred out like normal. :)
are you working on this issue? if not, can i look at this issue?
Hey , i'm working on it. I have some changes that i'm still discussing with before i raise my PR.
Issues go stale after 90d of inactivity. Mark the issue as fresh with . Stale issues rot after an additional 30d of inactivity and eventually close. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle stale
/remove-lifecycle stale
I understand boolean values are not allowed, however I am seeing that if i attempt to kubectl apply a yaml with a new boolean annotation, not only will it not add the new annotation, but it will also delete all existing annotations silently. this seems like more than an error messaging bug
FWIW -- this hit me and it was very confusing. What it looks like happens now (at least with kubectl v1.14.0 against v1.12.6-gke.10 is that the entire annotation map is not updated. No error is returned from kubectl.
This hit me too. Very confusing behavior. It cost 2 man-hours to understand the root cause.
I'm currently unable to deploy via kubectl apply -f due to this issue. Is there a way around it?
Hello! I have tracked down the root cause of this problem being the omission of error propagation in this . The error is dropped in the implementation of , but is emitted by . I’m going to present all of the solutions that I can think of: a prototype to the interface. This will allow the error to be propagated up naturally and would be the most elegant solution. I don’t fully understand any other consequences of this change other than reimplementing this function for other types that implement this interface. a warning using as done in other functions in this package like . I think this should be done no matter what to catch other users of this prototype from experiencing bugs like this. a type check earlier in the call stack to check if the type of is and call so that we can capture the error and default on using the interface function. An example location would be in ’s function . I don’t think that this is a good idea because is doesn’t solve the root cause of this error which could pop up in other areas. other types to strings. Would break compatibility and would cause confusion to users when it would work only with specific versions of kubectl Any other suggestions are welcome, and I’d like to hear from the community to find the most elegant solution to this critical problem. There may be other functions in this interface where we should pass an error as well. I’ve submitted a PR to implement what was described in bullet 1 and 2. Thank you! Griffin Dunn
Hm, Set* methods also return no errors, therefore you can SetAnnotation and receive different result back on subsequent GetAnotations breaking symmetry. I doubt API change is acceptable on that level, so there should be either some higher level type check of arts before descending into Unstructured codepath , for instance instantiate desired object as a typed object to see if passes all the checks.

Discussion:
"helper.go", "interfaces.go", "mapper.go", <ins> "metadata_decoder.go", </ins> "result.go", "scheme.go", "selector.go",

"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", <ins> "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library", </ins> "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",

localFn:      b.isLocal, restMapperFn: b.restMapperFn, clientFn:     b.getClient, <del> decoder:      unstructured.UnstructuredJSONScheme, </del> <ins> decoder:      &metadataValidatingDecoder{unstructured.UnstructuredJSONScheme}, </ins> } return b

} } ` <ins> var aPodBadAnnotations string = ` { "kind": "Pod", "apiVersion": "` + corev1GV.String() + `", "metadata": { "name": "busybox{id}", "labels": { "name": "busybox{id}" }, "annotations": { "name": 0 } }, "spec": { "containers": [ { "name": "busybox", "image": "busybox", "command": [ "sleep", "3600" ], "imagePullPolicy": "IfNotPresent" } ], "restartPolicy": "Always" } } ` </ins> var aRC string = ` {

WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...) } <ins> func newUnstructuredDefaultBuilder() *Builder { return newUnstructuredDefaultBuilderWith(fakeClient()) } func newUnstructuredDefaultBuilderWith(fakeClientFn FakeClientFunc) *Builder { return NewFakeBuilder( fakeClientFn, func() (meta.RESTMapper, error) { return testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme), nil }, func() (restmapper.CategoryExpander, error) { return FakeCategoryExpander, nil }). Unstructured() } </ins> type errorRestMapper struct { meta.RESTMapper err error

}) } } <ins> func TestUnstructured(t *testing.T) { // create test dirs tmpDir, err := utiltesting.MkTmpdir("unstructured_test") if err != nil { t.Fatalf("error creating temp dir: %v", err) } defer os.RemoveAll(tmpDir) // create test files writeTestFile(t, fmt.Sprintf("%s/pod.json", tmpDir), aPod) writeTestFile(t, fmt.Sprintf("%s/badpod.json", tmpDir), aPodBadAnnotations) tests := []struct { name          string file          string expectedError string }{ { name:          "pod", file:          "pod.json", expectedError: "", }, { name:          "badpod", file:          "badpod.json", expectedError: "v1.ObjectMeta.Annotations", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { result := newUnstructuredDefaultBuilder(). ContinueOnError(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{fmt.Sprintf("%s/%s", tmpDir, tc.file)}}). Flatten(). Do() err := result.Err() if err == nil { _, err = result.Infos() } if len(tc.expectedError) == 0 { if err != nil { t.Errorf("unexpected error: %v", err) } } else { if err == nil { t.Errorf("expected error, got none") } else if !strings.Contains(err.Error(), tc.expectedError) { t.Errorf("expected error with '%s', got: %v", tc.expectedError, err) } } }) } } </ins>

<ins> /* 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 resource import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/json" ) // hold a single instance of the case-sensitive decoder var caseSensitiveJsonIterator = json.CaseSensitiveJsonIterator() // metadataValidatingDecoder wraps a decoder and additionally ensures metadata schema fields decode before returning an unstructured object type metadataValidatingDecoder struct { decoder runtime.Decoder } func (m *metadataValidatingDecoder) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { obj, gvk, err := m.decoder.Decode(data, defaults, into) // if we already errored, return if err != nil { return obj, gvk, err } // if we're not unstructured, return if _, isUnstructured := obj.(runtime.Unstructured); !isUnstructured { return obj, gvk, err } // make sure the data can decode into ObjectMeta before we return, // so we don't silently truncate schema errors in metadata later with accesser get/set calls v := &metadataOnlyObject{} if typedErr := caseSensitiveJsonIterator.Unmarshal(data, v); typedErr != nil { return obj, gvk, typedErr } return obj, gvk, err } type metadataOnlyObject struct { metav1.TypeMeta   `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` } </ins>