Merge pull request #2786 from smarterclayton/load_opaque_objects

Allow runtime.Object to be encoded as runtime.RawExtension
pull/6/head
Daniel Smith 2014-12-10 16:24:05 -08:00
commit f81ec248d0
20 changed files with 384 additions and 58 deletions

View File

@ -25,7 +25,9 @@ import (
internal "github.com/GoogleCloudPlatform/kubernetes/pkg/api" internal "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
docker "github.com/fsouza/go-dockerclient" docker "github.com/fsouza/go-dockerclient"
fuzz "github.com/google/gofuzz" fuzz "github.com/google/gofuzz"
) )
@ -83,6 +85,26 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
// only replicas round trips // only replicas round trips
j.Replicas = int(c.RandUint64()) 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) { func(intstr *util.IntOrString, c fuzz.Continue) {
// util.IntOrString will panic if its kind is set wrong. // util.IntOrString will panic if its kind is set wrong.
if c.RandBool() { if c.RandBool() {

View File

@ -45,6 +45,7 @@ func init() {
&ContainerManifestList{}, &ContainerManifestList{},
&BoundPod{}, &BoundPod{},
&BoundPods{}, &BoundPods{},
&List{},
) )
// Legacy names are supported // Legacy names are supported
Scheme.AddKnownTypeWithName("", "Minion", &Node{}) Scheme.AddKnownTypeWithName("", "Minion", &Node{})
@ -71,3 +72,4 @@ func (*ContainerManifest) IsAnAPIObject() {}
func (*ContainerManifestList) IsAnAPIObject() {} func (*ContainerManifestList) IsAnAPIObject() {}
func (*BoundPod) IsAnAPIObject() {} func (*BoundPod) IsAnAPIObject() {}
func (*BoundPods) IsAnAPIObject() {} func (*BoundPods) IsAnAPIObject() {}
func (*List) IsAnAPIObject() {}

View File

@ -48,15 +48,6 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
// APIVersion and Kind must remain blank in memory. // APIVersion and Kind must remain blank in memory.
j.APIVersion = "" j.APIVersion = ""
j.Kind = "" 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) { func(j *api.TypeMeta, c fuzz.Continue) {
// We have to customize the randomization of TypeMetas because their // 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 // only replicas round trips
j.Replicas = int(c.RandUint64()) 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) { func(intstr *util.IntOrString, c fuzz.Continue) {
// util.IntOrString will panic if its kind is set wrong. // util.IntOrString will panic if its kind is set wrong.
if c.RandBool() { if c.RandBool() {
@ -154,7 +165,7 @@ func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) {
obj2, err := codec.Decode(data) obj2, err := codec.Decode(data)
if err != nil { 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 return
} }
if !reflect.DeepEqual(source, obj2) { if !reflect.DeepEqual(source, obj2) {
@ -188,7 +199,21 @@ func TestSpecificKind(t *testing.T) {
api.Scheme.Log(nil) 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 nonRoundTrippableTypes = util.NewStringSet("ContainerManifest")
var nonInternalRoundTrippableTypes = util.NewStringSet("List")
func TestRoundTripTypes(t *testing.T) { func TestRoundTripTypes(t *testing.T) {
for kind := range api.Scheme.KnownTypes("") { for kind := range api.Scheme.KnownTypes("") {
@ -206,9 +231,11 @@ func TestRoundTripTypes(t *testing.T) {
} }
runTest(t, v1beta1.Codec, item) runTest(t, v1beta1.Codec, item)
runTest(t, v1beta2.Codec, item) runTest(t, v1beta2.Codec, item)
if !nonInternalRoundTrippableTypes.Has(kind) {
runTest(t, api.Codec, item) runTest(t, api.Codec, item)
} }
} }
}
} }
func TestEncode_Ptr(t *testing.T) { func TestEncode_Ptr(t *testing.T) {

View File

@ -19,6 +19,7 @@ package api
import ( import (
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "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 is the list of all pods bound to a given host.
Items []BoundPod `json:"items"` 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"`
}

View File

@ -50,6 +50,7 @@ func init() {
&ContainerManifestList{}, &ContainerManifestList{},
&BoundPod{}, &BoundPod{},
&BoundPods{}, &BoundPods{},
&List{},
) )
// Future names are supported // Future names are supported
api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{}) api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{})
@ -76,3 +77,4 @@ func (*ContainerManifest) IsAnAPIObject() {}
func (*ContainerManifestList) IsAnAPIObject() {} func (*ContainerManifestList) IsAnAPIObject() {}
func (*BoundPod) IsAnAPIObject() {} func (*BoundPod) IsAnAPIObject() {}
func (*BoundPods) IsAnAPIObject() {} func (*BoundPods) IsAnAPIObject() {}
func (*List) IsAnAPIObject() {}

View File

@ -19,6 +19,7 @@ package v1beta1
import ( import (
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "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 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"` 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"`
}

View File

@ -50,6 +50,7 @@ func init() {
&ContainerManifestList{}, &ContainerManifestList{},
&BoundPod{}, &BoundPod{},
&BoundPods{}, &BoundPods{},
&List{},
) )
// Future names are supported // Future names are supported
api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{}) api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{})
@ -76,3 +77,4 @@ func (*ContainerManifest) IsAnAPIObject() {}
func (*ContainerManifestList) IsAnAPIObject() {} func (*ContainerManifestList) IsAnAPIObject() {}
func (*BoundPod) IsAnAPIObject() {} func (*BoundPod) IsAnAPIObject() {}
func (*BoundPods) IsAnAPIObject() {} func (*BoundPods) IsAnAPIObject() {}
func (*List) IsAnAPIObject() {}

View File

@ -19,6 +19,7 @@ package v1beta2
import ( import (
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "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 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"` 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"`
}

View File

@ -46,6 +46,7 @@ func init() {
&OperationList{}, &OperationList{},
&Event{}, &Event{},
&EventList{}, &EventList{},
&List{},
) )
// Legacy names are supported // Legacy names are supported
api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{}) api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{})
@ -72,3 +73,4 @@ func (*Operation) IsAnAPIObject() {}
func (*OperationList) IsAnAPIObject() {} func (*OperationList) IsAnAPIObject() {}
func (*Event) IsAnAPIObject() {} func (*Event) IsAnAPIObject() {}
func (*EventList) IsAnAPIObject() {} func (*EventList) IsAnAPIObject() {}
func (*List) IsAnAPIObject() {}

View File

@ -19,6 +19,7 @@ package v1beta3
import ( import (
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "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 is a label query over pods that should match the Replicas count.
Selector map[string]string `json:"selector,omitempty"` 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. // 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 // ReplicationControllerStatus represents the current status of a replication
@ -944,3 +950,11 @@ type EventList struct {
Items []Event `json:"items"` 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"`
}

View File

@ -49,15 +49,6 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
// APIVersion and Kind must remain blank in memory. // APIVersion and Kind must remain blank in memory.
j.APIVersion = "" j.APIVersion = ""
j.Kind = "" 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) { func(j *api.TypeMeta, c fuzz.Continue) {
// We have to customize the randomization of TypeMetas because their // We have to customize the randomization of TypeMetas because their

View File

@ -112,7 +112,7 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
} else { } else {
external, err := s.NewObject(dataVersion, dataKind) external, err := s.NewObject(dataVersion, dataKind)
if err != nil { 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, // yaml is a superset of json, so we use it to decode here. That way,
// we understand both. // we understand both.

51
pkg/conversion/error.go Normal file
View File

@ -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
}

View File

@ -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, // NewObject returns a new object of the given version and name,
// or an error if it hasn't been registered. // 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 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 reflect.New(t).Interface(), nil
} }
return nil, fmt.Errorf("no type '%v' for version '%v'", typeName, versionName) return nil, &notRegisteredErr{kind: kind, version: versionName}
} }
return nil, fmt.Errorf("no version '%v'", versionName) return nil, &notRegisteredErr{kind: kind, version: versionName}
} }
// AddConversionFuncs adds functions to the list of conversion functions. The given // 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. // add conversion functions for things with changed/removed fields.
func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error { func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
for _, f := range conversionFuncs { for _, f := range conversionFuncs {
err := s.converter.Register(f) if err := s.converter.Register(f); err != nil {
if err != nil {
return err return err
} }
} }
@ -286,7 +285,7 @@ func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string,
version, vOK := s.typeToVersion[t] version, vOK := s.typeToVersion[t]
kinds, kOK := s.typeToKind[t] kinds, kOK := s.typeToKind[t]
if !vOK || !kOK { if !vOK || !kOK {
return "", "", fmt.Errorf("unregistered type: %v", t) return "", "", &notRegisteredErr{t: t}
} }
apiVersion = version apiVersion = version
kind = kinds[0] kind = kinds[0]

View File

@ -22,16 +22,14 @@ import (
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "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 { type EmbeddedTest struct {
runtime.TypeMeta `json:",inline"` runtime.TypeMeta
ID string `json:"id,omitempty"` ID string
Object runtime.EmbeddedObject `json:"object,omitempty"` Object runtime.EmbeddedObject
EmptyObject runtime.EmbeddedObject `json:"emptyObject,omitempty"` EmptyObject runtime.EmbeddedObject
} }
type EmbeddedTestExternal struct { type EmbeddedTestExternal struct {
@ -41,20 +39,90 @@ type EmbeddedTestExternal struct {
EmptyObject runtime.RawExtension `json:"emptyObject,omitempty"` 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 (*EmbeddedTest) IsAnAPIObject() {}
func (*EmbeddedTestExternal) 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) { func TestEmbeddedObject(t *testing.T) {
s := scheme s := runtime.NewScheme()
s.AddKnownTypes("", &EmbeddedTest{}) s.AddKnownTypes("", &EmbeddedTest{})
s.AddKnownTypeWithName("v1test", "EmbeddedTest", &EmbeddedTestExternal{}) s.AddKnownTypeWithName("v1test", "EmbeddedTest", &EmbeddedTestExternal{})
outer := &EmbeddedTest{ outer := &EmbeddedTest{
TypeMeta: runtime.TypeMeta{Name: "outer"},
ID: "outer", ID: "outer",
Object: runtime.EmbeddedObject{ Object: runtime.EmbeddedObject{
&EmbeddedTest{ &EmbeddedTest{
TypeMeta: runtime.TypeMeta{Name: "inner"},
ID: "inner", ID: "inner",
}, },
}, },
@ -83,7 +151,7 @@ func TestEmbeddedObject(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Unexpected decode error %v", err) 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) 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 { if !reflect.DeepEqual(externalViaJSON.EmptyObject.RawJSON, []byte("null")) || len(externalViaJSON.Object.RawJSON) == 0 {

27
pkg/runtime/error.go Normal file
View File

@ -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)
}

View File

@ -26,6 +26,8 @@ func (re *RawExtension) UnmarshalJSON(in []byte) error {
return nil 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 return re.RawJSON, nil
} }

View File

@ -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))
}
}

