Move unstructured to its own package under v1

It is a versioned type.
This commit is contained in:
Clayton Coleman 2016-11-02 21:54:13 -04:00
parent bda57b8fb6
commit 8eb3e9a518
No known key found for this signature in database
GPG Key ID: 3D16906B4F1C5CB3
8 changed files with 102 additions and 54 deletions

View File

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"k8s.io/kubernetes/pkg/apis/meta/v1/unstructured"
"k8s.io/kubernetes/pkg/conversion" "k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/runtime" "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 // 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 // This is a list type for recognition, but other Items type methods will fail on it
// and give you errors. // and give you errors.
if unstructured, ok := obj.(*runtime.Unstructured); ok { if unstructured, ok := obj.(*unstructured.Unstructured); ok {
_, ok := unstructured.Object["items"] _, ok := unstructured.Object["items"]
return ok return ok
} }

View File

@ -17,7 +17,7 @@ limitations under the License.
package meta package meta
import ( import (
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/apis/meta/v1/unstructured"
"k8s.io/kubernetes/pkg/runtime/schema" "k8s.io/kubernetes/pkg/runtime/schema"
) )
@ -25,7 +25,7 @@ import (
// dealing with runtime.Unstructured objects. // dealing with runtime.Unstructured objects.
func InterfacesForUnstructured(schema.GroupVersion) (*VersionInterfaces, error) { func InterfacesForUnstructured(schema.GroupVersion) (*VersionInterfaces, error) {
return &VersionInterfaces{ return &VersionInterfaces{
ObjectConvertor: &runtime.UnstructuredObjectConverter{}, ObjectConvertor: &unstructured.UnstructuredObjectConverter{},
MetadataAccessor: NewAccessor(), MetadataAccessor: NewAccessor(),
}, nil }, nil
} }

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package runtime package unstructured
import ( import (
"bytes" "bytes"
@ -26,13 +26,59 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/kubernetes/pkg/api/meta/metatypes"
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1" metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/schema" "k8s.io/kubernetes/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/json" "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 // MarshalJSON ensures that the unstructured object produces proper
// JSON when passed to Go's standard JSON library. // JSON when passed to Go's standard JSON library.
func (u *Unstructured) MarshalJSON() ([]byte, error) { 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...) setNestedMap(u.Object, value, fields...)
} }
func extractOwnerReference(src interface{}) metatypes.OwnerReference { func extractOwnerReference(src interface{}) metav1.OwnerReference {
v := src.(map[string]interface{}) v := src.(map[string]interface{})
controllerPtr, ok := (getNestedField(v, "controller")).(*bool) controllerPtr, ok := (getNestedField(v, "controller")).(*bool)
if !ok { if !ok {
@ -153,7 +199,7 @@ func extractOwnerReference(src interface{}) metatypes.OwnerReference {
controllerPtr = &controller controllerPtr = &controller
} }
} }
return metatypes.OwnerReference{ return metav1.OwnerReference{
Kind: getNestedString(v, "kind"), Kind: getNestedString(v, "kind"),
Name: getNestedString(v, "name"), Name: getNestedString(v, "name"),
APIVersion: getNestedString(v, "apiVersion"), 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{}) ret := make(map[string]interface{})
controllerPtr := src.Controller controllerPtr := src.Controller
if controllerPtr != nil { if controllerPtr != nil {
@ -202,20 +248,20 @@ func getOwnerReferences(object map[string]interface{}) ([]map[string]interface{}
return ownerReferences, nil return ownerReferences, nil
} }
func (u *Unstructured) GetOwnerReferences() []metatypes.OwnerReference { func (u *Unstructured) GetOwnerReferences() []metav1.OwnerReference {
original, err := getOwnerReferences(u.Object) original, err := getOwnerReferences(u.Object)
if err != nil { if err != nil {
glog.V(6).Info(err) glog.V(6).Info(err)
return nil return nil
} }
ret := make([]metatypes.OwnerReference, 0, len(original)) ret := make([]metav1.OwnerReference, 0, len(original))
for i := 0; i < len(original); i++ { for i := 0; i < len(original); i++ {
ret = append(ret, extractOwnerReference(original[i])) ret = append(ret, extractOwnerReference(original[i]))
} }
return ret 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)) var newReferences = make([]map[string]interface{}, 0, len(references))
for i := 0; i < len(references); i++ { for i := 0; i < len(references); i++ {
newReferences = append(newReferences, setOwnerReference(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 // UnstructuredJSONScheme is capable of converting JSON data into the Unstructured
// type, which can be used for generic access to objects without a predefined scheme. // type, which can be used for generic access to objects without a predefined scheme.
// TODO: move into serializer/json. // TODO: move into serializer/json.
var UnstructuredJSONScheme Codec = unstructuredJSONScheme{} var UnstructuredJSONScheme runtime.Codec = unstructuredJSONScheme{}
type unstructuredJSONScheme struct{} 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 var err error
if obj != nil { if obj != nil {
err = s.decodeInto(data, obj) err = s.decodeInto(data, obj)
@ -457,13 +503,13 @@ func (s unstructuredJSONScheme) Decode(data []byte, _ *schema.GroupVersionKind,
gvk := obj.GetObjectKind().GroupVersionKind() gvk := obj.GetObjectKind().GroupVersionKind()
if len(gvk.Kind) == 0 { if len(gvk.Kind) == 0 {
return nil, &gvk, NewMissingKindErr(string(data)) return nil, &gvk, runtime.NewMissingKindErr(string(data))
} }
return obj, &gvk, nil 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) { switch t := obj.(type) {
case *Unstructured: case *Unstructured:
return json.NewEncoder(w).Encode(t.Object) return json.NewEncoder(w).Encode(t.Object)
@ -475,7 +521,7 @@ func (unstructuredJSONScheme) Encode(obj Object, w io.Writer) error {
t.Object["items"] = items t.Object["items"] = items
defer func() { delete(t.Object, "items") }() defer func() { delete(t.Object, "items") }()
return json.NewEncoder(w).Encode(t.Object) return json.NewEncoder(w).Encode(t.Object)
case *Unknown: case *runtime.Unknown:
// TODO: Unstructured needs to deal with ContentType. // TODO: Unstructured needs to deal with ContentType.
_, err := w.Write(t.Raw) _, err := w.Write(t.Raw)
return err 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 { type detector struct {
Items gojson.RawMessage Items gojson.RawMessage
} }
@ -505,16 +551,16 @@ func (s unstructuredJSONScheme) decode(data []byte) (Object, error) {
return unstruct, err 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) { switch x := obj.(type) {
case *Unstructured: case *Unstructured:
return s.decodeToUnstructured(data, x) return s.decodeToUnstructured(data, x)
case *UnstructuredList: case *UnstructuredList:
return s.decodeToList(data, x) return s.decodeToList(data, x)
case *VersionedObjects: case *runtime.VersionedObjects:
o, err := s.decode(data) o, err := s.decode(data)
if err == nil { if err == nil {
x.Objects = []Object{o} x.Objects = []runtime.Object{o}
} }
return err return err
default: default:
@ -596,7 +642,7 @@ func (UnstructuredObjectConverter) Convert(in, out, context interface{}) error {
return nil 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() { if kind := in.GetObjectKind().GroupVersionKind(); !kind.Empty() {
gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{kind}) gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{kind})
if !ok { if !ok {

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package runtime_test package unstructured_test
import ( import (
"fmt" "fmt"
@ -29,6 +29,7 @@ import (
"k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apimachinery/registered"
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1" 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/runtime"
"k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/types"
) )
@ -49,7 +50,7 @@ func TestDecodeUnstructured(t *testing.T) {
Raw: []byte(rawJson), Raw: []byte(rawJson),
ContentType: runtime.ContentTypeJSON, ContentType: runtime.ContentTypeJSON,
}, },
&runtime.Unstructured{ &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"kind": "Foo", "kind": "Foo",
"apiVersion": "Bar", "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) 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]) 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]) 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"}`), json: []byte(`{"apiVersion": "test", "kind": "test_kind"}`),
want: &runtime.Unstructured{ want: &unstructured.Unstructured{
Object: map[string]interface{}{"apiVersion": "test", "kind": "test_kind"}, Object: map[string]interface{}{"apiVersion": "test", "kind": "test_kind"},
}, },
}, },
{ {
json: []byte(`{"apiVersion": "test", "kind": "test_list", "items": []}`), json: []byte(`{"apiVersion": "test", "kind": "test_list", "items": []}`),
want: &runtime.UnstructuredList{ want: &unstructured.UnstructuredList{
Object: map[string]interface{}{"apiVersion": "test", "kind": "test_list"}, 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"}`), 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"}, Object: map[string]interface{}{"apiVersion": "test", "kind": "test_list"},
Items: []*runtime.Unstructured{ Items: []*unstructured.Unstructured{
{ {
Object: map[string]interface{}{ Object: map[string]interface{}{
"metadata": map[string]interface{}{"name": "object1"}, "metadata": map[string]interface{}{"name": "object1"},
@ -111,7 +112,7 @@ func TestDecode(t *testing.T) {
} }
for _, tc := range tcs { 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 { if err != nil {
t.Errorf("Unexpected error for %q: %v", string(tc.json), err) t.Errorf("Unexpected error for %q: %v", string(tc.json), err)
continue continue
@ -124,7 +125,7 @@ func TestDecode(t *testing.T) {
} }
func TestUnstructuredGetters(t *testing.T) { func TestUnstructuredGetters(t *testing.T) {
unstruct := runtime.Unstructured{ unstruct := unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"kind": "test_kind", "kind": "test_kind",
"apiVersion": "test_version", "apiVersion": "test_version",
@ -240,10 +241,10 @@ func TestUnstructuredGetters(t *testing.T) {
} }
func TestUnstructuredSetters(t *testing.T) { func TestUnstructuredSetters(t *testing.T) {
unstruct := runtime.Unstructured{} unstruct := unstructured.Unstructured{}
trueVar := true trueVar := true
want := runtime.Unstructured{ want := unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"kind": "test_kind", "kind": "test_kind",
"apiVersion": "test_version", "apiVersion": "test_version",
@ -325,7 +326,7 @@ func TestUnstructuredSetters(t *testing.T) {
} }
func TestUnstructuredListGetters(t *testing.T) { func TestUnstructuredListGetters(t *testing.T) {
unstruct := runtime.UnstructuredList{ unstruct := unstructured.UnstructuredList{
Object: map[string]interface{}{ Object: map[string]interface{}{
"kind": "test_kind", "kind": "test_kind",
"apiVersion": "test_version", "apiVersion": "test_version",
@ -354,9 +355,9 @@ func TestUnstructuredListGetters(t *testing.T) {
} }
func TestUnstructuredListSetters(t *testing.T) { func TestUnstructuredListSetters(t *testing.T) {
unstruct := runtime.UnstructuredList{} unstruct := unstructured.UnstructuredList{}
want := runtime.UnstructuredList{ want := unstructured.UnstructuredList{
Object: map[string]interface{}{ Object: map[string]interface{}{
"kind": "test_kind", "kind": "test_kind",
"apiVersion": "test_version", "apiVersion": "test_version",
@ -407,11 +408,11 @@ func TestDecodeNumbers(t *testing.T) {
} }
// Round-trip with unstructured codec // Round-trip with unstructured codec
unstructuredObj, err := runtime.Decode(runtime.UnstructuredJSONScheme, originalJSON) unstructuredObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalJSON)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
roundtripJSON, err := runtime.Encode(runtime.UnstructuredJSONScheme, unstructuredObj) roundtripJSON, err := runtime.Encode(unstructured.UnstructuredJSONScheme, unstructuredObj)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }

View File

@ -235,3 +235,17 @@ type SelfLinker interface {
type Object interface { type Object interface {
GetObjectKind() schema.ObjectKind 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{}
}

View File

@ -30,9 +30,6 @@ func (obj *TypeMeta) GroupVersionKind() schema.GroupVersionKind {
func (obj *Unknown) GetObjectKind() schema.ObjectKind { return &obj.TypeMeta } 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 // GetObjectKind implements Object for VersionedObjects, returning an empty ObjectKind
// interface if no objects are provided, or the ObjectKind interface of the object in the // interface if no objects are provided, or the ObjectKind interface of the object in the
// highest array position. // highest array position.

View File

@ -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. // conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is.
func (c *codec) Encode(obj runtime.Object, w io.Writer) error { func (c *codec) Encode(obj runtime.Object, w io.Writer) error {
switch obj.(type) { switch obj.(type) {
case *runtime.Unknown, *runtime.Unstructured, *runtime.UnstructuredList: case *runtime.Unknown, runtime.Unstructured:
return c.encoder.Encode(obj, w) return c.encoder.Encode(obj, w)
} }

View File

@ -122,17 +122,6 @@ type Unknown struct {
ContentType string `protobuf:"bytes,4,opt,name=contentType"` 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 // VersionedObjects is used by Decoders to give callers a way to access all versions
// of an object during the decoding process. // of an object during the decoding process.
type VersionedObjects struct { type VersionedObjects struct {