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

Merge pull request #55650 from smarterclayton/make_unstructured_conversion

Automatic merge from submit-queue. 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>. Move unstructured conversion into pkg/runtime Scheme conversion should support unstructured conversion natively to allow going from unstructured to typed and back. It is not a higher level responsibility to do that conversion because the scheme is the only one who knows what types it supports. @liggitt @kubernetes/sig-cli-api-reviews I am going to make Scheme support unstructured in ConvertToVersion and Convert, which means resource.Builder and the CLI can get simpler for all existing use cases where versioned and unstructured need to coexist.
parents 2a711199 f8cc69ca
...@@ -504,7 +504,6 @@ staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1 ...@@ -504,7 +504,6 @@ staging/src/k8s.io/apimachinery/pkg/apis/meta/v1alpha1
staging/src/k8s.io/apimachinery/pkg/apis/testapigroup staging/src/k8s.io/apimachinery/pkg/apis/testapigroup
staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1 staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1
staging/src/k8s.io/apimachinery/pkg/conversion staging/src/k8s.io/apimachinery/pkg/conversion
staging/src/k8s.io/apimachinery/pkg/conversion/unstructured
staging/src/k8s.io/apimachinery/pkg/labels staging/src/k8s.io/apimachinery/pkg/labels
staging/src/k8s.io/apimachinery/pkg/runtime/schema staging/src/k8s.io/apimachinery/pkg/runtime/schema
staging/src/k8s.io/apimachinery/pkg/runtime/serializer staging/src/k8s.io/apimachinery/pkg/runtime/serializer
......
...@@ -97,7 +97,6 @@ go_test( ...@@ -97,7 +97,6 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf:go_default_library",
......
...@@ -29,7 +29,6 @@ import ( ...@@ -29,7 +29,6 @@ import (
"k8s.io/apimachinery/pkg/api/testing/fuzzer" "k8s.io/apimachinery/pkg/api/testing/fuzzer"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metaunstruct "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" metaunstruct "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/conversion/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
...@@ -99,14 +98,14 @@ func doRoundTrip(t *testing.T, group testapi.TestGroup, kind string) { ...@@ -99,14 +98,14 @@ func doRoundTrip(t *testing.T, group testapi.TestGroup, kind string) {
return return
} }
newUnstr, err := unstructured.DefaultConverter.ToUnstructured(item) newUnstr, err := runtime.NewTestUnstructuredConverter(apiequality.Semantic).ToUnstructured(item)
if err != nil { if err != nil {
t.Errorf("ToUnstructured failed: %v", err) t.Errorf("ToUnstructured failed: %v", err)
return return
} }
newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object) newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
err = unstructured.DefaultConverter.FromUnstructured(newUnstr, newObj) err = runtime.DefaultUnstructuredConverter.FromUnstructured(newUnstr, newObj)
if err != nil { if err != nil {
t.Errorf("FromUnstructured failed: %v", err) t.Errorf("FromUnstructured failed: %v", err)
return return
...@@ -146,7 +145,7 @@ func TestRoundTripWithEmptyCreationTimestamp(t *testing.T) { ...@@ -146,7 +145,7 @@ func TestRoundTripWithEmptyCreationTimestamp(t *testing.T) {
} }
t.Logf("Testing: %v in %v", kind, groupKey) t.Logf("Testing: %v in %v", kind, groupKey)
unstrBody, err := unstructured.DefaultConverter.ToUnstructured(item) unstrBody, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item)
if err != nil { if err != nil {
t.Fatalf("ToUnstructured failed: %v", err) t.Fatalf("ToUnstructured failed: %v", err)
} }
...@@ -163,7 +162,7 @@ func TestRoundTripWithEmptyCreationTimestamp(t *testing.T) { ...@@ -163,7 +162,7 @@ func TestRoundTripWithEmptyCreationTimestamp(t *testing.T) {
// attempt to re-convert unstructured object - conversion should not fail // attempt to re-convert unstructured object - conversion should not fail
// based on empty metadata fields, such as creationTimestamp // based on empty metadata fields, such as creationTimestamp
newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object) newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
err = unstructured.DefaultConverter.FromUnstructured(unstructObj.Object, newObj) err = runtime.NewTestUnstructuredConverter(apiequality.Semantic).FromUnstructured(unstructObj.Object, newObj)
if err != nil { if err != nil {
t.Fatalf("FromUnstructured failed: %v", err) t.Fatalf("FromUnstructured failed: %v", err)
} }
...@@ -176,12 +175,12 @@ func BenchmarkToFromUnstructured(b *testing.B) { ...@@ -176,12 +175,12 @@ func BenchmarkToFromUnstructured(b *testing.B) {
size := len(items) size := len(items)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
unstr, err := unstructured.DefaultConverter.ToUnstructured(&items[i%size]) unstr, err := runtime.NewTestUnstructuredConverter(apiequality.Semantic).ToUnstructured(&items[i%size])
if err != nil { if err != nil {
b.Fatalf("unexpected error: %v", err) b.Fatalf("unexpected error: %v", err)
} }
obj := v1.Pod{} obj := v1.Pod{}
if err := unstructured.DefaultConverter.FromUnstructured(unstr, &obj); err != nil { if err := runtime.NewTestUnstructuredConverter(apiequality.Semantic).FromUnstructured(unstr, &obj); err != nil {
b.Fatalf("unexpected error: %v", err) b.Fatalf("unexpected error: %v", err)
} }
} }
......
...@@ -193,94 +193,94 @@ func TestEdit(t *testing.T) { ...@@ -193,94 +193,94 @@ func TestEdit(t *testing.T) {
} }
for _, testcaseName := range testcases.List() { for _, testcaseName := range testcases.List() {
t.Logf("Running testcase: %s", testcaseName) t.Run(testcaseName, func(t *testing.T) {
i = 0 i = 0
name = testcaseName name = testcaseName
testcase = EditTestCase{} testcase = EditTestCase{}
testcaseDir := filepath.Join("testdata", "edit", "testcase-"+name) testcaseDir := filepath.Join("testdata", "edit", "testcase-"+name)
testcaseData, err := ioutil.ReadFile(filepath.Join(testcaseDir, "test.yaml")) testcaseData, err := ioutil.ReadFile(filepath.Join(testcaseDir, "test.yaml"))
if err != nil { if err != nil {
t.Fatalf("%s: %v", name, err) t.Fatalf("%s: %v", name, err)
} }
if err := yaml.Unmarshal(testcaseData, &testcase); err != nil { if err := yaml.Unmarshal(testcaseData, &testcase); err != nil {
t.Fatalf("%s: %v", name, err) t.Fatalf("%s: %v", name, err)
}
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.UnstructuredClientForMappingFunc = func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
versionedAPIPath := ""
if mapping.GroupVersionKind.Group == "" {
versionedAPIPath = "/api/" + mapping.GroupVersionKind.Version
} else {
versionedAPIPath = "/apis/" + mapping.GroupVersionKind.Group + "/" + mapping.GroupVersionKind.Version
} }
return &fake.RESTClient{
VersionedAPIPath: versionedAPIPath,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(reqResp),
}, nil
}
if len(testcase.Namespace) > 0 { f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Namespace = testcase.Namespace tf.Printer = &testPrinter{}
} tf.UnstructuredClientForMappingFunc = func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
tf.ClientConfig = defaultClientConfig() versionedAPIPath := ""
tf.Command = "edit test cmd invocation" if mapping.GroupVersionKind.Group == "" {
buf := bytes.NewBuffer([]byte{}) versionedAPIPath = "/api/" + mapping.GroupVersionKind.Version
errBuf := bytes.NewBuffer([]byte{}) } else {
versionedAPIPath = "/apis/" + mapping.GroupVersionKind.Group + "/" + mapping.GroupVersionKind.Version
var cmd *cobra.Command }
switch testcase.Mode { return &fake.RESTClient{
case "edit": VersionedAPIPath: versionedAPIPath,
cmd = NewCmdEdit(f, buf, errBuf) NegotiatedSerializer: unstructuredSerializer,
case "create": Client: fake.CreateHTTPClient(reqResp),
cmd = NewCmdCreate(f, buf, errBuf) }, nil
cmd.Flags().Set("edit", "true") }
case "edit-last-applied":
cmd = NewCmdApplyEditLastApplied(f, buf, errBuf)
default:
t.Errorf("%s: unexpected mode %s", name, testcase.Mode)
continue
}
if len(testcase.Filename) > 0 {
cmd.Flags().Set("filename", filepath.Join(testcaseDir, testcase.Filename))
}
if len(testcase.Output) > 0 {
cmd.Flags().Set("output", testcase.Output)
}
if len(testcase.OutputPatch) > 0 {
cmd.Flags().Set("output-patch", testcase.OutputPatch)
}
if len(testcase.SaveConfig) > 0 {
cmd.Flags().Set("save-config", testcase.SaveConfig)
}
cmdutil.BehaviorOnFatal(func(str string, code int) { if len(testcase.Namespace) > 0 {
errBuf.WriteString(str) tf.Namespace = testcase.Namespace
if testcase.ExpectedExitCode != code {
t.Errorf("%s: expected exit code %d, got %d: %s", name, testcase.ExpectedExitCode, code, str)
} }
}) tf.ClientConfig = defaultClientConfig()
tf.Command = "edit test cmd invocation"
buf := bytes.NewBuffer([]byte{})
errBuf := bytes.NewBuffer([]byte{})
var cmd *cobra.Command
switch testcase.Mode {
case "edit":
cmd = NewCmdEdit(f, buf, errBuf)
case "create":
cmd = NewCmdCreate(f, buf, errBuf)
cmd.Flags().Set("edit", "true")
case "edit-last-applied":
cmd = NewCmdApplyEditLastApplied(f, buf, errBuf)
default:
t.Fatalf("%s: unexpected mode %s", name, testcase.Mode)
}
if len(testcase.Filename) > 0 {
cmd.Flags().Set("filename", filepath.Join(testcaseDir, testcase.Filename))
}
if len(testcase.Output) > 0 {
cmd.Flags().Set("output", testcase.Output)
}
if len(testcase.OutputPatch) > 0 {
cmd.Flags().Set("output-patch", testcase.OutputPatch)
}
if len(testcase.SaveConfig) > 0 {
cmd.Flags().Set("save-config", testcase.SaveConfig)
}
cmdutil.BehaviorOnFatal(func(str string, code int) {
errBuf.WriteString(str)
if testcase.ExpectedExitCode != code {
t.Errorf("%s: expected exit code %d, got %d: %s", name, testcase.ExpectedExitCode, code, str)
}
})
cmd.Run(cmd, testcase.Args) cmd.Run(cmd, testcase.Args)
stdout := buf.String() stdout := buf.String()
stderr := errBuf.String() stderr := errBuf.String()
for _, s := range testcase.ExpectedStdout { for _, s := range testcase.ExpectedStdout {
if !strings.Contains(stdout, s) { if !strings.Contains(stdout, s) {
t.Errorf("%s: expected to see '%s' in stdout\n\nstdout:\n%s\n\nstderr:\n%s", name, s, stdout, stderr) t.Errorf("%s: expected to see '%s' in stdout\n\nstdout:\n%s\n\nstderr:\n%s", name, s, stdout, stderr)
}
} }
} for _, s := range testcase.ExpectedStderr {
for _, s := range testcase.ExpectedStderr { if !strings.Contains(stderr, s) {
if !strings.Contains(stderr, s) { t.Errorf("%s: expected to see '%s' in stderr\n\nstdout:\n%s\n\nstderr:\n%s", name, s, stdout, stderr)
t.Errorf("%s: expected to see '%s' in stderr\n\nstdout:\n%s\n\nstderr:\n%s", name, s, stdout, stderr) }
} }
} if i < len(testcase.Steps) {
if i < len(testcase.Steps) { t.Errorf("%s: saw %d steps, testcase included %d additional steps that were not exercised", name, i, len(testcase.Steps)-i)
t.Errorf("%s: saw %d steps, testcase included %d additional steps that were not exercised", name, i, len(testcase.Steps)-i) }
} })
} }
} }
......
{
"kind": "ConfigMap",
"apiVersion": "v1",
"metadata": {
"name": "cm1",
"namespace": "myproject",
"selfLink": "/api/v1/namespaces/myproject/configmaps/cm1",
"uid": "cc08a131-3d6f-11e7-8ef0-c85b76034b7b",
"resourceVersion": "3518",
"creationTimestamp": "2017-05-20T15:20:03Z",
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"baz\":\"qux\",\"foo\":\"changed-value\",\"new-data\":\"new-value\",\"new-data2\":\"new-value\"},\"kind\":\"ConfigMap\",\"metadata\":{\"annotations\":{},\"name\":\"cm1\",\"namespace\":\"myproject\"}}\n"
}
},
"data": {
"baz": "qux",
"foo": "changed-value",
"new-data": "new-value",
"new-data2": "new-value"
}
}
\ No newline at end of file
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc1",
"namespace": "myproject",
"selfLink": "/api/v1/namespaces/myproject/services/svc1",
"uid": "d8b96f0b-3d6f-11e7-8ef0-c85b76034b7b",
"resourceVersion": "3525",
"creationTimestamp": "2017-05-20T15:20:24Z",
"labels": {
"app": "svc1",
"new-label": "foo"
},
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":81,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
}
},
"spec": {
"ports": [
{
"name": "80",
"protocol": "TCP",
"port": 81,
"targetPort": 81
}
],
"clusterIP": "172.30.32.183",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}
\ No newline at end of file
# Please edit the 'last-applied-configuration' annotations below.
# Lines beginning with a '#' will be ignored, and an empty file will abort the edit.
#
apiVersion: v1
items:
- apiVersion: v1
data:
baz: qux
foo: changed-value
new-data: new-value
new-data2: new-value
new-data3: newivalue
kind: ConfigMap
metadata:
annotations: {}
name: cm1
namespace: myproject
- kind: Service
metadata:
annotations: {}
labels:
app: svc1
new-label: foo
new-label2: foo2
name: svc1
namespace: myproject
spec:
ports:
- name: "80"
port: 82
protocol: TCP
targetPort: 81
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
kind: List
metadata: {}
# Please edit the 'last-applied-configuration' annotations below.
# Lines beginning with a '#' will be ignored, and an empty file will abort the edit.
#
apiVersion: v1
items:
- apiVersion: v1
data:
baz: qux
foo: changed-value
new-data: new-value
new-data2: new-value
kind: ConfigMap
metadata:
annotations: {}
name: cm1
namespace: myproject
- kind: Service
metadata:
annotations: {}
labels:
app: svc1
new-label: foo
name: svc1
namespace: myproject
spec:
ports:
- name: "80"
port: 81
protocol: TCP
targetPort: 81
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
kind: List
metadata: {}
# Please edit the 'last-applied-configuration' annotations below.
# Lines beginning with a '#' will be ignored, and an empty file will abort the edit.
#
# The edited file had a syntax error: error converting YAML to JSON: yaml: line 12: could not find expected ':'
#
apiVersion: v1
items:
- apiVersion: v1
data:
baz: qux
foo: changed-value
new-data: new-value
new-data2: new-value
new-data3: newivalue
kind: ConfigMap
metadata:
annotations: {}
name: cm1
namespace: myproject
- kind: Service
apiVersion: v1
metadata:
annotations: {}
labels:
app: svc1
new-label: foo
new-label2: foo2
name: svc1
namespace: myproject
spec:
ports:
- name: "80"
port: 82
protocol: TCP
targetPort: 81
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
kind: List
metadata: {}
# Please edit the 'last-applied-configuration' annotations below.
# Lines beginning with a '#' will be ignored, and an empty file will abort the edit.
#
# The edited file had a syntax error: unable to get type info from the object "*unstructured.Unstructured": Object 'apiVersion' is missing in 'unstructured object has no version'
#
apiVersion: v1
items:
- apiVersion: v1
data:
baz: qux
foo: changed-value
new-data: new-value
new-data2: new-value
new-data3: newivalue
kind: ConfigMap
metadata:
annotations: {}
name: cm1
namespace: myproject
- kind: Service
metadata:
annotations: {}
labels:
app: svc1
new-label: foo
new-label2: foo2
name: svc1
namespace: myproject
spec:
ports:
- name: "80"
port: 82
protocol: TCP
targetPort: 81
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
kind: List
metadata: {}
{
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"baz\":\"qux\",\"foo\":\"changed-value\",\"new-data\":\"new-value\",\"new-data2\":\"new-value\",\"new-data3\":\"newivalue\"},\"kind\":\"ConfigMap\",\"metadata\":{\"annotations\":{},\"name\":\"cm1\",\"namespace\":\"myproject\"}}\n"
}
}
}
\ No newline at end of file
{
"kind": "ConfigMap",
"apiVersion": "v1",
"metadata": {
"name": "cm1",
"namespace": "myproject",
"selfLink": "/api/v1/namespaces/myproject/configmaps/cm1",
"uid": "cc08a131-3d6f-11e7-8ef0-c85b76034b7b",
"resourceVersion": "3554",
"creationTimestamp": "2017-05-20T15:20:03Z",
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"baz\":\"qux\",\"foo\":\"changed-value\",\"new-data\":\"new-value\",\"new-data2\":\"new-value\",\"new-data3\":\"newivalue\"},\"kind\":\"ConfigMap\",\"metadata\":{\"annotations\":{},\"name\":\"cm1\",\"namespace\":\"myproject\"}}\n"
}
},
"data": {
"baz": "qux",
"foo": "changed-value",
"new-data": "new-value",
"new-data2": "new-value"
}
}
\ No newline at end of file
{
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\",\"new-label2\":\"foo2\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":82,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
}
}
}
\ No newline at end of file
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "svc1",
"namespace": "myproject",
"selfLink": "/api/v1/namespaces/myproject/services/svc1",
"uid": "d8b96f0b-3d6f-11e7-8ef0-c85b76034b7b",
"resourceVersion": "3555",
"creationTimestamp": "2017-05-20T15:20:24Z",
"labels": {
"app": "svc1",
"new-label": "foo"
},
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\",\"new-label2\":\"foo2\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":82,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
}
},
"spec": {
"ports": [
{
"name": "80",
"protocol": "TCP",
"port": 81,
"targetPort": 81
}
],
"clusterIP": "172.30.32.183",
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
}
\ No newline at end of file
description: if the user omits an API version, edit will fail
mode: edit-last-applied
args:
- configmaps/cm1
- service/svc1
namespace: "myproject"
expectedStdout:
- configmap "cm1" edited
- service "svc1" edited
expectedExitCode: 0
steps:
- type: request
expectedMethod: GET
expectedPath: /api/v1/namespaces/myproject/configmaps/cm1
expectedInput: 0.request
resultingStatusCode: 200
resultingOutput: 0.response
- type: request
expectedMethod: GET
expectedPath: /api/v1/namespaces/myproject/services/svc1
expectedInput: 1.request
resultingStatusCode: 200
resultingOutput: 1.response
- type: edit
expectedInput: 2.original
resultingOutput: 2.edited
- type: edit
expectedInput: 3.original
resultingOutput: 3.edited
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/myproject/configmaps/cm1
expectedContentType: application/merge-patch+json
expectedInput: 4.request
resultingStatusCode: 200
resultingOutput: 4.response
- type: request
expectedMethod: PATCH
expectedPath: /api/v1/namespaces/myproject/services/svc1
expectedContentType: application/merge-patch+json
expectedInput: 5.request
resultingStatusCode: 200
resultingOutput: 5.response
...@@ -16,6 +16,7 @@ items: ...@@ -16,6 +16,7 @@ items:
name: cm1 name: cm1
namespace: myproject namespace: myproject
- kind: Service - kind: Service
apiVersion: v1
metadata: metadata:
annotations: {} annotations: {}
labels: labels:
......
{ {
"metadata": { "metadata": {
"annotations": { "annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\",\"new-label2\":\"foo2\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":82,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n" "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\",\"new-label2\":\"foo2\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":82,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
} }
} }
} }
\ No newline at end of file
...@@ -183,6 +183,10 @@ ...@@ -183,6 +183,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/json",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/net", "ImportPath": "k8s.io/apimachinery/pkg/util/net",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
......
...@@ -703,10 +703,6 @@ ...@@ -703,10 +703,6 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/fields", "ImportPath": "k8s.io/apimachinery/pkg/fields",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
......
...@@ -59,11 +59,11 @@ func TestListOptions(t *testing.T) { ...@@ -59,11 +59,11 @@ func TestListOptions(t *testing.T) {
} }
// verify kind registration // verify kind registration
if gvk, unversioned, err := scheme.ObjectKind(in); err != nil || unversioned || gvk != metav1.SchemeGroupVersion.WithKind("ListOptions") { if gvks, unversioned, err := scheme.ObjectKinds(in); err != nil || unversioned || gvks[0] != metav1.SchemeGroupVersion.WithKind("ListOptions") {
t.Errorf("unexpected: %v %v %v", gvk, unversioned, err) t.Errorf("unexpected: %v %v %v", gvks[0], unversioned, err)
} }
if gvk, unversioned, err := scheme.ObjectKind(out); err != nil || unversioned || gvk != SchemeGroupVersion.WithKind("ListOptions") { if gvks, unversioned, err := scheme.ObjectKinds(out); err != nil || unversioned || gvks[0] != SchemeGroupVersion.WithKind("ListOptions") {
t.Errorf("unexpected: %v %v %v", gvk, unversioned, err) t.Errorf("unexpected: %v %v %v", gvks[0], unversioned, err)
} }
actual = &metav1.ListOptions{} actual = &metav1.ListOptions{}
......
...@@ -32,7 +32,6 @@ go_library( ...@@ -32,7 +32,6 @@ go_library(
importpath = "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured", importpath = "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
deps = [ deps = [
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
......
...@@ -24,7 +24,6 @@ import ( ...@@ -24,7 +24,6 @@ import (
"strings" "strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/conversion/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
...@@ -39,7 +38,7 @@ func NestedFieldCopy(obj map[string]interface{}, fields ...string) (interface{}, ...@@ -39,7 +38,7 @@ func NestedFieldCopy(obj map[string]interface{}, fields ...string) (interface{},
if !ok { if !ok {
return nil, false return nil, false
} }
return unstructured.DeepCopyJSONValue(val), true return runtime.DeepCopyJSONValue(val), true
} }
func nestedFieldNoCopy(obj map[string]interface{}, fields ...string) (interface{}, bool) { func nestedFieldNoCopy(obj map[string]interface{}, fields ...string) (interface{}, bool) {
...@@ -131,7 +130,7 @@ func NestedSlice(obj map[string]interface{}, fields ...string) ([]interface{}, b ...@@ -131,7 +130,7 @@ func NestedSlice(obj map[string]interface{}, fields ...string) ([]interface{}, b
return nil, false return nil, false
} }
if _, ok := val.([]interface{}); ok { if _, ok := val.([]interface{}); ok {
return unstructured.DeepCopyJSONValue(val).([]interface{}), true return runtime.DeepCopyJSONValue(val).([]interface{}), true
} }
return nil, false return nil, false
} }
...@@ -165,7 +164,7 @@ func NestedMap(obj map[string]interface{}, fields ...string) (map[string]interfa ...@@ -165,7 +164,7 @@ func NestedMap(obj map[string]interface{}, fields ...string) (map[string]interfa
return nil, false return nil, false
} }
if m, ok := val.(map[string]interface{}); ok { if m, ok := val.(map[string]interface{}); ok {
return unstructured.DeepCopyJSON(m), true return runtime.DeepCopyJSON(m), true
} }
return nil, false return nil, false
} }
...@@ -173,7 +172,7 @@ func NestedMap(obj map[string]interface{}, fields ...string) (map[string]interfa ...@@ -173,7 +172,7 @@ func NestedMap(obj map[string]interface{}, fields ...string) (map[string]interfa
// SetNestedField sets the value of a nested field to a deep copy of the value provided. // SetNestedField sets the value of a nested field to a deep copy of the value provided.
// Returns false if value cannot be set because one of the nesting levels is not a map[string]interface{}. // Returns false if value cannot be set because one of the nesting levels is not a map[string]interface{}.
func SetNestedField(obj map[string]interface{}, value interface{}, fields ...string) bool { func SetNestedField(obj map[string]interface{}, value interface{}, fields ...string) bool {
return setNestedFieldNoCopy(obj, unstructured.DeepCopyJSONValue(value), fields...) return setNestedFieldNoCopy(obj, runtime.DeepCopyJSONValue(value), fields...)
} }
func setNestedFieldNoCopy(obj map[string]interface{}, value interface{}, fields ...string) bool { func setNestedFieldNoCopy(obj map[string]interface{}, value interface{}, fields ...string) bool {
...@@ -288,8 +287,6 @@ func setOwnerReference(src metav1.OwnerReference) map[string]interface{} { ...@@ -288,8 +287,6 @@ func setOwnerReference(src metav1.OwnerReference) map[string]interface{} {
return ret return ret
} }
var converter = unstructured.NewConverter(false)
// 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.
......
...@@ -21,7 +21,6 @@ import ( ...@@ -21,7 +21,6 @@ import (
"fmt" "fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/conversion/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
...@@ -50,8 +49,6 @@ var _ runtime.Unstructured = &Unstructured{} ...@@ -50,8 +49,6 @@ var _ runtime.Unstructured = &Unstructured{}
func (obj *Unstructured) GetObjectKind() schema.ObjectKind { return obj } func (obj *Unstructured) GetObjectKind() schema.ObjectKind { return obj }
func (obj *Unstructured) IsUnstructuredObject() {}
func (obj *Unstructured) IsList() bool { func (obj *Unstructured) IsList() bool {
if obj.Object != nil { if obj.Object != nil {
_, ok := obj.Object["items"] _, ok := obj.Object["items"]
...@@ -91,6 +88,10 @@ func (obj *Unstructured) UnstructuredContent() map[string]interface{} { ...@@ -91,6 +88,10 @@ func (obj *Unstructured) UnstructuredContent() map[string]interface{} {
return obj.Object return obj.Object
} }
func (obj *Unstructured) SetUnstructuredContent(content map[string]interface{}) {
obj.Object = content
}
// 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) {
...@@ -112,7 +113,7 @@ func (in *Unstructured) DeepCopy() *Unstructured { ...@@ -112,7 +113,7 @@ func (in *Unstructured) DeepCopy() *Unstructured {
} }
out := new(Unstructured) out := new(Unstructured)
*out = *in *out = *in
out.Object = unstructured.DeepCopyJSON(in.Object) out.Object = runtime.DeepCopyJSON(in.Object)
return out return out
} }
...@@ -348,7 +349,7 @@ func (u *Unstructured) GetInitializers() *metav1.Initializers { ...@@ -348,7 +349,7 @@ func (u *Unstructured) GetInitializers() *metav1.Initializers {
return nil return nil
} }
out := &metav1.Initializers{} out := &metav1.Initializers{}
if err := converter.FromUnstructured(obj, out); err != nil { if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj, out); err != nil {
utilruntime.HandleError(fmt.Errorf("unable to retrieve initializers for object: %v", err)) utilruntime.HandleError(fmt.Errorf("unable to retrieve initializers for object: %v", err))
} }
return out return out
...@@ -359,7 +360,7 @@ func (u *Unstructured) SetInitializers(initializers *metav1.Initializers) { ...@@ -359,7 +360,7 @@ func (u *Unstructured) SetInitializers(initializers *metav1.Initializers) {
RemoveNestedField(u.Object, "metadata", "initializers") RemoveNestedField(u.Object, "metadata", "initializers")
return return
} }
out, err := converter.ToUnstructured(initializers) out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(initializers)
if err != nil { if err != nil {
utilruntime.HandleError(fmt.Errorf("unable to retrieve initializers for object: %v", err)) utilruntime.HandleError(fmt.Errorf("unable to retrieve initializers for object: %v", err))
} }
......
...@@ -20,7 +20,6 @@ import ( ...@@ -20,7 +20,6 @@ import (
"bytes" "bytes"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/conversion/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
...@@ -42,8 +41,6 @@ type UnstructuredList struct { ...@@ -42,8 +41,6 @@ type UnstructuredList struct {
func (u *UnstructuredList) GetObjectKind() schema.ObjectKind { return u } func (u *UnstructuredList) GetObjectKind() schema.ObjectKind { return u }
func (u *UnstructuredList) IsUnstructuredObject() {}
func (u *UnstructuredList) IsList() bool { return true } func (u *UnstructuredList) IsList() bool { return true }
func (u *UnstructuredList) EachListItem(fn func(runtime.Object) error) error { func (u *UnstructuredList) EachListItem(fn func(runtime.Object) error) error {
...@@ -74,13 +71,40 @@ func (u *UnstructuredList) UnstructuredContent() map[string]interface{} { ...@@ -74,13 +71,40 @@ func (u *UnstructuredList) UnstructuredContent() map[string]interface{} {
return out return out
} }
// SetUnstructuredContent obeys the conventions of List and keeps Items and the items
// array in sync. If items is not an array of objects in the incoming map, then any
// mismatched item will be removed.
func (obj *UnstructuredList) SetUnstructuredContent(content map[string]interface{}) {
obj.Object = content
if content == nil {
obj.Items = nil
return
}
items, ok := obj.Object["items"].([]interface{})
if !ok || items == nil {
items = []interface{}{}
}
unstructuredItems := make([]Unstructured, 0, len(items))
newItems := make([]interface{}, 0, len(items))
for _, item := range items {
o, ok := item.(map[string]interface{})
if !ok {
continue
}
unstructuredItems = append(unstructuredItems, Unstructured{Object: o})
newItems = append(newItems, o)
}
obj.Items = unstructuredItems
obj.Object["items"] = newItems
}
func (u *UnstructuredList) DeepCopy() *UnstructuredList { func (u *UnstructuredList) DeepCopy() *UnstructuredList {
if u == nil { if u == nil {
return nil return nil
} }
out := new(UnstructuredList) out := new(UnstructuredList)
*out = *u *out = *u
out.Object = unstructured.DeepCopyJSON(u.Object) out.Object = runtime.DeepCopyJSON(u.Object)
out.Items = make([]Unstructured, len(u.Items)) out.Items = make([]Unstructured, len(u.Items))
for i := range u.Items { for i := range u.Items {
u.Items[i].DeepCopyInto(&out.Items[i]) u.Items[i].DeepCopyInto(&out.Items[i])
......
...@@ -45,7 +45,6 @@ filegroup( ...@@ -45,7 +45,6 @@ filegroup(
srcs = [ srcs = [
":package-srcs", ":package-srcs",
"//staging/src/k8s.io/apimachinery/pkg/conversion/queryparams:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/conversion/queryparams:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/conversion/unstructured:all-srcs",
], ],
tags = ["automanaged"], tags = ["automanaged"],
) )
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"converter.go",
"doc.go",
],
importpath = "k8s.io/apimachinery/pkg/conversion/unstructured",
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/testing:all-srcs",
],
tags = ["automanaged"],
)
/*
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 provides conversion from runtime objects
// to map[string]interface{} representation.
package unstructured // import "k8s.io/apimachinery/pkg/conversion/unstructured"
load("@io_bazel_rules_go//go:def.bzl", "go_test")
go_test(
name = "go_default_test",
srcs = ["converter_test.go"],
importpath = "k8s.io/apimachinery/pkg/conversion/unstructured/testing",
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/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
...@@ -19,6 +19,7 @@ go_library( ...@@ -19,6 +19,7 @@ go_library(
"codec.go", "codec.go",
"codec_check.go", "codec_check.go",
"conversion.go", "conversion.go",
"converter.go",
"doc.go", "doc.go",
"embedded.go", "embedded.go",
"error.go", "error.go",
...@@ -37,10 +38,13 @@ go_library( ...@@ -37,10 +38,13 @@ go_library(
importpath = "k8s.io/apimachinery/pkg/runtime", importpath = "k8s.io/apimachinery/pkg/runtime",
deps = [ deps = [
"//vendor/github.com/gogo/protobuf/proto:go_default_library", "//vendor/github.com/gogo/protobuf/proto:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion/queryparams:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion/queryparams:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
], ],
) )
...@@ -48,6 +52,7 @@ go_test( ...@@ -48,6 +52,7 @@ go_test(
name = "go_default_xtest", name = "go_default_xtest",
srcs = [ srcs = [
"conversion_test.go", "conversion_test.go",
"converter_test.go",
"embedded_test.go", "embedded_test.go",
"extension_test.go", "extension_test.go",
"scheme_test.go", "scheme_test.go",
...@@ -56,13 +61,17 @@ go_test( ...@@ -56,13 +61,17 @@ go_test(
deps = [ deps = [
"//vendor/github.com/google/gofuzz:go_default_library", "//vendor/github.com/google/gofuzz:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/github.com/stretchr/testify/require:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/testing:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/testing:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
], ],
) )
......
...@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and ...@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package unstructured package runtime
import ( import (
"bytes" "bytes"
...@@ -27,19 +27,18 @@ import ( ...@@ -27,19 +27,18 @@ import (
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time"
apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"github.com/golang/glog" "github.com/golang/glog"
) )
// Converter is an interface for converting between interface{} // UnstructuredConverter is an interface for converting between interface{}
// and map[string]interface representation. // and map[string]interface representation.
type Converter interface { type UnstructuredConverter interface {
ToUnstructured(obj interface{}) (map[string]interface{}, error) ToUnstructured(obj interface{}) (map[string]interface{}, error)
FromUnstructured(u map[string]interface{}, obj interface{}) error FromUnstructured(u map[string]interface{}, obj interface{}) error
} }
...@@ -78,7 +77,16 @@ var ( ...@@ -78,7 +77,16 @@ var (
float64Type = reflect.TypeOf(float64(0)) float64Type = reflect.TypeOf(float64(0))
boolType = reflect.TypeOf(bool(false)) boolType = reflect.TypeOf(bool(false))
fieldCache = newFieldsCache() fieldCache = newFieldsCache()
DefaultConverter = NewConverter(parseBool(os.Getenv("KUBE_PATCH_CONVERSION_DETECTOR")))
// DefaultUnstructuredConverter performs unstructured to Go typed object conversions.
DefaultUnstructuredConverter = &unstructuredConverter{
mismatchDetection: parseBool(os.Getenv("KUBE_PATCH_CONVERSION_DETECTOR")),
comparison: conversion.EqualitiesOrDie(
func(a, b time.Time) bool {
return a.UTC() == b.UTC()
},
),
}
) )
func parseBool(key string) bool { func parseBool(key string) bool {
...@@ -92,24 +100,30 @@ func parseBool(key string) bool { ...@@ -92,24 +100,30 @@ func parseBool(key string) bool {
return value return value
} }
// ConverterImpl knows how to convert between interface{} and // unstructuredConverter knows how to convert between interface{} and
// Unstructured in both ways. // Unstructured in both ways.
type converterImpl struct { type unstructuredConverter struct {
// If true, we will be additionally running conversion via json // If true, we will be additionally running conversion via json
// to ensure that the result is true. // to ensure that the result is true.
// This is supposed to be set only in tests. // This is supposed to be set only in tests.
mismatchDetection bool mismatchDetection bool
// comparison is the default test logic used to compare
comparison conversion.Equalities
} }
func NewConverter(mismatchDetection bool) Converter { // NewTestUnstructuredConverter creates an UnstructuredConverter that accepts JSON typed maps and translates them
return &converterImpl{ // to Go types via reflection. It performs mismatch detection automatically and is intended for use by external
mismatchDetection: mismatchDetection, // test tools. Use DefaultUnstructuredConverter if you do not explicitly need mismatch detection.
func NewTestUnstructuredConverter(comparison conversion.Equalities) UnstructuredConverter {
return &unstructuredConverter{
mismatchDetection: true,
comparison: comparison,
} }
} }
// FromUnstructured converts an object from map[string]interface{} representation into a concrete type. // FromUnstructured converts an object from map[string]interface{} representation into a concrete type.
// It uses encoding/json/Unmarshaler if object implements it or reflection if not. // It uses encoding/json/Unmarshaler if object implements it or reflection if not.
func (c *converterImpl) FromUnstructured(u map[string]interface{}, obj interface{}) error { func (c *unstructuredConverter) FromUnstructured(u map[string]interface{}, obj interface{}) error {
t := reflect.TypeOf(obj) t := reflect.TypeOf(obj)
value := reflect.ValueOf(obj) value := reflect.ValueOf(obj)
if t.Kind() != reflect.Ptr || value.IsNil() { if t.Kind() != reflect.Ptr || value.IsNil() {
...@@ -122,8 +136,8 @@ func (c *converterImpl) FromUnstructured(u map[string]interface{}, obj interface ...@@ -122,8 +136,8 @@ func (c *converterImpl) FromUnstructured(u map[string]interface{}, obj interface
if (err != nil) != (newErr != nil) { if (err != nil) != (newErr != nil) {
glog.Fatalf("FromUnstructured unexpected error for %v: error: %v", u, err) glog.Fatalf("FromUnstructured unexpected error for %v: error: %v", u, err)
} }
if err == nil && !apiequality.Semantic.DeepEqual(obj, newObj) { if err == nil && !c.comparison.DeepEqual(obj, newObj) {
glog.Fatalf("FromUnstructured mismatch for %#v, diff: %v", obj, diff.ObjectReflectDiff(obj, newObj)) glog.Fatalf("FromUnstructured mismatch\nobj1: %#v\nobj2: %#v", obj, newObj)
} }
} }
return err return err
...@@ -393,10 +407,10 @@ func interfaceFromUnstructured(sv, dv reflect.Value) error { ...@@ -393,10 +407,10 @@ func interfaceFromUnstructured(sv, dv reflect.Value) error {
// ToUnstructured converts an object into map[string]interface{} representation. // ToUnstructured converts an object into map[string]interface{} representation.
// It uses encoding/json/Marshaler if object implements it or reflection if not. // It uses encoding/json/Marshaler if object implements it or reflection if not.
func (c *converterImpl) ToUnstructured(obj interface{}) (map[string]interface{}, error) { func (c *unstructuredConverter) ToUnstructured(obj interface{}) (map[string]interface{}, error) {
var u map[string]interface{} var u map[string]interface{}
var err error var err error
if unstr, ok := obj.(runtime.Unstructured); ok { if unstr, ok := obj.(Unstructured); ok {
u = DeepCopyJSON(unstr.UnstructuredContent()) u = DeepCopyJSON(unstr.UnstructuredContent())
} else { } else {
t := reflect.TypeOf(obj) t := reflect.TypeOf(obj)
...@@ -413,8 +427,8 @@ func (c *converterImpl) ToUnstructured(obj interface{}) (map[string]interface{}, ...@@ -413,8 +427,8 @@ func (c *converterImpl) ToUnstructured(obj interface{}) (map[string]interface{},
if (err != nil) != (newErr != nil) { if (err != nil) != (newErr != nil) {
glog.Fatalf("ToUnstructured unexpected error for %v: error: %v; newErr: %v", obj, err, newErr) glog.Fatalf("ToUnstructured unexpected error for %v: error: %v; newErr: %v", obj, err, newErr)
} }
if err == nil && !apiequality.Semantic.DeepEqual(u, newUnstr) { if err == nil && !c.comparison.DeepEqual(u, newUnstr) {
glog.Fatalf("ToUnstructured mismatch for %#v, diff: %v", u, diff.ObjectReflectDiff(u, newUnstr)) glog.Fatalf("ToUnstructured mismatch\nobj1: %#v\nobj2: %#v", u, newUnstr)
} }
} }
if err != nil { if err != nil {
......
...@@ -18,7 +18,7 @@ limitations under the License. ...@@ -18,7 +18,7 @@ limitations under the License.
// Unstructured type depends on unstructured converter package but we want to test how the converter handles // Unstructured type depends on unstructured converter package but we want to test how the converter handles
// the Unstructured type so we need to import both. // the Unstructured type so we need to import both.
package testing package runtime_test
import ( import (
encodingjson "encoding/json" encodingjson "encoding/json"
...@@ -26,9 +26,11 @@ import ( ...@@ -26,9 +26,11 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"testing" "testing"
"time"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
conversionunstructured "k8s.io/apimachinery/pkg/conversion/unstructured" "k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
...@@ -36,6 +38,12 @@ import ( ...@@ -36,6 +38,12 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var simpleEquality = conversion.EqualitiesOrDie(
func(a, b time.Time) bool {
return a.UTC() == b.UTC()
},
)
// Definte a number of test types. // Definte a number of test types.
type A struct { type A struct {
A int `json:"aa,omitempty"` A int `json:"aa,omitempty"`
...@@ -137,14 +145,15 @@ func doRoundTrip(t *testing.T, item interface{}) { ...@@ -137,14 +145,15 @@ func doRoundTrip(t *testing.T, item interface{}) {
return return
} }
newUnstr, err := conversionunstructured.DefaultConverter.ToUnstructured(item) // TODO: should be using mismatch detection but fails due to another error
newUnstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item)
if err != nil { if err != nil {
t.Errorf("ToUnstructured failed: %v", err) t.Errorf("ToUnstructured failed: %v", err)
return return
} }
newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
err = conversionunstructured.DefaultConverter.FromUnstructured(newUnstr, newObj) err = runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(newUnstr, newObj)
if err != nil { if err != nil {
t.Errorf("FromUnstructured failed: %v", err) t.Errorf("FromUnstructured failed: %v", err)
return return
...@@ -239,10 +248,9 @@ func TestRoundTrip(t *testing.T) { ...@@ -239,10 +248,9 @@ func TestRoundTrip(t *testing.T) {
} }
for i := range testCases { for i := range testCases {
doRoundTrip(t, testCases[i].obj) t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
if t.Failed() { doRoundTrip(t, testCases[i].obj)
break })
}
} }
} }
...@@ -265,7 +273,7 @@ func doUnrecognized(t *testing.T, jsonData string, item interface{}, expectedErr ...@@ -265,7 +273,7 @@ func doUnrecognized(t *testing.T, jsonData string, item interface{}, expectedErr
return return
} }
newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
err = conversionunstructured.DefaultConverter.FromUnstructured(unstr, newObj) err = runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, newObj)
if (err != nil) != (expectedErr != nil) { if (err != nil) != (expectedErr != nil) {
t.Errorf("Unexpected error in FromUnstructured: %v, expected: %v", err, expectedErr) t.Errorf("Unexpected error in FromUnstructured: %v, expected: %v", err, expectedErr)
} }
...@@ -479,7 +487,7 @@ func TestDeepCopyJSON(t *testing.T) { ...@@ -479,7 +487,7 @@ func TestDeepCopyJSON(t *testing.T) {
"f": true, "f": true,
"g": encodingjson.Number("123"), "g": encodingjson.Number("123"),
} }
deepCopy := conversionunstructured.DeepCopyJSON(src) deepCopy := runtime.DeepCopyJSON(src)
assert.Equal(t, src, deepCopy) assert.Equal(t, src, deepCopy)
} }
...@@ -487,7 +495,7 @@ func TestFloatIntConversion(t *testing.T) { ...@@ -487,7 +495,7 @@ func TestFloatIntConversion(t *testing.T) {
unstr := map[string]interface{}{"fd": float64(3)} unstr := map[string]interface{}{"fd": float64(3)}
var obj F var obj F
if err := conversionunstructured.DefaultConverter.FromUnstructured(unstr, &obj); err != nil { if err := runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, &obj); err != nil {
t.Errorf("Unexpected error in FromUnstructured: %v", err) t.Errorf("Unexpected error in FromUnstructured: %v", err)
} }
...@@ -525,7 +533,7 @@ func TestCustomToUnstructured(t *testing.T) { ...@@ -525,7 +533,7 @@ func TestCustomToUnstructured(t *testing.T) {
tc := tc tc := tc
t.Run(tc.Data, func(t *testing.T) { t.Run(tc.Data, func(t *testing.T) {
t.Parallel() t.Parallel()
result, err := conversionunstructured.DefaultConverter.ToUnstructured(&G{ result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(&G{
CustomValue1: CustomValue{data: []byte(tc.Data)}, CustomValue1: CustomValue{data: []byte(tc.Data)},
CustomValue2: &CustomValue{data: []byte(tc.Data)}, CustomValue2: &CustomValue{data: []byte(tc.Data)},
CustomPointer1: CustomPointer{data: []byte(tc.Data)}, CustomPointer1: CustomPointer{data: []byte(tc.Data)},
...@@ -550,7 +558,7 @@ func TestCustomToUnstructuredTopLevel(t *testing.T) { ...@@ -550,7 +558,7 @@ func TestCustomToUnstructuredTopLevel(t *testing.T) {
obj := obj obj := obj
t.Run(strconv.Itoa(i), func(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Parallel() t.Parallel()
result, err := conversionunstructured.DefaultConverter.ToUnstructured(obj) result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(obj)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, expected, result) assert.Equal(t, expected, result)
}) })
......
...@@ -233,13 +233,13 @@ type Object interface { ...@@ -233,13 +233,13 @@ type Object interface {
// Unstructured objects store values as map[string]interface{}, with only values that can be serialized // Unstructured objects store values as map[string]interface{}, with only values that can be serialized
// to JSON allowed. // to JSON allowed.
type Unstructured interface { type Unstructured interface {
// IsUnstructuredObject is a marker interface to allow objects that can be serialized but not introspected Object
// to bypass conversion.
IsUnstructuredObject()
// UnstructuredContent returns a non-nil, mutable map of the contents of this object. Values may be // 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 // []interface{}, map[string]interface{}, or any primitive type. Contents are typically serialized to
// and from JSON. // and from JSON.
UnstructuredContent() map[string]interface{} UnstructuredContent() map[string]interface{}
// SetUnstructuredContent updates the object content to match the provided map.
SetUnstructuredContent(map[string]interface{})
// IsList returns true if this type is a list or matches the list convention - has an array called "items". // IsList returns true if this type is a list or matches the list convention - has an array called "items".
IsList() bool IsList() bool
// EachListItem should pass a single item out of the list as an Object to the provided function. Any // EachListItem should pass a single item out of the list as an Object to the provided function. Any
......
...@@ -217,19 +217,22 @@ func (s *Scheme) AllKnownTypes() map[schema.GroupVersionKind]reflect.Type { ...@@ -217,19 +217,22 @@ func (s *Scheme) AllKnownTypes() map[schema.GroupVersionKind]reflect.Type {
return s.gvkToType return s.gvkToType
} }
// ObjectKind returns the group,version,kind of the go object and true if this object
// is considered unversioned, or an error if it's not a pointer or is unregistered.
func (s *Scheme) ObjectKind(obj Object) (schema.GroupVersionKind, bool, error) {
gvks, unversionedType, err := s.ObjectKinds(obj)
if err != nil {
return schema.GroupVersionKind{}, false, err
}
return gvks[0], unversionedType, nil
}
// ObjectKinds returns all possible group,version,kind of the go object, true if the // ObjectKinds returns all possible group,version,kind of the go object, true if the
// object is considered unversioned, or an error if it's not a pointer or is unregistered. // object is considered unversioned, or an error if it's not a pointer or is unregistered.
func (s *Scheme) ObjectKinds(obj Object) ([]schema.GroupVersionKind, bool, error) { func (s *Scheme) ObjectKinds(obj Object) ([]schema.GroupVersionKind, bool, error) {
// Unstructured objects are always considered to have their declared GVK
if _, ok := obj.(Unstructured); ok {
// we require that the GVK be populated in order to recognize the object
gvk := obj.GetObjectKind().GroupVersionKind()
if len(gvk.Kind) == 0 {
return nil, false, NewMissingKindErr("unstructured object has no kind")
}
if len(gvk.Version) == 0 {
return nil, false, NewMissingVersionErr("unstructured object has no version")
}
return []schema.GroupVersionKind{gvk}, false, nil
}
v, err := conversion.EnforcePtr(obj) v, err := conversion.EnforcePtr(obj)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
...@@ -338,7 +341,7 @@ func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error { ...@@ -338,7 +341,7 @@ func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
return nil return nil
} }
// Similar to AddConversionFuncs, but registers conversion functions that were // AddGeneratedConversionFuncs registers conversion functions that were
// automatically generated. // automatically generated.
func (s *Scheme) AddGeneratedConversionFuncs(conversionFuncs ...interface{}) error { func (s *Scheme) AddGeneratedConversionFuncs(conversionFuncs ...interface{}) error {
for _, f := range conversionFuncs { for _, f := range conversionFuncs {
...@@ -396,10 +399,68 @@ func (s *Scheme) Default(src Object) { ...@@ -396,10 +399,68 @@ func (s *Scheme) Default(src Object) {
// testing of conversion functions. Returns an error if the conversion isn't // testing of conversion functions. Returns an error if the conversion isn't
// possible. You can call this with types that haven't been registered (for example, // possible. You can call this with types that haven't been registered (for example,
// a to test conversion of types that are nested within registered types). The // a to test conversion of types that are nested within registered types). The
// context interface is passed to the convertor. // context interface is passed to the convertor. Convert also supports Unstructured
// TODO: identify whether context should be hidden, or behind a formal context/scope // types and will convert them intelligently.
// interface
func (s *Scheme) Convert(in, out interface{}, context interface{}) error { func (s *Scheme) Convert(in, out interface{}, context interface{}) error {
unstructuredIn, okIn := in.(Unstructured)
unstructuredOut, okOut := out.(Unstructured)
switch {
case okIn && okOut:
// converting unstructured input to an unstructured output is a straight copy - unstructured
// is a "smart holder" and the contents are passed by reference between the two objects
unstructuredOut.SetUnstructuredContent(unstructuredIn.UnstructuredContent())
return nil
case okOut:
// if the output is an unstructured object, use the standard Go type to unstructured
// conversion. The object must not be internal.
obj, ok := in.(Object)
if !ok {
return fmt.Errorf("unable to convert object type %T to Unstructured, must be a runtime.Object", in)
}
gvks, unversioned, err := s.ObjectKinds(obj)
if err != nil {
return err
}
gvk := gvks[0]
// if no conversion is necessary, convert immediately
if unversioned || gvk.Version != APIVersionInternal {
content, err := DefaultUnstructuredConverter.ToUnstructured(in)
if err != nil {
return err
}
unstructuredOut.SetUnstructuredContent(content)
return nil
}
// attempt to convert the object to an external version first.
target, ok := context.(GroupVersioner)
if !ok {
return fmt.Errorf("unable to convert the internal object type %T to Unstructured without providing a preferred version to convert to", in)
}
// Convert is implicitly unsafe, so we don't need to perform a safe conversion
versioned, err := s.UnsafeConvertToVersion(obj, target)
if err != nil {
return err
}
content, err := DefaultUnstructuredConverter.ToUnstructured(versioned)
if err != nil {
return err
}
unstructuredOut.SetUnstructuredContent(content)
return nil
case okIn:
// converting an unstructured object to any type is modeled by first converting
// the input to a versioned type, then running standard conversions
typed, err := s.unstructuredToTyped(unstructuredIn)
if err != nil {
return err
}
in = typed
}
flags, meta := s.generateConvertMeta(in) flags, meta := s.generateConvertMeta(in)
meta.Context = context meta.Context = context
if flags == 0 { if flags == 0 {
...@@ -408,8 +469,8 @@ func (s *Scheme) Convert(in, out interface{}, context interface{}) error { ...@@ -408,8 +469,8 @@ func (s *Scheme) Convert(in, out interface{}, context interface{}) error {
return s.converter.Convert(in, out, flags, meta) return s.converter.Convert(in, out, flags, meta)
} }
// Converts the given field label and value for an kind field selector from // ConvertFieldLabel alters the given field label and value for an kind field selector from
// versioned representation to an unversioned one. // versioned representation to an unversioned one or returns an error.
func (s *Scheme) ConvertFieldLabel(version, kind, label, value string) (string, string, error) { func (s *Scheme) ConvertFieldLabel(version, kind, label, value string) (string, string, error) {
if s.fieldLabelConversionFuncs[version] == nil { if s.fieldLabelConversionFuncs[version] == nil {
return DefaultMetaV1FieldSelectorConversion(label, value) return DefaultMetaV1FieldSelectorConversion(label, value)
...@@ -439,15 +500,30 @@ func (s *Scheme) UnsafeConvertToVersion(in Object, target GroupVersioner) (Objec ...@@ -439,15 +500,30 @@ func (s *Scheme) UnsafeConvertToVersion(in Object, target GroupVersioner) (Objec
// convertToVersion handles conversion with an optional copy. // convertToVersion handles conversion with an optional copy.
func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (Object, error) { func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (Object, error) {
// determine the incoming kinds with as few allocations as possible. var t reflect.Type
t := reflect.TypeOf(in)
if t.Kind() != reflect.Ptr { if u, ok := in.(Unstructured); ok {
return nil, fmt.Errorf("only pointer types may be converted: %v", t) typed, err := s.unstructuredToTyped(u)
} if err != nil {
t = t.Elem() return nil, err
if t.Kind() != reflect.Struct { }
return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t)
in = typed
// unstructuredToTyped returns an Object, which must be a pointer to a struct.
t = reflect.TypeOf(in).Elem()
} else {
// determine the incoming kinds with as few allocations as possible.
t = reflect.TypeOf(in)
if t.Kind() != reflect.Ptr {
return nil, fmt.Errorf("only pointer types may be converted: %v", t)
}
t = t.Elem()
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t)
}
} }
kinds, ok := s.typeToGVK[t] kinds, ok := s.typeToGVK[t]
if !ok || len(kinds) == 0 { if !ok || len(kinds) == 0 {
return nil, NewNotRegisteredErrForType(t) return nil, NewNotRegisteredErrForType(t)
...@@ -463,7 +539,6 @@ func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) ( ...@@ -463,7 +539,6 @@ func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (
} }
return copyAndSetTargetKind(copy, in, unversionedKind) return copyAndSetTargetKind(copy, in, unversionedKind)
} }
return nil, NewNotRegisteredErrForTarget(t, target) return nil, NewNotRegisteredErrForTarget(t, target)
} }
...@@ -501,6 +576,25 @@ func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) ( ...@@ -501,6 +576,25 @@ func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (
return out, nil return out, nil
} }
// unstructuredToTyped attempts to transform an unstructured object to a typed
// object if possible. It will return an error if conversion is not possible, or the versioned
// Go form of the object. Note that this conversion will lose fields.
func (s *Scheme) unstructuredToTyped(in Unstructured) (Object, error) {
// the type must be something we recognize
gvks, _, err := s.ObjectKinds(in)
if err != nil {
return nil, err
}
typed, err := s.New(gvks[0])
if err != nil {
return nil, err
}
if err := DefaultUnstructuredConverter.FromUnstructured(in.UnstructuredContent(), typed); err != nil {
return nil, fmt.Errorf("unable to convert unstructured object to %v: %v", gvks[0], err)
}
return typed, nil
}
// generateConvertMeta constructs the meta value we pass to Convert. // generateConvertMeta constructs the meta value we pass to Convert.
func (s *Scheme) generateConvertMeta(in interface{}) (conversion.FieldMatchingFlags, *conversion.Meta) { func (s *Scheme) generateConvertMeta(in interface{}) (conversion.FieldMatchingFlags, *conversion.Meta) {
return s.converter.DefaultMeta(reflect.TypeOf(in)) return s.converter.DefaultMeta(reflect.TypeOf(in))
......
...@@ -150,9 +150,10 @@ func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, i ...@@ -150,9 +150,10 @@ func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, i
} }
if into != nil { if into != nil {
_, isUnstructured := into.(runtime.Unstructured)
types, _, err := s.typer.ObjectKinds(into) types, _, err := s.typer.ObjectKinds(into)
switch { switch {
case runtime.IsNotRegisteredError(err): case runtime.IsNotRegisteredError(err), isUnstructured:
if err := jsoniter.ConfigFastest.Unmarshal(data, into); err != nil { if err := jsoniter.ConfigFastest.Unmarshal(data, into); err != nil {
return nil, actual, err return nil, actual, err
} }
......
...@@ -16,6 +16,7 @@ go_library( ...@@ -16,6 +16,7 @@ go_library(
deps = [ deps = [
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
], ],
) )
......
...@@ -17,8 +17,11 @@ limitations under the License. ...@@ -17,8 +17,11 @@ limitations under the License.
package testing package testing
import ( import (
"fmt"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/json"
) )
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
...@@ -213,3 +216,105 @@ func (obj *MyWeirdCustomEmbeddedVersionKindField) GroupVersionKind() schema.Grou ...@@ -213,3 +216,105 @@ func (obj *MyWeirdCustomEmbeddedVersionKindField) GroupVersionKind() schema.Grou
func (obj *TestType2) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (obj *TestType2) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind }
func (obj *ExternalTestType2) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (obj *ExternalTestType2) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind }
// +k8s:deepcopy-gen=false
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{}
func (obj *Unstructured) GetObjectKind() schema.ObjectKind { return obj }
func (obj *Unstructured) IsList() bool {
if obj.Object != nil {
_, ok := obj.Object["items"]
return ok
}
return false
}
func (obj *Unstructured) EachListItem(fn func(runtime.Object) error) error {
if obj.Object == nil {
return fmt.Errorf("content is not a list")
}
field, ok := obj.Object["items"]
if !ok {
return fmt.Errorf("content is not a list")
}
items, ok := field.([]interface{})
if !ok {
return nil
}
for _, item := range items {
child, ok := item.(map[string]interface{})
if !ok {
return fmt.Errorf("items member is not an object")
}
if err := fn(&Unstructured{Object: child}); err != nil {
return err
}
}
return nil
}
func (obj *Unstructured) UnstructuredContent() map[string]interface{} {
if obj.Object == nil {
obj.Object = make(map[string]interface{})
}
return obj.Object
}
func (obj *Unstructured) SetUnstructuredContent(content map[string]interface{}) {
obj.Object = content
}
// MarshalJSON ensures that the unstructured object produces proper
// JSON when passed to Go's standard JSON library.
func (u *Unstructured) MarshalJSON() ([]byte, error) {
return json.Marshal(u.Object)
}
// UnmarshalJSON ensures that the unstructured object properly decodes
// JSON when passed to Go's standard JSON library.
func (u *Unstructured) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &u.Object)
}
func (in *Unstructured) DeepCopyObject() runtime.Object {
return in.DeepCopy()
}
func (in *Unstructured) DeepCopy() *Unstructured {
if in == nil {
return nil
}
out := new(Unstructured)
*out = *in
out.Object = runtime.DeepCopyJSON(in.Object)
return out
}
func (u *Unstructured) GroupVersionKind() schema.GroupVersionKind {
apiVersion, ok := u.Object["apiVersion"].(string)
if !ok {
return schema.GroupVersionKind{}
}
gv, err := schema.ParseGroupVersion(apiVersion)
if err != nil {
return schema.GroupVersionKind{}
}
kind, ok := u.Object["kind"].(string)
if ok {
return gv.WithKind(kind)
}
return schema.GroupVersionKind{}
}
func (u *Unstructured) SetGroupVersionKind(gvk schema.GroupVersionKind) {
u.Object["apiVersion"] = gvk.GroupVersion().String()
u.Object["kind"] = gvk.Kind
}
...@@ -955,10 +955,6 @@ ...@@ -955,10 +955,6 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/fields", "ImportPath": "k8s.io/apimachinery/pkg/fields",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
......
...@@ -60,7 +60,6 @@ go_library( ...@@ -60,7 +60,6 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
......
...@@ -27,7 +27,6 @@ import ( ...@@ -27,7 +27,6 @@ import (
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/conversion/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
...@@ -232,7 +231,7 @@ func patchResource( ...@@ -232,7 +231,7 @@ func patchResource(
return nil, err return nil, err
} }
// Capture the original object map and patch for possible retries. // Capture the original object map and patch for possible retries.
originalMap, err := unstructured.DefaultConverter.ToUnstructured(currentVersionedObject) originalMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(currentVersionedObject)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -278,7 +277,7 @@ func patchResource( ...@@ -278,7 +277,7 @@ func patchResource(
if err != nil { if err != nil {
return nil, err return nil, err
} }
currentObjMap, err := unstructured.DefaultConverter.ToUnstructured(currentVersionedObject) currentObjMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(currentVersionedObject)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -425,7 +424,7 @@ func strategicPatchObject( ...@@ -425,7 +424,7 @@ func strategicPatchObject(
objToUpdate runtime.Object, objToUpdate runtime.Object,
versionedObj runtime.Object, versionedObj runtime.Object,
) error { ) error {
originalObjMap, err := unstructured.DefaultConverter.ToUnstructured(originalObject) originalObjMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(originalObject)
if err != nil { if err != nil {
return err return err
} }
...@@ -458,7 +457,7 @@ func applyPatchToObject( ...@@ -458,7 +457,7 @@ func applyPatchToObject(
} }
// Rather than serialize the patched map to JSON, then decode it to an object, we go directly from a map to an object // Rather than serialize the patched map to JSON, then decode it to an object, we go directly from a map to an object
if err := unstructured.DefaultConverter.FromUnstructured(patchedObjMap, objToUpdate); err != nil { if err := runtime.DefaultUnstructuredConverter.FromUnstructured(patchedObjMap, objToUpdate); err != nil {
return err return err
} }
// Decoding from JSON to a versioned object would apply defaults, so we do the same here // Decoding from JSON to a versioned object would apply defaults, so we do the same here
......
...@@ -547,10 +547,6 @@ ...@@ -547,10 +547,6 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/fields", "ImportPath": "k8s.io/apimachinery/pkg/fields",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
......
...@@ -49,28 +49,22 @@ func NewUnstructuredObjectTyper(groupResources []*APIGroupResources) *Unstructur ...@@ -49,28 +49,22 @@ func NewUnstructuredObjectTyper(groupResources []*APIGroupResources) *Unstructur
return dot return dot
} }
// ObjectKind returns the group,version,kind of the provided object, or an error
// if the object in not runtime.Unstructured or has no group,version,kind
// information.
func (d *UnstructuredObjectTyper) ObjectKind(obj runtime.Object) (schema.GroupVersionKind, error) {
if _, ok := obj.(runtime.Unstructured); !ok {
return schema.GroupVersionKind{}, fmt.Errorf("type %T is invalid for dynamic object typer", obj)
}
return obj.GetObjectKind().GroupVersionKind(), nil
}
// ObjectKinds returns a slice of one element with the group,version,kind of the // ObjectKinds returns a slice of one element with the group,version,kind of the
// provided object, or an error if the object is not runtime.Unstructured or // provided object, or an error if the object is not runtime.Unstructured or
// has no group,version,kind information. unversionedType will always be false // has no group,version,kind information. unversionedType will always be false
// because runtime.Unstructured object should always have group,version,kind // because runtime.Unstructured object should always have group,version,kind
// information set. // information set.
func (d *UnstructuredObjectTyper) ObjectKinds(obj runtime.Object) (gvks []schema.GroupVersionKind, unversionedType bool, err error) { func (d *UnstructuredObjectTyper) ObjectKinds(obj runtime.Object) (gvks []schema.GroupVersionKind, unversionedType bool, err error) {
gvk, err := d.ObjectKind(obj) if _, ok := obj.(runtime.Unstructured); !ok {
if err != nil { return nil, false, fmt.Errorf("type %T is invalid for dynamic object typer", obj)
return nil, false, err }
gvk := obj.GetObjectKind().GroupVersionKind()
if len(gvk.Kind) == 0 {
return nil, false, runtime.NewMissingKindErr("unstructured object has no kind")
}
if len(gvk.Version) == 0 {
return nil, false, runtime.NewMissingVersionErr("unstructured object has no version")
} }
return []schema.GroupVersionKind{gvk}, false, nil return []schema.GroupVersionKind{gvk}, false, nil
} }
...@@ -80,16 +74,4 @@ func (d *UnstructuredObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool { ...@@ -80,16 +74,4 @@ func (d *UnstructuredObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool {
return d.registered[gvk] return d.registered[gvk]
} }
// IsUnversioned returns false always because runtime.Unstructured objects
// should always have group,version,kind information set. ok will be true if the
// object's group,version,kind is api.Registry.
func (d *UnstructuredObjectTyper) IsUnversioned(obj runtime.Object) (unversioned bool, ok bool) {
gvk, err := d.ObjectKind(obj)
if err != nil {
return false, false
}
return false, d.registered[gvk]
}
var _ runtime.ObjectTyper = &UnstructuredObjectTyper{} var _ runtime.ObjectTyper = &UnstructuredObjectTyper{}
...@@ -667,10 +667,6 @@ ...@@ -667,10 +667,6 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/fields", "ImportPath": "k8s.io/apimachinery/pkg/fields",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
......
...@@ -15,10 +15,6 @@ ...@@ -15,10 +15,6 @@
"Rev": "5bd2802263f21d8788851d5305584c82a5c75d7e" "Rev": "5bd2802263f21d8788851d5305584c82a5c75d7e"
}, },
{ {
"ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "782f4967f2dc4564575ca782fe2d04090b5faca8"
},
{
"ImportPath": "github.com/emicklei/go-restful", "ImportPath": "github.com/emicklei/go-restful",
"Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46" "Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46"
}, },
...@@ -307,10 +303,6 @@ ...@@ -307,10 +303,6 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/api/equality",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/errors", "ImportPath": "k8s.io/apimachinery/pkg/api/errors",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
...@@ -355,10 +347,6 @@ ...@@ -355,10 +347,6 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/fields", "ImportPath": "k8s.io/apimachinery/pkg/fields",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
...@@ -411,10 +399,6 @@ ...@@ -411,10 +399,6 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/diff",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/errors", "ImportPath": "k8s.io/apimachinery/pkg/util/errors",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
......
...@@ -667,10 +667,6 @@ ...@@ -667,10 +667,6 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/fields", "ImportPath": "k8s.io/apimachinery/pkg/fields",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
......
...@@ -343,10 +343,6 @@ ...@@ -343,10 +343,6 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/api/equality",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/errors", "ImportPath": "k8s.io/apimachinery/pkg/api/errors",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
...@@ -383,10 +379,6 @@ ...@@ -383,10 +379,6 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/fields", "ImportPath": "k8s.io/apimachinery/pkg/fields",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
......
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