mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #83552 from liggitt/unstructured-metadata-decoder
Verify metadata schema when decoding unstructured objects in resource builder
This commit is contained in:
commit
c42eceb136
@ -10,6 +10,7 @@ go_library(
|
|||||||
"helper.go",
|
"helper.go",
|
||||||
"interfaces.go",
|
"interfaces.go",
|
||||||
"mapper.go",
|
"mapper.go",
|
||||||
|
"metadata_decoder.go",
|
||||||
"result.go",
|
"result.go",
|
||||||
"scheme.go",
|
"scheme.go",
|
||||||
"selector.go",
|
"selector.go",
|
||||||
@ -30,6 +31,7 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//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/schema:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
"//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/errors:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
|
@ -265,7 +265,7 @@ func (b *Builder) Unstructured() *Builder {
|
|||||||
localFn: b.isLocal,
|
localFn: b.isLocal,
|
||||||
restMapperFn: b.restMapperFn,
|
restMapperFn: b.restMapperFn,
|
||||||
clientFn: b.getClient,
|
clientFn: b.getClient,
|
||||||
decoder: unstructured.UnstructuredJSONScheme,
|
decoder: &metadataValidatingDecoder{unstructured.UnstructuredJSONScheme},
|
||||||
}
|
}
|
||||||
|
|
||||||
return b
|
return b
|
||||||
|
@ -225,6 +225,35 @@ var aPod string = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
var aRC string = `
|
var aRC string = `
|
||||||
{
|
{
|
||||||
@ -280,6 +309,22 @@ func newDefaultBuilderWith(fakeClientFn FakeClientFunc) *Builder {
|
|||||||
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
type errorRestMapper struct {
|
type errorRestMapper struct {
|
||||||
meta.RESTMapper
|
meta.RESTMapper
|
||||||
err error
|
err error
|
||||||
@ -1679,3 +1724,61 @@ func TestHasNames(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
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"`
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user