diff --git a/pkg/api/latest/latest_test.go b/pkg/api/latest/latest_test.go index f484fadcff6..e446fdb3b2b 100644 --- a/pkg/api/latest/latest_test.go +++ b/pkg/api/latest/latest_test.go @@ -25,7 +25,9 @@ import ( internal "github.com/GoogleCloudPlatform/kubernetes/pkg/api" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + docker "github.com/fsouza/go-dockerclient" fuzz "github.com/google/gofuzz" ) @@ -83,6 +85,26 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( // only replicas round trips j.Replicas = int(c.RandUint64()) }, + func(j *internal.List, c fuzz.Continue) { + c.Fuzz(&j.ListMeta) + c.Fuzz(&j.Items) + if j.Items == nil { + j.Items = []runtime.Object{} + } + }, + func(j *runtime.Object, c fuzz.Continue) { + if c.RandBool() { + *j = &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{Kind: "Something", APIVersion: "unknown"}, + RawJSON: []byte(`{"apiVersion":"unknown","kind":"Something","someKey":"someValue"}`), + } + } else { + types := []runtime.Object{&internal.Pod{}, &internal.ReplicationController{}} + t := types[c.Rand.Intn(len(types))] + c.Fuzz(t) + *j = t + } + }, func(intstr *util.IntOrString, c fuzz.Continue) { // util.IntOrString will panic if its kind is set wrong. if c.RandBool() { diff --git a/pkg/api/register.go b/pkg/api/register.go index 0d4ef3a389b..013ffcde1a5 100644 --- a/pkg/api/register.go +++ b/pkg/api/register.go @@ -45,6 +45,7 @@ func init() { &ContainerManifestList{}, &BoundPod{}, &BoundPods{}, + &List{}, ) // Legacy names are supported Scheme.AddKnownTypeWithName("", "Minion", &Node{}) @@ -71,3 +72,4 @@ func (*ContainerManifest) IsAnAPIObject() {} func (*ContainerManifestList) IsAnAPIObject() {} func (*BoundPod) IsAnAPIObject() {} func (*BoundPods) IsAnAPIObject() {} +func (*List) IsAnAPIObject() {} diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index ddd32ac0097..e65bffe1270 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -48,15 +48,6 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( // APIVersion and Kind must remain blank in memory. j.APIVersion = "" j.Kind = "" - - j.Name = c.RandString() - j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) - j.SelfLink = c.RandString() - - var sec, nsec int64 - c.Fuzz(&sec) - c.Fuzz(&nsec) - j.CreationTimestamp = util.Unix(sec, nsec).Rfc3339Copy() }, func(j *api.TypeMeta, c fuzz.Continue) { // We have to customize the randomization of TypeMetas because their @@ -99,6 +90,26 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( // only replicas round trips j.Replicas = int(c.RandUint64()) }, + func(j *api.List, c fuzz.Continue) { + c.Fuzz(&j.ListMeta) + c.Fuzz(&j.Items) + if j.Items == nil { + j.Items = []runtime.Object{} + } + }, + func(j *runtime.Object, c fuzz.Continue) { + if c.RandBool() { + *j = &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{Kind: "Something", APIVersion: "unknown"}, + RawJSON: []byte(`{"apiVersion":"unknown","kind":"Something","someKey":"someValue"}`), + } + } else { + types := []runtime.Object{&api.Pod{}, &api.ReplicationController{}} + t := types[c.Rand.Intn(len(types))] + c.Fuzz(t) + *j = t + } + }, func(intstr *util.IntOrString, c fuzz.Continue) { // util.IntOrString will panic if its kind is set wrong. if c.RandBool() { @@ -154,7 +165,7 @@ func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) { obj2, err := codec.Decode(data) if err != nil { - t.Errorf("%v: %v", name, err) + t.Errorf("0: %v: %v\nCodec: %v\nData: %s\nSource: %#v", name, err, codec, string(data), source) return } if !reflect.DeepEqual(source, obj2) { @@ -188,7 +199,21 @@ func TestSpecificKind(t *testing.T) { api.Scheme.Log(nil) } +func TestList(t *testing.T) { + api.Scheme.Log(t) + kind := "List" + item, err := api.Scheme.New("", kind) + if err != nil { + t.Errorf("Couldn't make a %v? %v", kind, err) + return + } + runTest(t, v1beta1.Codec, item) + runTest(t, v1beta2.Codec, item) + api.Scheme.Log(nil) +} + var nonRoundTrippableTypes = util.NewStringSet("ContainerManifest") +var nonInternalRoundTrippableTypes = util.NewStringSet("List") func TestRoundTripTypes(t *testing.T) { for kind := range api.Scheme.KnownTypes("") { @@ -206,7 +231,9 @@ func TestRoundTripTypes(t *testing.T) { } runTest(t, v1beta1.Codec, item) runTest(t, v1beta2.Codec, item) - runTest(t, api.Codec, item) + if !nonInternalRoundTrippableTypes.Has(kind) { + runTest(t, api.Codec, item) + } } } } diff --git a/pkg/api/types.go b/pkg/api/types.go index 54501dea5e0..cd1b40c75b8 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -19,6 +19,7 @@ package api import ( "time" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -1000,3 +1001,11 @@ type BoundPods struct { // Items is the list of all pods bound to a given host. Items []BoundPod `json:"items"` } + +// List holds a list of objects, which may not be known by the server. +type List struct { + TypeMeta `json:",inline"` + ListMeta `json:"metadata,omitempty"` + + Items []runtime.Object `json:"items"` +} diff --git a/pkg/api/v1beta1/register.go b/pkg/api/v1beta1/register.go index 75e6bfc848d..ad779eae716 100644 --- a/pkg/api/v1beta1/register.go +++ b/pkg/api/v1beta1/register.go @@ -50,6 +50,7 @@ func init() { &ContainerManifestList{}, &BoundPod{}, &BoundPods{}, + &List{}, ) // Future names are supported api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{}) @@ -76,3 +77,4 @@ func (*ContainerManifest) IsAnAPIObject() {} func (*ContainerManifestList) IsAnAPIObject() {} func (*BoundPod) IsAnAPIObject() {} func (*BoundPods) IsAnAPIObject() {} +func (*List) IsAnAPIObject() {} diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 108ec89fce9..9780468d1db 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -19,6 +19,7 @@ package v1beta1 import ( "time" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -782,3 +783,9 @@ type BoundPods struct { // Items is the list of all pods bound to a given host. Items []BoundPod `json:"items" description:"list of all pods bound to a given host"` } + +// List holds a list of objects, which may not be known by the server. +type List struct { + TypeMeta `json:",inline"` + Items []runtime.RawExtension `json:"items" description:"list of objects"` +} diff --git a/pkg/api/v1beta2/register.go b/pkg/api/v1beta2/register.go index 4f70be9fb8f..8e85d2e8cdf 100644 --- a/pkg/api/v1beta2/register.go +++ b/pkg/api/v1beta2/register.go @@ -50,6 +50,7 @@ func init() { &ContainerManifestList{}, &BoundPod{}, &BoundPods{}, + &List{}, ) // Future names are supported api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{}) @@ -76,3 +77,4 @@ func (*ContainerManifest) IsAnAPIObject() {} func (*ContainerManifestList) IsAnAPIObject() {} func (*BoundPod) IsAnAPIObject() {} func (*BoundPods) IsAnAPIObject() {} +func (*List) IsAnAPIObject() {} diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index dd2268c094b..42da500ed6d 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -19,6 +19,7 @@ package v1beta2 import ( "time" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -783,3 +784,9 @@ type BoundPods struct { // Items is the list of all pods bound to a given host. Items []BoundPod `json:"items" description:"list of all pods bound to a given host"` } + +// List holds a list of objects, which may not be known by the server. +type List struct { + TypeMeta `json:",inline"` + Items []runtime.RawExtension `json:"items" description:"list of objects"` +} diff --git a/pkg/api/v1beta3/register.go b/pkg/api/v1beta3/register.go index 6cfd094e5aa..bc474da4139 100644 --- a/pkg/api/v1beta3/register.go +++ b/pkg/api/v1beta3/register.go @@ -46,6 +46,7 @@ func init() { &OperationList{}, &Event{}, &EventList{}, + &List{}, ) // Legacy names are supported api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{}) @@ -72,3 +73,4 @@ func (*Operation) IsAnAPIObject() {} func (*OperationList) IsAnAPIObject() {} func (*Event) IsAnAPIObject() {} func (*EventList) IsAnAPIObject() {} +func (*List) IsAnAPIObject() {} diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index 69652e716ab..320301fe474 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -19,6 +19,7 @@ package v1beta3 import ( "time" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -556,9 +557,14 @@ type ReplicationControllerSpec struct { // Selector is a label query over pods that should match the Replicas count. Selector map[string]string `json:"selector,omitempty"` - // Template is a reference to an object that describes the pod that will be created if + // TemplateRef is a reference to an object that describes the pod that will be created if // insufficient replicas are detected. - Template ObjectReference `json:"template,omitempty"` + TemplateRef *ObjectReference `json:"templateRef,omitempty"` + + // Template is the object that describes the pod that will be created if + // insufficient replicas are detected. This takes precedence over a + // TemplateRef. + Template *PodTemplateSpec `json:"template,omitempty"` } // ReplicationControllerStatus represents the current status of a replication @@ -944,3 +950,11 @@ type EventList struct { Items []Event `json:"items"` } + +// List holds a list of objects, which may not be known by the server. +type List struct { + TypeMeta `json:",inline"` + ListMeta `json:"metadata,omitempty"` + + Items []runtime.RawExtension `json:"items" description:"list of objects"` +} diff --git a/pkg/api/validation/schema_test.go b/pkg/api/validation/schema_test.go index 88722e9e137..12e78f94892 100644 --- a/pkg/api/validation/schema_test.go +++ b/pkg/api/validation/schema_test.go @@ -49,15 +49,6 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( // APIVersion and Kind must remain blank in memory. j.APIVersion = "" j.Kind = "" - - j.Name = c.RandString() - j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) - j.SelfLink = c.RandString() - - var sec, nsec int64 - c.Fuzz(&sec) - c.Fuzz(&nsec) - j.CreationTimestamp = util.Unix(sec, nsec).Rfc3339Copy() }, func(j *api.TypeMeta, c fuzz.Continue) { // We have to customize the randomization of TypeMetas because their diff --git a/pkg/conversion/decode.go b/pkg/conversion/decode.go index fe90bbe44e0..6e7b50de4e7 100644 --- a/pkg/conversion/decode.go +++ b/pkg/conversion/decode.go @@ -112,7 +112,7 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error { } else { external, err := s.NewObject(dataVersion, dataKind) if err != nil { - return fmt.Errorf("unable to create new object of type ('%s', '%s')", dataVersion, dataKind) + return err } // yaml is a superset of json, so we use it to decode here. That way, // we understand both. diff --git a/pkg/conversion/error.go b/pkg/conversion/error.go new file mode 100644 index 00000000000..7ee24ee4380 --- /dev/null +++ b/pkg/conversion/error.go @@ -0,0 +1,51 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 conversion + +import ( + "fmt" + "reflect" +) + +type notRegisteredErr struct { + kind string + version string + t reflect.Type +} + +func (k *notRegisteredErr) Error() string { + if k.t != nil { + return fmt.Sprintf("no kind is registered for the type %v", k.t) + } + if len(k.kind) == 0 { + return fmt.Sprintf("no version %q has been registered", k.version) + } + if len(k.version) == 0 { + return fmt.Sprintf("no kind %q is registered for the default version", k.kind) + } + return fmt.Sprintf("no kind %q is registered for version %q", k.kind, k.version) +} + +// IsNotRegisteredError returns true if the error indicates the provided +// object or input data is not registered. +func IsNotRegisteredError(err error) bool { + if err == nil { + return false + } + _, ok := err.(*notRegisteredErr) + return ok +} diff --git a/pkg/conversion/scheme.go b/pkg/conversion/scheme.go index 84e05b8bd87..05cc6f0f5b5 100644 --- a/pkg/conversion/scheme.go +++ b/pkg/conversion/scheme.go @@ -145,14 +145,14 @@ func (s *Scheme) KnownTypes(version string) map[string]reflect.Type { // NewObject returns a new object of the given version and name, // or an error if it hasn't been registered. -func (s *Scheme) NewObject(versionName, typeName string) (interface{}, error) { +func (s *Scheme) NewObject(versionName, kind string) (interface{}, error) { if types, ok := s.versionMap[versionName]; ok { - if t, ok := types[typeName]; ok { + if t, ok := types[kind]; ok { return reflect.New(t).Interface(), nil } - return nil, fmt.Errorf("no type '%v' for version '%v'", typeName, versionName) + return nil, ¬RegisteredErr{kind: kind, version: versionName} } - return nil, fmt.Errorf("no version '%v'", versionName) + return nil, ¬RegisteredErr{kind: kind, version: versionName} } // AddConversionFuncs adds functions to the list of conversion functions. The given @@ -187,8 +187,7 @@ func (s *Scheme) NewObject(versionName, typeName string) (interface{}, error) { // add conversion functions for things with changed/removed fields. func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error { for _, f := range conversionFuncs { - err := s.converter.Register(f) - if err != nil { + if err := s.converter.Register(f); err != nil { return err } } @@ -286,7 +285,7 @@ func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string, version, vOK := s.typeToVersion[t] kinds, kOK := s.typeToKind[t] if !vOK || !kOK { - return "", "", fmt.Errorf("unregistered type: %v", t) + return "", "", ¬RegisteredErr{t: t} } apiVersion = version kind = kinds[0] diff --git a/pkg/runtime/embedded_test.go b/pkg/runtime/embedded_test.go index 9fb795c95bb..90aae83f1fe 100644 --- a/pkg/runtime/embedded_test.go +++ b/pkg/runtime/embedded_test.go @@ -22,16 +22,14 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) -var scheme = runtime.NewScheme() -var Codec = runtime.CodecFor(scheme, "v1test") - type EmbeddedTest struct { - runtime.TypeMeta `json:",inline"` - ID string `json:"id,omitempty"` - Object runtime.EmbeddedObject `json:"object,omitempty"` - EmptyObject runtime.EmbeddedObject `json:"emptyObject,omitempty"` + runtime.TypeMeta + ID string + Object runtime.EmbeddedObject + EmptyObject runtime.EmbeddedObject } type EmbeddedTestExternal struct { @@ -41,21 +39,91 @@ type EmbeddedTestExternal struct { EmptyObject runtime.RawExtension `json:"emptyObject,omitempty"` } +type ObjectTest struct { + runtime.TypeMeta + + ID string + Items []runtime.Object +} + +type ObjectTestExternal struct { + runtime.TypeMeta `yaml:",inline" json:",inline"` + + ID string `json:"id,omitempty"` + Items []runtime.RawExtension `json:"items,omitempty"` +} + +func (*ObjectTest) IsAnAPIObject() {} +func (*ObjectTestExternal) IsAnAPIObject() {} func (*EmbeddedTest) IsAnAPIObject() {} func (*EmbeddedTestExternal) IsAnAPIObject() {} +func TestDecodeEmptyRawExtensionAsObject(t *testing.T) { + s := runtime.NewScheme() + s.AddKnownTypes("", &ObjectTest{}) + s.AddKnownTypeWithName("v1test", "ObjectTest", &ObjectTestExternal{}) + + _, err := s.Decode([]byte(`{"kind":"ObjectTest","apiVersion":"v1test","items":[{}]}`)) + if err == nil { + t.Fatalf("unexpected non-error") + } +} + +func TestArrayOfRuntimeObject(t *testing.T) { + s := runtime.NewScheme() + s.AddKnownTypes("", &EmbeddedTest{}) + s.AddKnownTypeWithName("v1test", "EmbeddedTest", &EmbeddedTestExternal{}) + s.AddKnownTypes("", &ObjectTest{}) + s.AddKnownTypeWithName("v1test", "ObjectTest", &ObjectTestExternal{}) + + internal := &ObjectTest{ + Items: []runtime.Object{ + &EmbeddedTest{ID: "foo"}, + &EmbeddedTest{ID: "bar"}, + // TODO: until YAML is removed, this JSON must be in ascending key order to ensure consistent roundtrip serialization + &runtime.Unknown{RawJSON: []byte(`{"apiVersion":"unknown","foo":"bar","kind":"OtherTest"}`)}, + &ObjectTest{ + Items: []runtime.Object{ + &EmbeddedTest{ID: "baz"}, + }, + }, + }, + } + wire, err := s.EncodeToVersion(internal, "v1test") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Logf("Wire format is:\n%s\n", string(wire)) + + obj := &ObjectTestExternal{} + if err := json.Unmarshal(wire, obj); err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Logf("exact wire is: %#v", string(obj.Items[0].RawJSON)) + + decoded, err := s.Decode(wire) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + internal.Items[2].(*runtime.Unknown).Kind = "OtherTest" + internal.Items[2].(*runtime.Unknown).APIVersion = "unknown" + if e, a := internal, decoded; !reflect.DeepEqual(e, a) { + t.Log(string(decoded.(*ObjectTest).Items[2].(*runtime.Unknown).RawJSON)) + t.Errorf("mismatched decoded: %s", util.ObjectDiff(e, a)) + } +} + func TestEmbeddedObject(t *testing.T) { - s := scheme + s := runtime.NewScheme() s.AddKnownTypes("", &EmbeddedTest{}) s.AddKnownTypeWithName("v1test", "EmbeddedTest", &EmbeddedTestExternal{}) outer := &EmbeddedTest{ - TypeMeta: runtime.TypeMeta{Name: "outer"}, - ID: "outer", + ID: "outer", Object: runtime.EmbeddedObject{ &EmbeddedTest{ - TypeMeta: runtime.TypeMeta{Name: "inner"}, - ID: "inner", + ID: "inner", }, }, } @@ -83,7 +151,7 @@ func TestEmbeddedObject(t *testing.T) { if err != nil { t.Fatalf("Unexpected decode error %v", err) } - if externalViaJSON.Kind == "" || externalViaJSON.APIVersion == "" || externalViaJSON.Name != "outer" { + if externalViaJSON.Kind == "" || externalViaJSON.APIVersion == "" || externalViaJSON.ID != "outer" { t.Errorf("Expected objects to have type info set, got %#v", externalViaJSON) } if !reflect.DeepEqual(externalViaJSON.EmptyObject.RawJSON, []byte("null")) || len(externalViaJSON.Object.RawJSON) == 0 { diff --git a/pkg/runtime/error.go b/pkg/runtime/error.go new file mode 100644 index 00000000000..a84fe6ae9f3 --- /dev/null +++ b/pkg/runtime/error.go @@ -0,0 +1,27 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 runtime + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" +) + +// IsNotRegisteredError returns true if the error indicates the provided +// object or input data is not registered. +func IsNotRegisteredError(err error) bool { + return conversion.IsNotRegisteredError(err) +} diff --git a/pkg/runtime/extension.go b/pkg/runtime/extension.go index 8f94f2b578f..982a4c4f69e 100644 --- a/pkg/runtime/extension.go +++ b/pkg/runtime/extension.go @@ -26,6 +26,8 @@ func (re *RawExtension) UnmarshalJSON(in []byte) error { return nil } -func (re *RawExtension) MarshalJSON() ([]byte, error) { +// Marshal may get called on pointers or values, so implement MarshalJSON on value. +// http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go +func (re RawExtension) MarshalJSON() ([]byte, error) { return re.RawJSON, nil } diff --git a/pkg/runtime/extension_test.go b/pkg/runtime/extension_test.go new file mode 100644 index 00000000000..07c0f9401ed --- /dev/null +++ b/pkg/runtime/extension_test.go @@ -0,0 +1,39 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 runtime_test + +import ( + "encoding/json" + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" +) + +func TestEmbeddedRawExtensionMarshal(t *testing.T) { + type test struct { + Ext runtime.RawExtension + } + + extension := test{Ext: runtime.RawExtension{RawJSON: []byte(`{"foo":"bar"}`)}} + data, err := json.Marshal(extension) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if string(data) != `{"Ext":{"foo":"bar"}}` { + t.Errorf("unexpected data: %s", string(data)) + } +} diff --git a/pkg/runtime/scheme.go b/pkg/runtime/scheme.go index 3984eabd2d5..0a3f8d040af 100644 --- a/pkg/runtime/scheme.go +++ b/pkg/runtime/scheme.go @@ -140,15 +140,77 @@ func (self *Scheme) rawExtensionToEmbeddedObject(in *RawExtension, out *Embedded return nil } +// runtimeObjectToRawExtensionArray takes a list of objects and encodes them as RawExtension in the output version +// defined by the conversion.Scope. If objects must be encoded to different schema versions you should set them as +// runtime.Unknown in the internal version instead. +func (self *Scheme) runtimeObjectToRawExtensionArray(in *[]Object, out *[]RawExtension, s conversion.Scope) error { + src := *in + dest := make([]RawExtension, len(src)) + + _, outVersion, scheme := self.fromScope(s) + + for i := range src { + switch t := src[i].(type) { + case *Unknown: + dest[i].RawJSON = t.RawJSON + default: + data, err := scheme.EncodeToVersion(src[i], outVersion) + if err != nil { + return err + } + dest[i].RawJSON = data + } + } + *out = dest + return nil +} + +// rawExtensionToRuntimeObjectArray attempts to decode objects from the array - if they are unrecognized objects, +// they are added as Unknown. +func (self *Scheme) rawExtensionToRuntimeObjectArray(in *[]RawExtension, out *[]Object, s conversion.Scope) error { + src := *in + dest := make([]Object, len(src)) + + _, _, scheme := self.fromScope(s) + + for i := range src { + data := src[i].RawJSON + obj, err := scheme.Decode(data) + if err != nil { + if !IsNotRegisteredError(err) { + return err + } + version, kind, err := scheme.raw.DataVersionAndKind(data) + if err != nil { + return err + } + obj = &Unknown{ + TypeMeta: TypeMeta{ + APIVersion: version, + Kind: kind, + }, + RawJSON: data, + } + } + dest[i] = obj + } + *out = dest + return nil +} + // NewScheme creates a new Scheme. This scheme is pluggable by default. func NewScheme() *Scheme { s := &Scheme{conversion.NewScheme()} s.raw.InternalVersion = "" s.raw.MetaFactory = conversion.SimpleMetaFactory{BaseFields: []string{"TypeMeta"}, VersionField: "APIVersion", KindField: "Kind"} - s.raw.AddConversionFuncs( + if err := s.raw.AddConversionFuncs( s.embeddedObjectToRawExtension, s.rawExtensionToEmbeddedObject, - ) + s.runtimeObjectToRawExtensionArray, + s.rawExtensionToRuntimeObjectArray, + ); err != nil { + panic(err) + } return s } @@ -304,6 +366,8 @@ func (s *Scheme) DecodeInto(data []byte, obj Object) error { // Copy does a deep copy of an API object. Useful mostly for tests. // TODO(dbsmith): implement directly instead of via Encode/Decode +// TODO(claytonc): Copy cannot be used for objects which do not encode type information, such +// as lists of runtime.Objects func (s *Scheme) Copy(obj Object) (Object, error) { data, err := s.EncodeToVersion(obj, "") if err != nil { diff --git a/pkg/runtime/types.go b/pkg/runtime/types.go index aa7eec39201..da7e0e93c5d 100644 --- a/pkg/runtime/types.go +++ b/pkg/runtime/types.go @@ -16,9 +16,7 @@ limitations under the License. package runtime -import ( - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" -) +import () // Note that the types provided in this file are not versioned and are intended to be // safe to use from within all versions of every API object. @@ -35,15 +33,8 @@ import ( // your own with the same fields. // type TypeMeta struct { - APIVersion string `json:"apiVersion,omitempty"` - Kind string `json:"kind,omitempty"` - - Namespace string `json:"namespace,omitempty"` - Name string `json:"name,omitempty"` - UID string `json:"uid,omitempty"` - CreationTimestamp util.Time `json:"creationTimestamp,omitempty"` - SelfLink string `json:"selfLink,omitempty"` - ResourceVersion string `json:"resourceVersion,omitempty"` + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` } // PluginBase is like TypeMeta, but it's intended for plugin objects that won't ever be encoded