From 8eb3e9a51883e0b71f63586739acfa3b73de00fd Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Wed, 2 Nov 2016 21:54:13 -0400 Subject: [PATCH] Move unstructured to its own package under v1 It is a versioned type. --- pkg/api/meta/help.go | 3 +- pkg/api/meta/unstructured.go | 4 +- .../meta/v1/unstructured}/unstructured.go | 82 +++++++++++++++---- .../v1/unstructured}/unstructured_test.go | 37 +++++---- pkg/runtime/interfaces.go | 14 ++++ pkg/runtime/register.go | 3 - .../serializer/versioning/versioning.go | 2 +- pkg/runtime/types.go | 11 --- 8 files changed, 102 insertions(+), 54 deletions(-) rename pkg/{runtime => apis/meta/v1/unstructured}/unstructured.go (85%) rename pkg/{runtime => apis/meta/v1/unstructured}/unstructured_test.go (91%) diff --git a/pkg/api/meta/help.go b/pkg/api/meta/help.go index 39868e6b006..97b6c2ce6c3 100644 --- a/pkg/api/meta/help.go +++ b/pkg/api/meta/help.go @@ -20,6 +20,7 @@ import ( "fmt" "reflect" + "k8s.io/kubernetes/pkg/apis/meta/v1/unstructured" "k8s.io/kubernetes/pkg/conversion" "k8s.io/kubernetes/pkg/runtime" ) @@ -29,7 +30,7 @@ func IsListType(obj runtime.Object) bool { // if we're a runtime.Unstructured, check to see if we have an `items` key // This is a list type for recognition, but other Items type methods will fail on it // and give you errors. - if unstructured, ok := obj.(*runtime.Unstructured); ok { + if unstructured, ok := obj.(*unstructured.Unstructured); ok { _, ok := unstructured.Object["items"] return ok } diff --git a/pkg/api/meta/unstructured.go b/pkg/api/meta/unstructured.go index e03e1c69387..02cafdedd48 100644 --- a/pkg/api/meta/unstructured.go +++ b/pkg/api/meta/unstructured.go @@ -17,7 +17,7 @@ limitations under the License. package meta import ( - "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/apis/meta/v1/unstructured" "k8s.io/kubernetes/pkg/runtime/schema" ) @@ -25,7 +25,7 @@ import ( // dealing with runtime.Unstructured objects. func InterfacesForUnstructured(schema.GroupVersion) (*VersionInterfaces, error) { return &VersionInterfaces{ - ObjectConvertor: &runtime.UnstructuredObjectConverter{}, + ObjectConvertor: &unstructured.UnstructuredObjectConverter{}, MetadataAccessor: NewAccessor(), }, nil } diff --git a/pkg/runtime/unstructured.go b/pkg/apis/meta/v1/unstructured/unstructured.go similarity index 85% rename from pkg/runtime/unstructured.go rename to pkg/apis/meta/v1/unstructured/unstructured.go index b7076cde2c1..1041202101a 100644 --- a/pkg/runtime/unstructured.go +++ b/pkg/apis/meta/v1/unstructured/unstructured.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package runtime +package unstructured import ( "bytes" @@ -26,13 +26,59 @@ import ( "github.com/golang/glog" - "k8s.io/kubernetes/pkg/api/meta/metatypes" metav1 "k8s.io/kubernetes/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime/schema" "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/util/json" ) +// Unstructured allows objects that do not have Golang structs registered to be manipulated +// generically. This can be used to deal with the API objects from a plug-in. Unstructured +// objects still have functioning TypeMeta features-- kind, version, etc. +// +// WARNING: This object has accessors for the v1 standard metadata. You *MUST NOT* use this +// type if you are dealing with objects that are not in the server meta v1 schema. +// +// TODO: make the serialization part of this type distinct from the field accessors. +type Unstructured struct { + // Object is a JSON compatible map with string, float, int, bool, []interface{}, or + // map[string]interface{} + // children. + Object map[string]interface{} +} + +var _ runtime.Unstructured = &Unstructured{} +var _ runtime.Unstructured = &UnstructuredList{} + +func (obj *Unstructured) GetObjectKind() schema.ObjectKind { return obj } +func (obj *UnstructuredList) GetObjectKind() schema.ObjectKind { return obj } + +func (obj *Unstructured) IsUnstructuredObject() {} +func (obj *UnstructuredList) IsUnstructuredObject() {} + +func (obj *Unstructured) IsList() bool { + if obj.Object != nil { + _, ok := obj.Object["items"] + return ok + } + return false +} +func (obj *UnstructuredList) IsList() bool { return true } + +func (obj *Unstructured) UnstructuredContent() map[string]interface{} { + if obj.Object == nil { + obj.Object = make(map[string]interface{}) + } + return obj.Object +} +func (obj *UnstructuredList) UnstructuredContent() map[string]interface{} { + if obj.Object == nil { + obj.Object = make(map[string]interface{}) + } + return obj.Object +} + // MarshalJSON ensures that the unstructured object produces proper // JSON when passed to Go's standard JSON library. func (u *Unstructured) MarshalJSON() ([]byte, error) { @@ -142,7 +188,7 @@ func (u *Unstructured) setNestedMap(value map[string]string, fields ...string) { setNestedMap(u.Object, value, fields...) } -func extractOwnerReference(src interface{}) metatypes.OwnerReference { +func extractOwnerReference(src interface{}) metav1.OwnerReference { v := src.(map[string]interface{}) controllerPtr, ok := (getNestedField(v, "controller")).(*bool) if !ok { @@ -153,7 +199,7 @@ func extractOwnerReference(src interface{}) metatypes.OwnerReference { controllerPtr = &controller } } - return metatypes.OwnerReference{ + return metav1.OwnerReference{ Kind: getNestedString(v, "kind"), Name: getNestedString(v, "name"), APIVersion: getNestedString(v, "apiVersion"), @@ -162,7 +208,7 @@ func extractOwnerReference(src interface{}) metatypes.OwnerReference { } } -func setOwnerReference(src metatypes.OwnerReference) map[string]interface{} { +func setOwnerReference(src metav1.OwnerReference) map[string]interface{} { ret := make(map[string]interface{}) controllerPtr := src.Controller if controllerPtr != nil { @@ -202,20 +248,20 @@ func getOwnerReferences(object map[string]interface{}) ([]map[string]interface{} return ownerReferences, nil } -func (u *Unstructured) GetOwnerReferences() []metatypes.OwnerReference { +func (u *Unstructured) GetOwnerReferences() []metav1.OwnerReference { original, err := getOwnerReferences(u.Object) if err != nil { glog.V(6).Info(err) return nil } - ret := make([]metatypes.OwnerReference, 0, len(original)) + ret := make([]metav1.OwnerReference, 0, len(original)) for i := 0; i < len(original); i++ { ret = append(ret, extractOwnerReference(original[i])) } return ret } -func (u *Unstructured) SetOwnerReferences(references []metatypes.OwnerReference) { +func (u *Unstructured) SetOwnerReferences(references []metav1.OwnerReference) { var newReferences = make([]map[string]interface{}, 0, len(references)) for i := 0; i < len(references); i++ { newReferences = append(newReferences, setOwnerReference(references[i])) @@ -439,11 +485,11 @@ func (u *UnstructuredList) GroupVersionKind() schema.GroupVersionKind { // UnstructuredJSONScheme is capable of converting JSON data into the Unstructured // type, which can be used for generic access to objects without a predefined scheme. // TODO: move into serializer/json. -var UnstructuredJSONScheme Codec = unstructuredJSONScheme{} +var UnstructuredJSONScheme runtime.Codec = unstructuredJSONScheme{} type unstructuredJSONScheme struct{} -func (s unstructuredJSONScheme) Decode(data []byte, _ *schema.GroupVersionKind, obj Object) (Object, *schema.GroupVersionKind, error) { +func (s unstructuredJSONScheme) Decode(data []byte, _ *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { var err error if obj != nil { err = s.decodeInto(data, obj) @@ -457,13 +503,13 @@ func (s unstructuredJSONScheme) Decode(data []byte, _ *schema.GroupVersionKind, gvk := obj.GetObjectKind().GroupVersionKind() if len(gvk.Kind) == 0 { - return nil, &gvk, NewMissingKindErr(string(data)) + return nil, &gvk, runtime.NewMissingKindErr(string(data)) } return obj, &gvk, nil } -func (unstructuredJSONScheme) Encode(obj Object, w io.Writer) error { +func (unstructuredJSONScheme) Encode(obj runtime.Object, w io.Writer) error { switch t := obj.(type) { case *Unstructured: return json.NewEncoder(w).Encode(t.Object) @@ -475,7 +521,7 @@ func (unstructuredJSONScheme) Encode(obj Object, w io.Writer) error { t.Object["items"] = items defer func() { delete(t.Object, "items") }() return json.NewEncoder(w).Encode(t.Object) - case *Unknown: + case *runtime.Unknown: // TODO: Unstructured needs to deal with ContentType. _, err := w.Write(t.Raw) return err @@ -484,7 +530,7 @@ func (unstructuredJSONScheme) Encode(obj Object, w io.Writer) error { } } -func (s unstructuredJSONScheme) decode(data []byte) (Object, error) { +func (s unstructuredJSONScheme) decode(data []byte) (runtime.Object, error) { type detector struct { Items gojson.RawMessage } @@ -505,16 +551,16 @@ func (s unstructuredJSONScheme) decode(data []byte) (Object, error) { return unstruct, err } -func (s unstructuredJSONScheme) decodeInto(data []byte, obj Object) error { +func (s unstructuredJSONScheme) decodeInto(data []byte, obj runtime.Object) error { switch x := obj.(type) { case *Unstructured: return s.decodeToUnstructured(data, x) case *UnstructuredList: return s.decodeToList(data, x) - case *VersionedObjects: + case *runtime.VersionedObjects: o, err := s.decode(data) if err == nil { - x.Objects = []Object{o} + x.Objects = []runtime.Object{o} } return err default: @@ -596,7 +642,7 @@ func (UnstructuredObjectConverter) Convert(in, out, context interface{}) error { return nil } -func (UnstructuredObjectConverter) ConvertToVersion(in Object, target GroupVersioner) (Object, error) { +func (UnstructuredObjectConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) { if kind := in.GetObjectKind().GroupVersionKind(); !kind.Empty() { gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{kind}) if !ok { diff --git a/pkg/runtime/unstructured_test.go b/pkg/apis/meta/v1/unstructured/unstructured_test.go similarity index 91% rename from pkg/runtime/unstructured_test.go rename to pkg/apis/meta/v1/unstructured/unstructured_test.go index 40c5423c15f..c8ff60b19da 100644 --- a/pkg/runtime/unstructured_test.go +++ b/pkg/apis/meta/v1/unstructured/unstructured_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package runtime_test +package unstructured_test import ( "fmt" @@ -29,6 +29,7 @@ import ( "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/apimachinery/registered" metav1 "k8s.io/kubernetes/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/apis/meta/v1/unstructured" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/types" ) @@ -49,7 +50,7 @@ func TestDecodeUnstructured(t *testing.T) { Raw: []byte(rawJson), ContentType: runtime.ContentTypeJSON, }, - &runtime.Unstructured{ + &unstructured.Unstructured{ Object: map[string]interface{}{ "kind": "Foo", "apiVersion": "Bar", @@ -58,13 +59,13 @@ func TestDecodeUnstructured(t *testing.T) { }, }, } - if errs := runtime.DecodeList(pl.Items, runtime.UnstructuredJSONScheme); len(errs) == 1 { + if errs := runtime.DecodeList(pl.Items, unstructured.UnstructuredJSONScheme); len(errs) == 1 { t.Fatalf("unexpected error %v", errs) } - if pod, ok := pl.Items[1].(*runtime.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" { + if pod, ok := pl.Items[1].(*unstructured.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" { t.Errorf("object not converted: %#v", pl.Items[1]) } - if pod, ok := pl.Items[2].(*runtime.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" { + if pod, ok := pl.Items[2].(*unstructured.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" { t.Errorf("object not converted: %#v", pl.Items[2]) } } @@ -76,21 +77,21 @@ func TestDecode(t *testing.T) { }{ { json: []byte(`{"apiVersion": "test", "kind": "test_kind"}`), - want: &runtime.Unstructured{ + want: &unstructured.Unstructured{ Object: map[string]interface{}{"apiVersion": "test", "kind": "test_kind"}, }, }, { json: []byte(`{"apiVersion": "test", "kind": "test_list", "items": []}`), - want: &runtime.UnstructuredList{ + want: &unstructured.UnstructuredList{ Object: map[string]interface{}{"apiVersion": "test", "kind": "test_list"}, }, }, { json: []byte(`{"items": [{"metadata": {"name": "object1"}, "apiVersion": "test", "kind": "test_kind"}, {"metadata": {"name": "object2"}, "apiVersion": "test", "kind": "test_kind"}], "apiVersion": "test", "kind": "test_list"}`), - want: &runtime.UnstructuredList{ + want: &unstructured.UnstructuredList{ Object: map[string]interface{}{"apiVersion": "test", "kind": "test_list"}, - Items: []*runtime.Unstructured{ + Items: []*unstructured.Unstructured{ { Object: map[string]interface{}{ "metadata": map[string]interface{}{"name": "object1"}, @@ -111,7 +112,7 @@ func TestDecode(t *testing.T) { } for _, tc := range tcs { - got, _, err := runtime.UnstructuredJSONScheme.Decode(tc.json, nil, nil) + got, _, err := unstructured.UnstructuredJSONScheme.Decode(tc.json, nil, nil) if err != nil { t.Errorf("Unexpected error for %q: %v", string(tc.json), err) continue @@ -124,7 +125,7 @@ func TestDecode(t *testing.T) { } func TestUnstructuredGetters(t *testing.T) { - unstruct := runtime.Unstructured{ + unstruct := unstructured.Unstructured{ Object: map[string]interface{}{ "kind": "test_kind", "apiVersion": "test_version", @@ -240,10 +241,10 @@ func TestUnstructuredGetters(t *testing.T) { } func TestUnstructuredSetters(t *testing.T) { - unstruct := runtime.Unstructured{} + unstruct := unstructured.Unstructured{} trueVar := true - want := runtime.Unstructured{ + want := unstructured.Unstructured{ Object: map[string]interface{}{ "kind": "test_kind", "apiVersion": "test_version", @@ -325,7 +326,7 @@ func TestUnstructuredSetters(t *testing.T) { } func TestUnstructuredListGetters(t *testing.T) { - unstruct := runtime.UnstructuredList{ + unstruct := unstructured.UnstructuredList{ Object: map[string]interface{}{ "kind": "test_kind", "apiVersion": "test_version", @@ -354,9 +355,9 @@ func TestUnstructuredListGetters(t *testing.T) { } func TestUnstructuredListSetters(t *testing.T) { - unstruct := runtime.UnstructuredList{} + unstruct := unstructured.UnstructuredList{} - want := runtime.UnstructuredList{ + want := unstructured.UnstructuredList{ Object: map[string]interface{}{ "kind": "test_kind", "apiVersion": "test_version", @@ -407,11 +408,11 @@ func TestDecodeNumbers(t *testing.T) { } // Round-trip with unstructured codec - unstructuredObj, err := runtime.Decode(runtime.UnstructuredJSONScheme, originalJSON) + unstructuredObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalJSON) if err != nil { t.Fatalf("unexpected error: %v", err) } - roundtripJSON, err := runtime.Encode(runtime.UnstructuredJSONScheme, unstructuredObj) + roundtripJSON, err := runtime.Encode(unstructured.UnstructuredJSONScheme, unstructuredObj) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/pkg/runtime/interfaces.go b/pkg/runtime/interfaces.go index 6edbb2b30a9..0626e1ca0dc 100644 --- a/pkg/runtime/interfaces.go +++ b/pkg/runtime/interfaces.go @@ -235,3 +235,17 @@ type SelfLinker interface { type Object interface { GetObjectKind() schema.ObjectKind } + +// Unstructured objects store values as map[string]interface{}, with only values that can be serialized +// to JSON allowed. +type Unstructured interface { + // IsUnstructuredObject is a marker interface to allow objects that can be serialized but not introspected + // to bypass conversion. + IsUnstructuredObject() + // IsList returns true if this type is a list or matches the list convention - has an array called "items". + IsList() bool + // UnstructuredContent returns a non-nil, mutable map of the contents of this object. Values may be + // []interface{}, map[string]interface{}, or any primitive type. Contents are typically serialized to + // and from JSON. + UnstructuredContent() map[string]interface{} +} diff --git a/pkg/runtime/register.go b/pkg/runtime/register.go index f5ea0118478..f53507a3250 100644 --- a/pkg/runtime/register.go +++ b/pkg/runtime/register.go @@ -30,9 +30,6 @@ func (obj *TypeMeta) GroupVersionKind() schema.GroupVersionKind { func (obj *Unknown) GetObjectKind() schema.ObjectKind { return &obj.TypeMeta } -func (obj *Unstructured) GetObjectKind() schema.ObjectKind { return obj } -func (obj *UnstructuredList) GetObjectKind() schema.ObjectKind { return obj } - // GetObjectKind implements Object for VersionedObjects, returning an empty ObjectKind // interface if no objects are provided, or the ObjectKind interface of the object in the // highest array position. diff --git a/pkg/runtime/serializer/versioning/versioning.go b/pkg/runtime/serializer/versioning/versioning.go index c6f50053830..d8fd62d53a7 100644 --- a/pkg/runtime/serializer/versioning/versioning.go +++ b/pkg/runtime/serializer/versioning/versioning.go @@ -181,7 +181,7 @@ func (c *codec) Decode(data []byte, defaultGVK *schema.GroupVersionKind, into ru // conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is. func (c *codec) Encode(obj runtime.Object, w io.Writer) error { switch obj.(type) { - case *runtime.Unknown, *runtime.Unstructured, *runtime.UnstructuredList: + case *runtime.Unknown, runtime.Unstructured: return c.encoder.Encode(obj, w) } diff --git a/pkg/runtime/types.go b/pkg/runtime/types.go index 9a2a1b6bc92..f972c5e697a 100644 --- a/pkg/runtime/types.go +++ b/pkg/runtime/types.go @@ -122,17 +122,6 @@ type Unknown struct { ContentType string `protobuf:"bytes,4,opt,name=contentType"` } -// Unstructured allows objects that do not have Golang structs registered to be manipulated -// generically. This can be used to deal with the API objects from a plug-in. Unstructured -// objects still have functioning TypeMeta features-- kind, version, etc. -// TODO: Make this object have easy access to field based accessors and settors for -// metadata and field mutatation. -type Unstructured struct { - // Object is a JSON compatible map with string, float, int, []interface{}, or map[string]interface{} - // children. - Object map[string]interface{} -} - // VersionedObjects is used by Decoders to give callers a way to access all versions // of an object during the decoding process. type VersionedObjects struct {