Merge pull request #24751 from krousey/meta_unstructured

Automatic merge from submit-queue

Redo Unstructured to have accessor methods

Add accessor methods that implement pkg/api/unversioned.ObjectKind,
pkg/api/meta.Object, pkg/api/meta.Type and pkg/api/meta.List.

Removed the convenience fields since writing to them was not reflected
in serialized JSON.
pull/6/head
k8s-merge-robot 2016-05-04 04:22:34 -07:00
commit bc010d76cc
9 changed files with 460 additions and 78 deletions

View File

@ -177,3 +177,5 @@ type RESTMapper interface {
AliasesForResource(resource string) ([]string, bool)
ResourceSingularizer(resource string) (singular string, err error)
}
var _ Object = &runtime.Unstructured{}

View File

@ -157,12 +157,12 @@ func (rc *ResourceClient) Create(obj *runtime.Unstructured) (*runtime.Unstructur
// Update updates the provided resource.
func (rc *ResourceClient) Update(obj *runtime.Unstructured) (*runtime.Unstructured, error) {
result := new(runtime.Unstructured)
if len(obj.Name) == 0 {
if len(obj.GetName()) == 0 {
return result, errors.New("object missing name")
}
err := rc.namespace(rc.cl.Put()).
Resource(rc.resource.Name).
Name(obj.Name).
Name(obj.GetName()).
Body(obj).
Do().
Into(result)

View File

@ -45,11 +45,6 @@ func getListJSON(version, kind string, items ...[]byte) []byte {
func getObject(version, kind, name string) *runtime.Unstructured {
return &runtime.Unstructured{
TypeMeta: runtime.TypeMeta{
APIVersion: version,
Kind: kind,
},
Name: name,
Object: map[string]interface{}{
"apiVersion": version,
"kind": kind,
@ -88,9 +83,9 @@ func TestList(t *testing.T) {
getJSON("vTest", "rTest", "item1"),
getJSON("vTest", "rTest", "item2")),
want: &runtime.UnstructuredList{
TypeMeta: runtime.TypeMeta{
APIVersion: "vTest",
Kind: "rTestList",
Object: map[string]interface{}{
"apiVersion": "vTest",
"kind": "rTestList",
},
Items: []*runtime.Unstructured{
getObject("vTest", "rTest", "item1"),
@ -106,9 +101,9 @@ func TestList(t *testing.T) {
getJSON("vTest", "rTest", "item1"),
getJSON("vTest", "rTest", "item2")),
want: &runtime.UnstructuredList{
TypeMeta: runtime.TypeMeta{
APIVersion: "vTest",
Kind: "rTestList",
Object: map[string]interface{}{
"apiVersion": "vTest",
"kind": "rTestList",
},
Items: []*runtime.Unstructured{
getObject("vTest", "rTest", "item1"),

View File

@ -236,7 +236,7 @@ func deleteEachItem(
}
apiResource := unversioned.APIResource{Name: gvr.Resource, Namespaced: true}
for _, item := range unstructuredList.Items {
if err = dynamicClient.Resource(&apiResource, namespace).Delete(item.Name, nil); err != nil && !errors.IsNotFound(err) && !errors.IsMethodNotSupported(err) {
if err = dynamicClient.Resource(&apiResource, namespace).Delete(item.GetName(), nil); err != nil && !errors.IsNotFound(err) && !errors.IsMethodNotSupported(err) {
return err
}
}

View File

@ -33,7 +33,13 @@ func TestDecodeList(t *testing.T) {
Raw: []byte(`{"kind":"Pod","apiVersion":"` + testapi.Default.GroupVersion().String() + `","metadata":{"name":"test"}}`),
ContentType: runtime.ContentTypeJSON,
},
&runtime.Unstructured{TypeMeta: runtime.TypeMeta{Kind: "Foo", APIVersion: "Bar"}, Object: map[string]interface{}{"test": "value"}},
&runtime.Unstructured{
Object: map[string]interface{}{
"kind": "Foo",
"apiVersion": "Bar",
"test": "value",
},
},
},
}
if errs := runtime.DecodeList(pl.Items, testapi.Default.Codec()); len(errs) != 0 {

View File

@ -30,9 +30,10 @@ func (obj *TypeMeta) GroupVersionKind() *unversioned.GroupVersionKind {
return unversioned.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
func (obj *Unknown) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Unstructured) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *UnstructuredList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Unknown) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Unstructured) GetObjectKind() unversioned.ObjectKind { return obj }
func (obj *UnstructuredList) GetObjectKind() unversioned.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

View File

@ -16,6 +16,11 @@ limitations under the License.
package runtime
import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/types"
)
// Note that the types provided in this file are not versioned and are intended to be
// safe to use from within all versions of every API object.
@ -120,26 +125,259 @@ type Unknown struct {
// TODO: Make this object have easy access to field based accessors and settors for
// metadata and field mutatation.
type Unstructured struct {
TypeMeta `json:",inline"`
// Name is populated from metadata (if present) upon deserialization
Name string
// Object is a JSON compatible map with string, float, int, []interface{}, or map[string]interface{}
// children.
Object map[string]interface{}
}
func getNestedField(obj map[string]interface{}, fields ...string) interface{} {
var val interface{} = obj
for _, field := range fields {
if _, ok := val.(map[string]interface{}); !ok {
return nil
}
val = val.(map[string]interface{})[field]
}
return val
}
func getNestedString(obj map[string]interface{}, fields ...string) string {
if str, ok := getNestedField(obj, fields...).(string); ok {
return str
}
return ""
}
func getNestedMap(obj map[string]interface{}, fields ...string) map[string]string {
if m, ok := getNestedField(obj, fields...).(map[string]interface{}); ok {
strMap := make(map[string]string, len(m))
for k, v := range m {
if str, ok := v.(string); ok {
strMap[k] = str
}
}
return strMap
}
return nil
}
func setNestedField(obj map[string]interface{}, value interface{}, fields ...string) {
m := obj
if len(fields) > 1 {
for _, field := range fields[0 : len(fields)-1] {
if _, ok := m[field].(map[string]interface{}); !ok {
m[field] = make(map[string]interface{})
}
m = m[field].(map[string]interface{})
}
}
m[fields[len(fields)-1]] = value
}
func setNestedMap(obj map[string]interface{}, value map[string]string, fields ...string) {
m := make(map[string]interface{}, len(value))
for k, v := range value {
m[k] = v
}
setNestedField(obj, m, fields...)
}
func (u *Unstructured) setNestedField(value interface{}, fields ...string) {
if u.Object == nil {
u.Object = make(map[string]interface{})
}
setNestedField(u.Object, value, fields...)
}
func (u *Unstructured) setNestedMap(value map[string]string, fields ...string) {
if u.Object == nil {
u.Object = make(map[string]interface{})
}
setNestedMap(u.Object, value, fields...)
}
func (u *Unstructured) GetAPIVersion() string {
return getNestedString(u.Object, "apiVersion")
}
func (u *Unstructured) SetAPIVersion(version string) {
u.setNestedField(version, "apiVersion")
}
func (u *Unstructured) GetKind() string {
return getNestedString(u.Object, "kind")
}
func (u *Unstructured) SetKind(kind string) {
u.setNestedField(kind, "kind")
}
func (u *Unstructured) GetNamespace() string {
return getNestedString(u.Object, "metadata", "namespace")
}
func (u *Unstructured) SetNamespace(namespace string) {
u.setNestedField(namespace, "metadata", "namespace")
}
func (u *Unstructured) GetName() string {
return getNestedString(u.Object, "metadata", "name")
}
func (u *Unstructured) SetName(name string) {
u.setNestedField(name, "metadata", "name")
}
func (u *Unstructured) GetGenerateName() string {
return getNestedString(u.Object, "metadata", "generateName")
}
func (u *Unstructured) SetGenerateName(name string) {
u.setNestedField(name, "metadata", "generateName")
}
func (u *Unstructured) GetUID() types.UID {
return types.UID(getNestedString(u.Object, "metadata", "uid"))
}
func (u *Unstructured) SetUID(uid types.UID) {
u.setNestedField(string(uid), "metadata", "uid")
}
func (u *Unstructured) GetResourceVersion() string {
return getNestedString(u.Object, "metadata", "resourceVersion")
}
func (u *Unstructured) SetResourceVersion(version string) {
u.setNestedField(version, "metadata", "resourceVersion")
}
func (u *Unstructured) GetSelfLink() string {
return getNestedString(u.Object, "metadata", "selfLink")
}
func (u *Unstructured) SetSelfLink(selfLink string) {
u.setNestedField(selfLink, "metadata", "selfLink")
}
func (u *Unstructured) GetCreationTimestamp() unversioned.Time {
var timestamp unversioned.Time
timestamp.UnmarshalQueryParameter(getNestedString(u.Object, "metadata", "creationTimestamp"))
return timestamp
}
func (u *Unstructured) SetCreationTimestamp(timestamp unversioned.Time) {
ts, _ := timestamp.MarshalQueryParameter()
u.setNestedField(ts, "metadata", "creationTimestamp")
}
func (u *Unstructured) GetDeletionTimestamp() *unversioned.Time {
var timestamp unversioned.Time
timestamp.UnmarshalQueryParameter(getNestedString(u.Object, "metadata", "deletionTimestamp"))
if timestamp.IsZero() {
return nil
}
return &timestamp
}
func (u *Unstructured) SetDeletionTimestamp(timestamp *unversioned.Time) {
ts, _ := timestamp.MarshalQueryParameter()
u.setNestedField(ts, "metadata", "deletionTimestamp")
}
func (u *Unstructured) GetLabels() map[string]string {
return getNestedMap(u.Object, "metadata", "labels")
}
func (u *Unstructured) SetLabels(labels map[string]string) {
u.setNestedMap(labels, "metadata", "labels")
}
func (u *Unstructured) GetAnnotations() map[string]string {
return getNestedMap(u.Object, "metadata", "annotations")
}
func (u *Unstructured) SetAnnotations(annotations map[string]string) {
u.setNestedMap(annotations, "metadata", "annotations")
}
func (u *Unstructured) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
u.SetAPIVersion(gvk.GroupVersion().String())
u.SetKind(gvk.Kind)
}
func (u *Unstructured) GroupVersionKind() *unversioned.GroupVersionKind {
gv, err := unversioned.ParseGroupVersion(u.GetAPIVersion())
if err != nil {
return nil
}
gvk := gv.WithKind(u.GetKind())
return &gvk
}
// UnstructuredList allows lists that do not have Golang structs
// registered to be manipulated generically. This can be used to deal
// with the API lists from a plug-in.
type UnstructuredList struct {
TypeMeta `json:",inline"`
Object map[string]interface{}
// Items is a list of unstructured objects.
Items []*Unstructured `json:"items"`
}
func (u *UnstructuredList) setNestedField(value interface{}, fields ...string) {
if u.Object == nil {
u.Object = make(map[string]interface{})
}
setNestedField(u.Object, value, fields...)
}
func (u *UnstructuredList) GetAPIVersion() string {
return getNestedString(u.Object, "apiVersion")
}
func (u *UnstructuredList) SetAPIVersion(version string) {
u.setNestedField(version, "apiVersion")
}
func (u *UnstructuredList) GetKind() string {
return getNestedString(u.Object, "kind")
}
func (u *UnstructuredList) SetKind(kind string) {
u.setNestedField(kind, "kind")
}
func (u *UnstructuredList) GetResourceVersion() string {
return getNestedString(u.Object, "metadata", "resourceVersion")
}
func (u *UnstructuredList) SetResourceVersion(version string) {
u.setNestedField(version, "metadata", "resourceVersion")
}
func (u *UnstructuredList) GetSelfLink() string {
return getNestedString(u.Object, "metadata", "selfLink")
}
func (u *UnstructuredList) SetSelfLink(selfLink string) {
u.setNestedField(selfLink, "metadata", "selfLink")
}
func (u *UnstructuredList) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
u.SetAPIVersion(gvk.GroupVersion().String())
u.SetKind(gvk.Kind)
}
func (u *UnstructuredList) GroupVersionKind() *unversioned.GroupVersionKind {
gv, err := unversioned.ParseGroupVersion(u.GetAPIVersion())
if err != nil {
return nil
}
gvk := gv.WithKind(u.GetKind())
return &gvk
}
// VersionedObjects is used by Decoders to give callers a way to access all versions
// of an object during the decoding process.
type VersionedObjects struct {

View File

@ -56,17 +56,13 @@ func (unstructuredJSONScheme) EncodeToStream(obj Object, w io.Writer, overrides
case *Unstructured:
return json.NewEncoder(w).Encode(t.Object)
case *UnstructuredList:
type encodeList struct {
TypeMeta `json:",inline"`
Items []map[string]interface{} `json:"items"`
}
eList := encodeList{
TypeMeta: t.TypeMeta,
}
var items []map[string]interface{}
for _, i := range t.Items {
eList.Items = append(eList.Items, i.Object)
items = append(items, i.Object)
}
return json.NewEncoder(w).Encode(eList)
t.Object["items"] = items
defer func() { delete(t.Object, "items") }()
return json.NewEncoder(w).Encode(t.Object)
case *Unknown:
// TODO: Unstructured needs to deal with ContentType.
_, err := w.Write(t.Raw)
@ -113,25 +109,6 @@ func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstru
return err
}
if v, ok := m["kind"]; ok {
if s, ok := v.(string); ok {
unstruct.Kind = s
}
}
if v, ok := m["apiVersion"]; ok {
if s, ok := v.(string); ok {
unstruct.APIVersion = s
}
}
if metadata, ok := m["metadata"]; ok {
if metadata, ok := metadata.(map[string]interface{}); ok {
if name, ok := metadata["name"]; ok {
if name, ok := name.(string); ok {
unstruct.Name = name
}
}
}
}
unstruct.Object = m
return nil
@ -139,8 +116,7 @@ func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstru
func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList) error {
type decodeList struct {
TypeMeta `json:",inline"`
Items []gojson.RawMessage
Items []gojson.RawMessage
}
var dList decodeList
@ -148,7 +124,11 @@ func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList
return err
}
list.TypeMeta = dList.TypeMeta
if err := json.Unmarshal(data, &list.Object); err != nil {
return err
}
delete(list.Object, "items")
list.Items = nil
for _, i := range dList.Items {
unstruct := &Unstructured{}

View File

@ -21,11 +21,14 @@ import (
"reflect"
"strings"
"testing"
"time"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/types"
)
func TestDecodeUnstructured(t *testing.T) {
@ -44,7 +47,13 @@ func TestDecodeUnstructured(t *testing.T) {
Raw: []byte(rawJson),
ContentType: runtime.ContentTypeJSON,
},
&runtime.Unstructured{TypeMeta: runtime.TypeMeta{Kind: "Foo", APIVersion: "Bar"}, Object: map[string]interface{}{"test": "value"}},
&runtime.Unstructured{
Object: map[string]interface{}{
"kind": "Foo",
"apiVersion": "Bar",
"test": "value",
},
},
},
}
if errs := runtime.DecodeList(pl.Items, runtime.UnstructuredJSONScheme); len(errs) == 1 {
@ -66,36 +75,21 @@ func TestDecode(t *testing.T) {
{
json: []byte(`{"apiVersion": "test", "kind": "test_kind"}`),
want: &runtime.Unstructured{
TypeMeta: runtime.TypeMeta{
APIVersion: "test",
Kind: "test_kind",
},
Object: map[string]interface{}{"apiVersion": "test", "kind": "test_kind"},
},
},
{
json: []byte(`{"apiVersion": "test", "kind": "test_list", "items": []}`),
want: &runtime.UnstructuredList{
TypeMeta: runtime.TypeMeta{
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"}`),
want: &runtime.UnstructuredList{
TypeMeta: runtime.TypeMeta{
APIVersion: "test",
Kind: "test_list",
},
Object: map[string]interface{}{"apiVersion": "test", "kind": "test_list"},
Items: []*runtime.Unstructured{
{
TypeMeta: runtime.TypeMeta{
APIVersion: "test",
Kind: "test_kind",
},
Name: "object1",
Object: map[string]interface{}{
"metadata": map[string]interface{}{"name": "object1"},
"apiVersion": "test",
@ -103,11 +97,6 @@ func TestDecode(t *testing.T) {
},
},
{
TypeMeta: runtime.TypeMeta{
APIVersion: "test",
Kind: "test_kind",
},
Name: "object2",
Object: map[string]interface{}{
"metadata": map[string]interface{}{"name": "object2"},
"apiVersion": "test",
@ -132,6 +121,177 @@ func TestDecode(t *testing.T) {
}
}
func TestUnstructuredGetters(t *testing.T) {
unstruct := runtime.Unstructured{
Object: map[string]interface{}{
"kind": "test_kind",
"apiVersion": "test_version",
"metadata": map[string]interface{}{
"name": "test_name",
"namespace": "test_namespace",
"generateName": "test_generateName",
"uid": "test_uid",
"resourceVersion": "test_resourceVersion",
"selfLink": "test_selfLink",
"creationTimestamp": "2009-11-10T23:00:00Z",
"deletionTimestamp": "2010-11-10T23:00:00Z",
"labels": map[string]interface{}{
"test_label": "test_value",
},
"annotations": map[string]interface{}{
"test_annotation": "test_value",
},
},
},
}
if got, want := unstruct.GetAPIVersion(), "test_version"; got != want {
t.Errorf("GetAPIVersions() = %s, want %s", got, want)
}
if got, want := unstruct.GetKind(), "test_kind"; got != want {
t.Errorf("GetKind() = %s, want %s", got, want)
}
if got, want := unstruct.GetNamespace(), "test_namespace"; got != want {
t.Errorf("GetNamespace() = %s, want %s", got, want)
}
if got, want := unstruct.GetName(), "test_name"; got != want {
t.Errorf("GetName() = %s, want %s", got, want)
}
if got, want := unstruct.GetGenerateName(), "test_generateName"; got != want {
t.Errorf("GetGenerateName() = %s, want %s", got, want)
}
if got, want := unstruct.GetUID(), types.UID("test_uid"); got != want {
t.Errorf("GetUID() = %s, want %s", got, want)
}
if got, want := unstruct.GetResourceVersion(), "test_resourceVersion"; got != want {
t.Errorf("GetResourceVersion() = %s, want %s", got, want)
}
if got, want := unstruct.GetSelfLink(), "test_selfLink"; got != want {
t.Errorf("GetSelfLink() = %s, want %s", got, want)
}
if got, want := unstruct.GetCreationTimestamp(), unversioned.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC); !got.Equal(want) {
t.Errorf("GetCreationTimestamp() = %s, want %s", got, want)
}
if got, want := unstruct.GetDeletionTimestamp(), unversioned.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC); got == nil || !got.Equal(want) {
t.Errorf("GetDeletionTimestamp() = %s, want %s", got, want)
}
if got, want := unstruct.GetLabels(), map[string]string{"test_label": "test_value"}; !reflect.DeepEqual(got, want) {
t.Errorf("GetLabels() = %s, want %s", got, want)
}
if got, want := unstruct.GetAnnotations(), map[string]string{"test_annotation": "test_value"}; !reflect.DeepEqual(got, want) {
t.Errorf("GetAnnotations() = %s, want %s", got, want)
}
}
func TestUnstructuredSetters(t *testing.T) {
unstruct := runtime.Unstructured{}
want := runtime.Unstructured{
Object: map[string]interface{}{
"kind": "test_kind",
"apiVersion": "test_version",
"metadata": map[string]interface{}{
"name": "test_name",
"namespace": "test_namespace",
"generateName": "test_generateName",
"uid": "test_uid",
"resourceVersion": "test_resourceVersion",
"selfLink": "test_selfLink",
"creationTimestamp": "2009-11-10T23:00:00Z",
"deletionTimestamp": "2010-11-10T23:00:00Z",
"labels": map[string]interface{}{
"test_label": "test_value",
},
"annotations": map[string]interface{}{
"test_annotation": "test_value",
},
},
},
}
unstruct.SetAPIVersion("test_version")
unstruct.SetKind("test_kind")
unstruct.SetNamespace("test_namespace")
unstruct.SetName("test_name")
unstruct.SetGenerateName("test_generateName")
unstruct.SetUID(types.UID("test_uid"))
unstruct.SetResourceVersion("test_resourceVersion")
unstruct.SetSelfLink("test_selfLink")
unstruct.SetCreationTimestamp(unversioned.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC))
date := unversioned.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)
unstruct.SetDeletionTimestamp(&date)
unstruct.SetLabels(map[string]string{"test_label": "test_value"})
unstruct.SetAnnotations(map[string]string{"test_annotation": "test_value"})
if !reflect.DeepEqual(unstruct, want) {
t.Errorf("Wanted: \n%s\n Got:\n%s", unstruct, want)
}
}
func TestUnstructuredListGetters(t *testing.T) {
unstruct := runtime.UnstructuredList{
Object: map[string]interface{}{
"kind": "test_kind",
"apiVersion": "test_version",
"metadata": map[string]interface{}{
"resourceVersion": "test_resourceVersion",
"selfLink": "test_selfLink",
},
},
}
if got, want := unstruct.GetAPIVersion(), "test_version"; got != want {
t.Errorf("GetAPIVersions() = %s, want %s", got, want)
}
if got, want := unstruct.GetKind(), "test_kind"; got != want {
t.Errorf("GetKind() = %s, want %s", got, want)
}
if got, want := unstruct.GetResourceVersion(), "test_resourceVersion"; got != want {
t.Errorf("GetResourceVersion() = %s, want %s", got, want)
}
if got, want := unstruct.GetSelfLink(), "test_selfLink"; got != want {
t.Errorf("GetSelfLink() = %s, want %s", got, want)
}
}
func TestUnstructuredListSetters(t *testing.T) {
unstruct := runtime.UnstructuredList{}
want := runtime.UnstructuredList{
Object: map[string]interface{}{
"kind": "test_kind",
"apiVersion": "test_version",
"metadata": map[string]interface{}{
"resourceVersion": "test_resourceVersion",
"selfLink": "test_selfLink",
},
},
}
unstruct.SetAPIVersion("test_version")
unstruct.SetKind("test_kind")
unstruct.SetResourceVersion("test_resourceVersion")
unstruct.SetSelfLink("test_selfLink")
if !reflect.DeepEqual(unstruct, want) {
t.Errorf("Wanted: \n%s\n Got:\n%s", unstruct, want)
}
}
func TestDecodeNumbers(t *testing.T) {
// Start with a valid pod