Unverified Commit c3f31376 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #51940 from atlassian/unstructured-helpers

Automatic merge from submit-queue (batch tested with PRs 54787, 51940). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Useful helper functions for Unstructured **Which issue this PR fixes**: Fixes #40790 **Release note**: ```release-note NONE ``` /kind feature /sig api-machinery /area client-libraries /assign @sttts @liggitt
parents 5ad58228 7c10cbc6
......@@ -8,21 +8,29 @@ load(
go_test(
name = "go_default_test",
srcs = ["unstructured_test.go"],
srcs = [
"helpers_test.go",
"unstructured_list_test.go",
],
importpath = "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
library = ":go_default_library",
deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"],
deps = [
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/github.com/stretchr/testify/require:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"helpers.go",
"unstructured.go",
"unstructured_list.go",
"zz_generated.deepcopy.go",
],
importpath = "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion/unstructured:go_default_library",
......
/*
Copyright 2017 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 unstructured
import (
"io/ioutil"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
// TestCodecOfUnstructuredList tests that there are no data races in Encode().
// i.e. that it does not mutate the object being encoded.
func TestCodecOfUnstructuredList(t *testing.T) {
var wg sync.WaitGroup
concurrency := 10
list := UnstructuredList{
Object: map[string]interface{}{},
}
wg.Add(concurrency)
for i := 0; i < concurrency; i++ {
go func() {
defer wg.Done()
assert.NoError(t, UnstructuredJSONScheme.Encode(&list, ioutil.Discard))
}()
}
wg.Wait()
}
func TestRemoveNestedField(t *testing.T) {
obj := map[string]interface{}{
"x": map[string]interface{}{
"y": 1,
"a": "foo",
},
}
RemoveNestedField(obj, "x", "a")
assert.Len(t, obj["x"], 1)
RemoveNestedField(obj, "x", "y")
assert.Empty(t, obj["x"])
RemoveNestedField(obj, "x")
assert.Empty(t, obj)
RemoveNestedField(obj, "x") // Remove of a non-existent field
assert.Empty(t, obj)
}
/*
Copyright 2015 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 unstructured
import (
"bytes"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/conversion/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var _ runtime.Unstructured = &UnstructuredList{}
var _ metav1.ListInterface = &UnstructuredList{}
// 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.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:deepcopy-gen=true
type UnstructuredList struct {
Object map[string]interface{}
// Items is a list of unstructured objects.
Items []Unstructured `json:"items"`
}
func (u *UnstructuredList) GetObjectKind() schema.ObjectKind { return u }
func (u *UnstructuredList) IsUnstructuredObject() {}
func (u *UnstructuredList) IsList() bool { return true }
func (u *UnstructuredList) EachListItem(fn func(runtime.Object) error) error {
for i := range u.Items {
if err := fn(&u.Items[i]); err != nil {
return err
}
}
return nil
}
// UnstructuredContent returns a map contain an overlay of the Items field onto
// the Object field. Items always overwrites overlay. Changing "items" in the
// returned object will affect items in the underlying Items field, but changing
// the "items" slice itself will have no effect.
// TODO: expose SetUnstructuredContent on runtime.Unstructured that allows
// items to be changed.
func (u *UnstructuredList) UnstructuredContent() map[string]interface{} {
out := u.Object
if out == nil {
out = make(map[string]interface{})
}
items := make([]interface{}, len(u.Items))
for i, item := range u.Items {
items[i] = item.Object
}
out["items"] = items
return out
}
func (u *UnstructuredList) DeepCopy() *UnstructuredList {
if u == nil {
return nil
}
out := new(UnstructuredList)
*out = *u
out.Object = unstructured.DeepCopyJSON(u.Object)
out.Items = make([]Unstructured, len(u.Items))
for i := range u.Items {
u.Items[i].DeepCopyInto(&out.Items[i])
}
return out
}
// MarshalJSON ensures that the unstructured list object produces proper
// JSON when passed to Go's standard JSON library.
func (u *UnstructuredList) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
err := UnstructuredJSONScheme.Encode(u, &buf)
return buf.Bytes(), err
}
// UnmarshalJSON ensures that the unstructured list object properly
// decodes JSON when passed to Go's standard JSON library.
func (u *UnstructuredList) UnmarshalJSON(b []byte) error {
_, _, err := UnstructuredJSONScheme.Decode(b, nil, u)
return err
}
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) GetContinue() string {
return getNestedString(u.Object, "metadata", "continue")
}
func (u *UnstructuredList) SetContinue(c string) {
u.setNestedField(c, "metadata", "continue")
}
func (u *UnstructuredList) SetGroupVersionKind(gvk schema.GroupVersionKind) {
u.SetAPIVersion(gvk.GroupVersion().String())
u.SetKind(gvk.Kind)
}
func (u *UnstructuredList) GroupVersionKind() schema.GroupVersionKind {
gv, err := schema.ParseGroupVersion(u.GetAPIVersion())
if err != nil {
return schema.GroupVersionKind{}
}
gvk := gv.WithKind(u.GetKind())
return gvk
}
func (u *UnstructuredList) setNestedField(value interface{}, fields ...string) {
if u.Object == nil {
u.Object = make(map[string]interface{})
}
SetNestedField(u.Object, value, fields...)
}
......@@ -17,31 +17,14 @@ limitations under the License.
package unstructured
import (
"io/ioutil"
"sync"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestCodecOfUnstructuredList tests that there are no data races in Encode().
// i.e. that it does not mutate the object being encoded.
func TestCodecOfUnstructuredList(t *testing.T) {
var wg sync.WaitGroup
concurrency := 10
list := UnstructuredList{
Object: map[string]interface{}{},
}
wg.Add(concurrency)
for i := 0; i < concurrency; i++ {
go func() {
defer wg.Done()
assert.NoError(t, UnstructuredJSONScheme.Encode(&list, ioutil.Discard))
}()
}
wg.Wait()
}
func TestUnstructuredList(t *testing.T) {
list := &UnstructuredList{
Object: map[string]interface{}{"kind": "List", "apiVersion": "v1"},
......@@ -51,12 +34,10 @@ func TestUnstructuredList(t *testing.T) {
}
content := list.UnstructuredContent()
items := content["items"].([]interface{})
if len(items) != 1 {
t.Fatalf("unexpected items: %#v", items)
}
if getNestedField(items[0].(map[string]interface{}), "metadata", "name") != "test" {
t.Fatalf("unexpected fields: %#v", items[0])
}
require.Len(t, items, 1)
val, ok := NestedFieldCopy(items[0].(map[string]interface{}), "metadata", "name")
require.True(t, ok)
assert.Equal(t, "test", val)
}
func TestNilDeletionTimestamp(t *testing.T) {
......@@ -70,9 +51,14 @@ func TestNilDeletionTimestamp(t *testing.T) {
if del != nil {
t.Errorf("unexpected non-nil deletion timestamp: %v", del)
}
_, ok := u.Object["metadata"]
assert.False(t, ok)
now := metav1.Now()
u.SetDeletionTimestamp(&now)
assert.Equal(t, now.Unix(), u.GetDeletionTimestamp().Unix())
u.SetDeletionTimestamp(nil)
metadata := u.Object["metadata"].(map[string]interface{})
deletionTimestamp := metadata["deletionTimestamp"]
if deletionTimestamp != nil {
t.Errorf("unexpected deletion timestamp field: %q", deletionTimestamp)
}
_, ok = metadata["deletionTimestamp"]
assert.False(t, ok)
}
......@@ -426,21 +426,23 @@ func (c *converterImpl) ToUnstructured(obj interface{}) (map[string]interface{},
// DeepCopyJSON deep copies the passed value, assuming it is a valid JSON representation i.e. only contains
// types produced by json.Unmarshal().
func DeepCopyJSON(x map[string]interface{}) map[string]interface{} {
return deepCopyJSON(x).(map[string]interface{})
return DeepCopyJSONValue(x).(map[string]interface{})
}
func deepCopyJSON(x interface{}) interface{} {
// DeepCopyJSONValue deep copies the passed value, assuming it is a valid JSON representation i.e. only contains
// types produced by json.Unmarshal().
func DeepCopyJSONValue(x interface{}) interface{} {
switch x := x.(type) {
case map[string]interface{}:
clone := make(map[string]interface{}, len(x))
for k, v := range x {
clone[k] = deepCopyJSON(v)
clone[k] = DeepCopyJSONValue(v)
}
return clone
case []interface{}:
clone := make([]interface{}, len(x))
for i, v := range x {
clone[i] = deepCopyJSON(v)
clone[i] = DeepCopyJSONValue(v)
}
return clone
case string, int64, bool, float64, nil, encodingjson.Number:
......
......@@ -84,6 +84,7 @@ func TestDecode(t *testing.T) {
json: []byte(`{"apiVersion": "test", "kind": "test_list", "items": []}`),
want: &unstructured.UnstructuredList{
Object: map[string]interface{}{"apiVersion": "test", "kind": "test_list"},
Items: []unstructured.Unstructured{},
},
},
{
......@@ -147,14 +148,14 @@ func TestUnstructuredGetters(t *testing.T) {
"annotations": map[string]interface{}{
"test_annotation": "test_value",
},
"ownerReferences": []map[string]interface{}{
{
"ownerReferences": []interface{}{
map[string]interface{}{
"kind": "Pod",
"name": "poda",
"apiVersion": "v1",
"uid": "1",
},
{
map[string]interface{}{
"kind": "Pod",
"name": "podb",
"apiVersion": "v1",
......@@ -273,7 +274,7 @@ func TestUnstructuredSetters(t *testing.T) {
"selfLink": "test_selfLink",
"creationTimestamp": "2009-11-10T23:00:00Z",
"deletionTimestamp": "2010-11-10T23:00:00Z",
"deletionGracePeriodSeconds": &ten,
"deletionGracePeriodSeconds": ten,
"generation": ten,
"labels": map[string]interface{}{
"test_label": "test_value",
......@@ -281,14 +282,14 @@ func TestUnstructuredSetters(t *testing.T) {
"annotations": map[string]interface{}{
"test_annotation": "test_value",
},
"ownerReferences": []map[string]interface{}{
{
"ownerReferences": []interface{}{
map[string]interface{}{
"kind": "Pod",
"name": "poda",
"apiVersion": "v1",
"uid": "1",
},
{
map[string]interface{}{
"kind": "Pod",
"name": "podb",
"apiVersion": "v1",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment