mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-02 09:47:06 +00:00
Set expected in-memory version when decoding unstructured objects from etcd
This commit is contained in:
@@ -177,12 +177,19 @@ type StatusREST struct {
|
|||||||
var _ = rest.Patcher(&StatusREST{})
|
var _ = rest.Patcher(&StatusREST{})
|
||||||
|
|
||||||
func (r *StatusREST) New() runtime.Object {
|
func (r *StatusREST) New() runtime.Object {
|
||||||
return &unstructured.Unstructured{}
|
return r.store.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get retrieves the object from the storage. It is required to support Patch.
|
// Get retrieves the object from the storage. It is required to support Patch.
|
||||||
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||||
return r.store.Get(ctx, name, options)
|
o, err := r.store.Get(ctx, name, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if u, ok := o.(*unstructured.Unstructured); ok {
|
||||||
|
shallowCopyObjectMeta(u)
|
||||||
|
}
|
||||||
|
return o, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update alters the status subset of an object.
|
// Update alters the status subset of an object.
|
||||||
|
@@ -58,15 +58,18 @@ func checks(checkers ...Checker) []Checker {
|
|||||||
return checkers
|
return checkers
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebhookConverter(t *testing.T) {
|
func TestWebhookConverterWithWatchCache(t *testing.T) {
|
||||||
testWebhookConverter(t, false)
|
testWebhookConverter(t, false, true)
|
||||||
|
}
|
||||||
|
func TestWebhookConverterWithoutWatchCache(t *testing.T) {
|
||||||
|
testWebhookConverter(t, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebhookConverterWithDefaulting(t *testing.T) {
|
func TestWebhookConverterWithDefaulting(t *testing.T) {
|
||||||
testWebhookConverter(t, true)
|
testWebhookConverter(t, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testWebhookConverter(t *testing.T, defaulting bool) {
|
func testWebhookConverter(t *testing.T, defaulting, watchCache bool) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
group string
|
group string
|
||||||
handler http.Handler
|
handler http.Handler
|
||||||
@@ -115,7 +118,7 @@ func testWebhookConverter(t *testing.T, defaulting bool) {
|
|||||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceDefaulting, true)()
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceDefaulting, true)()
|
||||||
}
|
}
|
||||||
|
|
||||||
tearDown, config, options, err := fixtures.StartDefaultServer(t)
|
tearDown, config, options, err := fixtures.StartDefaultServer(t, fmt.Sprintf("--watch-cache=%v", watchCache))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -320,6 +323,28 @@ func validateNonTrivialConverted(t *testing.T, ctc *conversionTestContext) {
|
|||||||
}
|
}
|
||||||
verifyMultiVersionObject(t, getVersion.Name, obj)
|
verifyMultiVersionObject(t, getVersion.Name, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send a non-trivial patch to the main resource to verify the oldObject is in the right version
|
||||||
|
if _, err := client.Patch(name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"main":"true"}}}`), metav1.PatchOptions{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// verify that the right, pruned version is in storage
|
||||||
|
obj, err = ctc.etcdObjectReader.GetStoredCustomResource(ns, name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
verifyMultiVersionObject(t, "v1beta1", obj)
|
||||||
|
|
||||||
|
// send a non-trivial patch to the status subresource to verify the oldObject is in the right version
|
||||||
|
if _, err := client.Patch(name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"status":"true"}}}`), metav1.PatchOptions{}, "status"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// verify that the right, pruned version is in storage
|
||||||
|
obj, err = ctc.etcdObjectReader.GetStoredCustomResource(ns, name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
verifyMultiVersionObject(t, "v1beta1", obj)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -127,6 +127,16 @@ func (u *Unstructured) UnmarshalJSON(b []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewEmptyInstance returns a new instance of the concrete type containing only kind/apiVersion and no other data.
|
||||||
|
// This should be called instead of reflect.New() for unstructured types because the go type alone does not preserve kind/apiVersion info.
|
||||||
|
func (in *Unstructured) NewEmptyInstance() runtime.Unstructured {
|
||||||
|
out := new(Unstructured)
|
||||||
|
if in != nil {
|
||||||
|
out.GetObjectKind().SetGroupVersionKind(in.GetObjectKind().GroupVersionKind())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func (in *Unstructured) DeepCopy() *Unstructured {
|
func (in *Unstructured) DeepCopy() *Unstructured {
|
||||||
if in == nil {
|
if in == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@@ -52,6 +52,16 @@ func (u *UnstructuredList) EachListItem(fn func(runtime.Object) error) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewEmptyInstance returns a new instance of the concrete type containing only kind/apiVersion and no other data.
|
||||||
|
// This should be called instead of reflect.New() for unstructured types because the go type alone does not preserve kind/apiVersion info.
|
||||||
|
func (u *UnstructuredList) NewEmptyInstance() runtime.Unstructured {
|
||||||
|
out := new(UnstructuredList)
|
||||||
|
if u != nil {
|
||||||
|
out.SetGroupVersionKind(u.GroupVersionKind())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// UnstructuredContent returns a map contain an overlay of the Items field onto
|
// UnstructuredContent returns a map contain an overlay of the Items field onto
|
||||||
// the Object field. Items always overwrites overlay.
|
// the Object field. Items always overwrites overlay.
|
||||||
func (u *UnstructuredList) UnstructuredContent() map[string]interface{} {
|
func (u *UnstructuredList) UnstructuredContent() map[string]interface{} {
|
||||||
|
@@ -260,6 +260,9 @@ type Object interface {
|
|||||||
// to JSON allowed.
|
// to JSON allowed.
|
||||||
type Unstructured interface {
|
type Unstructured interface {
|
||||||
Object
|
Object
|
||||||
|
// NewEmptyInstance returns a new instance of the concrete type containing only kind/apiVersion and no other data.
|
||||||
|
// This should be called instead of reflect.New() for unstructured types because the go type alone does not preserve kind/apiVersion info.
|
||||||
|
NewEmptyInstance() Unstructured
|
||||||
// UnstructuredContent returns a non-nil map with this object's contents. Values may be
|
// UnstructuredContent returns a non-nil map with this object's contents. Values may be
|
||||||
// []interface{}, map[string]interface{}, or any primitive type. Contents are typically serialized to
|
// []interface{}, map[string]interface{}, or any primitive type. Contents are typically serialized to
|
||||||
// and from JSON. SetUnstructuredContent should be used to mutate the contents.
|
// and from JSON. SetUnstructuredContent should be used to mutate the contents.
|
||||||
|
@@ -261,6 +261,14 @@ func (obj *Unstructured) EachListItem(fn func(runtime.Object) error) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (obj *Unstructured) NewEmptyInstance() runtime.Unstructured {
|
||||||
|
out := new(Unstructured)
|
||||||
|
if obj != nil {
|
||||||
|
out.SetGroupVersionKind(obj.GroupVersionKind())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func (obj *Unstructured) UnstructuredContent() map[string]interface{} {
|
func (obj *Unstructured) UnstructuredContent() map[string]interface{} {
|
||||||
if obj.Object == nil {
|
if obj.Object == nil {
|
||||||
return make(map[string]interface{})
|
return make(map[string]interface{})
|
||||||
|
@@ -20,6 +20,7 @@ go_test(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/api/apitesting:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/apitesting:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/conversion:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
@@ -685,9 +685,15 @@ func (s *store) watch(ctx context.Context, key string, rv string, pred storage.S
|
|||||||
|
|
||||||
func (s *store) getState(getResp *clientv3.GetResponse, key string, v reflect.Value, ignoreNotFound bool) (*objState, error) {
|
func (s *store) getState(getResp *clientv3.GetResponse, key string, v reflect.Value, ignoreNotFound bool) (*objState, error) {
|
||||||
state := &objState{
|
state := &objState{
|
||||||
obj: reflect.New(v.Type()).Interface().(runtime.Object),
|
|
||||||
meta: &storage.ResponseMeta{},
|
meta: &storage.ResponseMeta{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if u, ok := v.Addr().Interface().(runtime.Unstructured); ok {
|
||||||
|
state.obj = u.NewEmptyInstance()
|
||||||
|
} else {
|
||||||
|
state.obj = reflect.New(v.Type()).Interface().(runtime.Object)
|
||||||
|
}
|
||||||
|
|
||||||
if len(getResp.Kvs) == 0 {
|
if len(getResp.Kvs) == 0 {
|
||||||
if !ignoreNotFound {
|
if !ignoreNotFound {
|
||||||
return nil, storage.NewKeyNotFoundError(key, 0)
|
return nil, storage.NewKeyNotFoundError(key, 0)
|
||||||
|
@@ -35,6 +35,7 @@ import (
|
|||||||
apitesting "k8s.io/apimachinery/pkg/api/apitesting"
|
apitesting "k8s.io/apimachinery/pkg/api/apitesting"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/conversion"
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@@ -1349,7 +1350,11 @@ func testSetup(t *testing.T) (context.Context, *store, *integration.ClusterV3) {
|
|||||||
func testPropogateStore(ctx context.Context, t *testing.T, store *store, obj *example.Pod) (string, *example.Pod) {
|
func testPropogateStore(ctx context.Context, t *testing.T, store *store, obj *example.Pod) (string, *example.Pod) {
|
||||||
// Setup store with a key and grab the output for returning.
|
// Setup store with a key and grab the output for returning.
|
||||||
key := "/testkey"
|
key := "/testkey"
|
||||||
err := store.conditionalDelete(ctx, key, &example.Pod{}, reflect.ValueOf(example.Pod{}), nil, storage.ValidateAllObjectFunc)
|
v, err := conversion.EnforcePtr(obj)
|
||||||
|
if err != nil {
|
||||||
|
panic("unable to convert output object to pointer")
|
||||||
|
}
|
||||||
|
err = store.conditionalDelete(ctx, key, &example.Pod{}, v, nil, storage.ValidateAllObjectFunc)
|
||||||
if err != nil && !storage.IsNotFound(err) {
|
if err != nil && !storage.IsNotFound(err) {
|
||||||
t.Fatalf("Cleanup failed: %v", err)
|
t.Fatalf("Cleanup failed: %v", err)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user