View File

@ -140,15 +140,77 @@ func (self *Scheme) rawExtensionToEmbeddedObject(in *RawExtension, out *Embedded
return nil 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. // NewScheme creates a new Scheme. This scheme is pluggable by default.
func NewScheme() *Scheme { func NewScheme() *Scheme {
s := &Scheme{conversion.NewScheme()} s := &Scheme{conversion.NewScheme()}
s.raw.InternalVersion = "" s.raw.InternalVersion = ""
s.raw.MetaFactory = conversion.SimpleMetaFactory{BaseFields: []string{"TypeMeta"}, VersionField: "APIVersion", KindField: "Kind"} s.raw.MetaFactory = conversion.SimpleMetaFactory{BaseFields: []string{"TypeMeta"}, VersionField: "APIVersion", KindField: "Kind"}
s.raw.AddConversionFuncs( if err := s.raw.AddConversionFuncs(
s.embeddedObjectToRawExtension, s.embeddedObjectToRawExtension,
s.rawExtensionToEmbeddedObject, s.rawExtensionToEmbeddedObject,
) s.runtimeObjectToRawExtensionArray,
s.rawExtensionToRuntimeObjectArray,
); err != nil {
panic(err)
}
return s 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. // Copy does a deep copy of an API object. Useful mostly for tests.
// TODO(dbsmith): implement directly instead of via Encode/Decode // 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) { func (s *Scheme) Copy(obj Object) (Object, error) {
data, err := s.EncodeToVersion(obj, "") data, err := s.EncodeToVersion(obj, "")
if err != nil { if err != nil {

View File

@ -16,9 +16,7 @@ limitations under the License.
package runtime package runtime
import ( import ()
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
// Note that the types provided in this file are not versioned and are intended to be // 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. // safe to use from within all versions of every API object.
@ -35,15 +33,8 @@ import (
// your own with the same fields. // your own with the same fields.
// //
type TypeMeta struct { type TypeMeta struct {
APIVersion string `json:"apiVersion,omitempty"` APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"` Kind string `json:"kind,omitempty" yaml:"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"`
} }
// PluginBase is like TypeMeta, but it's intended for plugin objects that won't ever be encoded // PluginBase is like TypeMeta, but it's intended for plugin objects that won't ever be encoded