Merge pull request #7490 from smarterclayton/alter_list

Do not automatically decode runtime.RawExtension
This commit is contained in:
Clayton Coleman 2015-05-01 16:48:26 -04:00
commit 1a8845af61
20 changed files with 391 additions and 61 deletions

View File

@ -115,12 +115,14 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
}, },
func(j *api.List, c fuzz.Continue) { func(j *api.List, c fuzz.Continue) {
c.FuzzNoCustom(j) // fuzz self without calling this function again c.FuzzNoCustom(j) // fuzz self without calling this function again
if j.Items == nil { // TODO: uncomment when round trip starts from a versioned object
if false { //j.Items == nil {
j.Items = []runtime.Object{} j.Items = []runtime.Object{}
} }
}, },
func(j *runtime.Object, c fuzz.Continue) { func(j *runtime.Object, c fuzz.Continue) {
if c.RandBool() { // TODO: uncomment when round trip starts from a versioned object
if true { //c.RandBool() {
*j = &runtime.Unknown{ *j = &runtime.Unknown{
TypeMeta: runtime.TypeMeta{Kind: "Something", APIVersion: "unknown"}, TypeMeta: runtime.TypeMeta{Kind: "Something", APIVersion: "unknown"},
RawJSON: []byte(`{"apiVersion":"unknown","kind":"Something","someKey":"someValue"}`), RawJSON: []byte(`{"apiVersion":"unknown","kind":"Something","someKey":"someValue"}`),

View File

@ -81,7 +81,7 @@ func ObjectReaction(o ObjectRetriever, mapper meta.RESTMapper) ReactionFunc {
// AddObjectsFromPath loads the JSON or YAML file containing Kubernetes API resources // AddObjectsFromPath loads the JSON or YAML file containing Kubernetes API resources
// and adds them to the provided ObjectRetriever. // and adds them to the provided ObjectRetriever.
func AddObjectsFromPath(path string, o ObjectRetriever) error { func AddObjectsFromPath(path string, o ObjectRetriever, decoder runtime.Decoder) error {
data, err := ioutil.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return err return err
@ -90,7 +90,7 @@ func AddObjectsFromPath(path string, o ObjectRetriever) error {
if err != nil { if err != nil {
return err return err
} }
obj, err := api.Codec.Decode(data) obj, err := decoder.Decode(data)
if err != nil { if err != nil {
return err return err
} }
@ -103,17 +103,12 @@ func AddObjectsFromPath(path string, o ObjectRetriever) error {
type objects struct { type objects struct {
types map[string][]runtime.Object types map[string][]runtime.Object
last map[string]int last map[string]int
typer runtime.ObjectTyper scheme runtime.ObjectScheme
creater runtime.ObjectCreater decoder runtime.ObjectDecoder
copier copier
} }
var _ ObjectRetriever = &objects{} var _ ObjectRetriever = &objects{}
type copier interface {
Copy(obj runtime.Object) (runtime.Object, error)
}
// NewObjects implements the ObjectRetriever interface by introspecting the // NewObjects implements the ObjectRetriever interface by introspecting the
// objects provided to Add() and returning them when the Kind method is invoked. // objects provided to Add() and returning them when the Kind method is invoked.
// If an api.List object is provided to Add(), each child item is added. If an // If an api.List object is provided to Add(), each child item is added. If an
@ -124,18 +119,17 @@ type copier interface {
// as a runtime.Object if Status == Success). If multiple PodLists are provided, they // as a runtime.Object if Status == Success). If multiple PodLists are provided, they
// will be returned in order by the Kind call, and the last PodList will be reused for // will be returned in order by the Kind call, and the last PodList will be reused for
// subsequent calls. // subsequent calls.
func NewObjects(scheme *runtime.Scheme) ObjectRetriever { func NewObjects(scheme runtime.ObjectScheme, decoder runtime.ObjectDecoder) ObjectRetriever {
return objects{ return objects{
types: make(map[string][]runtime.Object), types: make(map[string][]runtime.Object),
last: make(map[string]int), last: make(map[string]int),
typer: scheme, scheme: scheme,
creater: scheme, decoder: decoder,
copier: scheme,
} }
} }
func (o objects) Kind(kind, name string) (runtime.Object, error) { func (o objects) Kind(kind, name string) (runtime.Object, error) {
empty, _ := o.creater.New("", kind) empty, _ := o.scheme.New("", kind)
nilValue := reflect.Zero(reflect.TypeOf(empty)).Interface().(runtime.Object) nilValue := reflect.Zero(reflect.TypeOf(empty)).Interface().(runtime.Object)
arr, ok := o.types[kind] arr, ok := o.types[kind]
@ -146,14 +140,14 @@ func (o objects) Kind(kind, name string) (runtime.Object, error) {
if !ok { if !ok {
return empty, nil return empty, nil
} }
out, err := o.creater.New("", kind) out, err := o.scheme.New("", kind)
if err != nil { if err != nil {
return nilValue, err return nilValue, err
} }
if err := runtime.SetList(out, arr); err != nil { if err := runtime.SetList(out, arr); err != nil {
return nilValue, err return nilValue, err
} }
if out, err = o.copier.Copy(out); err != nil { if out, err = o.scheme.Copy(out); err != nil {
return nilValue, err return nilValue, err
} }
return out, nil return out, nil
@ -168,7 +162,7 @@ func (o objects) Kind(kind, name string) (runtime.Object, error) {
if index < 0 { if index < 0 {
return nilValue, errors.NewNotFound(kind, name) return nilValue, errors.NewNotFound(kind, name)
} }
out, err := o.copier.Copy(arr[index]) out, err := o.scheme.Copy(arr[index])
if err != nil { if err != nil {
return nilValue, err return nilValue, err
} }
@ -187,7 +181,7 @@ func (o objects) Kind(kind, name string) (runtime.Object, error) {
} }
func (o objects) Add(obj runtime.Object) error { func (o objects) Add(obj runtime.Object) error {
_, kind, err := o.typer.ObjectVersionAndKind(obj) _, kind, err := o.scheme.ObjectVersionAndKind(obj)
if err != nil { if err != nil {
return err return err
} }
@ -202,6 +196,9 @@ func (o objects) Add(obj runtime.Object) error {
if err != nil { if err != nil {
return err return err
} }
if errs := runtime.DecodeList(list, o.decoder); len(errs) > 0 {
return errs[0]
}
for _, obj := range list { for _, obj := range list {
if err := o.Add(obj); err != nil { if err := o.Add(obj); err != nil {
return err return err

View File

@ -27,7 +27,7 @@ import (
// NewSimpleFake returns a client that will respond with the provided objects // NewSimpleFake returns a client that will respond with the provided objects
func NewSimpleFake(objects ...runtime.Object) *Fake { func NewSimpleFake(objects ...runtime.Object) *Fake {
o := NewObjects(api.Scheme) o := NewObjects(api.Scheme, api.Scheme)
for _, obj := range objects { for _, obj := range objects {
if err := o.Add(obj); err != nil { if err := o.Add(obj); err != nil {
panic(err) panic(err)

View File

@ -27,8 +27,8 @@ import (
) )
func TestNewClient(t *testing.T) { func TestNewClient(t *testing.T) {
o := NewObjects(api.Scheme) o := NewObjects(api.Scheme, api.Scheme)
if err := AddObjectsFromPath("../../../examples/guestbook/frontend-service.json", o); err != nil { if err := AddObjectsFromPath("../../../examples/guestbook/frontend-service.json", o, api.Scheme); err != nil {
t.Fatal(err) t.Fatal(err)
} }
client := &Fake{ReactFn: ObjectReaction(o, latest.RESTMapper)} client := &Fake{ReactFn: ObjectReaction(o, latest.RESTMapper)}
@ -52,7 +52,7 @@ func TestNewClient(t *testing.T) {
} }
func TestErrors(t *testing.T) { func TestErrors(t *testing.T) {
o := NewObjects(api.Scheme) o := NewObjects(api.Scheme, api.Scheme)
o.Add(&api.List{ o.Add(&api.List{
Items: []runtime.Object{ Items: []runtime.Object{
// This first call to List will return this error // This first call to List will return this error

View File

@ -54,6 +54,10 @@ type missingKindErr struct {
data string data string
} }
func NewMissingKindErr(data string) error {
return &missingKindErr{data}
}
func (k *missingKindErr) Error() string { func (k *missingKindErr) Error() string {
return fmt.Sprintf("Object 'Kind' is missing in '%s'", k.data) return fmt.Sprintf("Object 'Kind' is missing in '%s'", k.data)
} }
@ -70,6 +74,10 @@ type missingVersionErr struct {
data string data string
} }
func NewMissingVersionErr(data string) error {
return &missingVersionErr{data}
}
func (k *missingVersionErr) Error() string { func (k *missingVersionErr) Error() string {
return fmt.Sprintf("Object 'apiVersion' is missing in '%s'", k.data) return fmt.Sprintf("Object 'apiVersion' is missing in '%s'", k.data)
} }

View File

@ -238,6 +238,17 @@ func (s *Scheme) AddDefaultingFuncs(defaultingFuncs ...interface{}) error {
return nil return nil
} }
// Recognizes returns true if the scheme is able to handle the provided version and kind
// of an object.
func (s *Scheme) Recognizes(version, kind string) bool {
m, ok := s.versionMap[version]
if !ok {
return false
}
_, ok = m[kind]
return ok
}
// RegisterInputDefaults sets the provided field mapping function and field matching // RegisterInputDefaults sets the provided field mapping function and field matching
// as the defaults for the provided input type. The fn may be nil, in which case no // as the defaults for the provided input type. The fn may be nil, in which case no
// mapping will happen by default. Use this method to register a mechanism for handling // mapping will happen by default. Use this method to register a mechanism for handling

View File

@ -393,6 +393,17 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
list, err := runtime.ExtractList(out)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if errs := runtime.DecodeList(list, api.Scheme); len(errs) > 0 {
t.Fatalf("unexpected error: %v", errs)
}
if err := runtime.SetList(out, list); err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := &api.List{ expected := &api.List{
Items: []runtime.Object{ Items: []runtime.Object{
&pods.Items[0], &pods.Items[0],

View File

@ -347,6 +347,12 @@ func (v FlattenListVisitor) Visit(fn VisitorFunc) error {
if err != nil { if err != nil {
return fn(info) return fn(info)
} }
if errs := runtime.DecodeList(items, struct {
runtime.ObjectTyper
runtime.Decoder
}{v.Mapper, info.Mapping.Codec}); len(errs) > 0 {
return errors.NewAggregate(errs)
}
for i := range items { for i := range items {
item, err := v.InfoForObject(items[i]) item, err := v.InfoForObject(items[i])
if err != nil { if err != nil {

View File

@ -135,7 +135,7 @@ func TestSyncNamespaceThatIsActive(t *testing.T) {
} }
func TestRunStop(t *testing.T) { func TestRunStop(t *testing.T) {
o := testclient.NewObjects(api.Scheme) o := testclient.NewObjects(api.Scheme, api.Scheme)
client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)} client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)}
nsMgr := NewNamespaceManager(client, 1*time.Second) nsMgr := NewNamespaceManager(client, 1*time.Second)

View File

@ -21,8 +21,8 @@ import (
) )
// CodecFor returns a Codec that invokes Encode with the provided version. // CodecFor returns a Codec that invokes Encode with the provided version.
func CodecFor(scheme *Scheme, version string) Codec { func CodecFor(codec ObjectCodec, version string) Codec {
return &codecWrapper{scheme, version} return &codecWrapper{codec, version}
} }
// yamlCodec converts YAML passed to the Decoder methods to JSON. // yamlCodec converts YAML passed to the Decoder methods to JSON.
@ -69,11 +69,11 @@ func EncodeOrDie(codec Codec, obj Object) string {
// codecWrapper implements encoding to an alternative // codecWrapper implements encoding to an alternative
// default version for a scheme. // default version for a scheme.
type codecWrapper struct { type codecWrapper struct {
*Scheme ObjectCodec
version string version string
} }
// Encode implements Codec // Encode implements Codec
func (c *codecWrapper) Encode(obj Object) ([]byte, error) { func (c *codecWrapper) Encode(obj Object) ([]byte, error) {
return c.Scheme.EncodeToVersion(obj, c.version) return c.EncodeToVersion(obj, c.version)
} }

View File

@ -63,9 +63,22 @@ func TestDecodeEmptyRawExtensionAsObject(t *testing.T) {
s.AddKnownTypes("", &ObjectTest{}) s.AddKnownTypes("", &ObjectTest{})
s.AddKnownTypeWithName("v1test", "ObjectTest", &ObjectTestExternal{}) s.AddKnownTypeWithName("v1test", "ObjectTest", &ObjectTestExternal{})
_, err := s.Decode([]byte(`{"kind":"ObjectTest","apiVersion":"v1test","items":[{}]}`)) obj, err := s.Decode([]byte(`{"kind":"ObjectTest","apiVersion":"v1test","items":[{}]}`))
if err == nil { if err != nil {
t.Fatalf("unexpected non-error") t.Fatalf("unexpected error: %v", err)
}
test := obj.(*ObjectTest)
if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.RawJSON) != "{}" {
t.Fatalf("unexpected object: %#v", test.Items[0])
}
obj, err = s.Decode([]byte(`{"kind":"ObjectTest","apiVersion":"v1test","items":[{"kind":"Other","apiVersion":"v1"}]}`))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
test = obj.(*ObjectTest)
if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "Other" || unk.APIVersion != "v1" || string(unk.RawJSON) != `{"kind":"Other","apiVersion":"v1"}` {
t.Fatalf("unexpected object: %#v", test.Items[0])
} }
} }
@ -99,17 +112,34 @@ func TestArrayOfRuntimeObject(t *testing.T) {
if err := json.Unmarshal(wire, obj); err != nil { if err := json.Unmarshal(wire, obj); err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
t.Logf("exact wire is: %#v", string(obj.Items[0].RawJSON)) t.Logf("exact wire is: %s", string(obj.Items[0].RawJSON))
decoded, err := s.Decode(wire) decoded, err := s.Decode(wire)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
list, err := runtime.ExtractList(decoded)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if errs := runtime.DecodeList(list, s); len(errs) > 0 {
t.Fatalf("unexpected error: %v", errs)
}
list2, err := runtime.ExtractList(list[3])
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if errs := runtime.DecodeList(list2, s); len(errs) > 0 {
t.Fatalf("unexpected error: %v", errs)
}
if err := runtime.SetList(list[3], list2); err != nil {
t.Fatalf("unexpected error: %v", err)
}
internal.Items[2].(*runtime.Unknown).Kind = "OtherTest" internal.Items[2].(*runtime.Unknown).Kind = "OtherTest"
internal.Items[2].(*runtime.Unknown).APIVersion = "unknown" internal.Items[2].(*runtime.Unknown).APIVersion = "unknown"
if e, a := internal, decoded; !reflect.DeepEqual(e, a) { if e, a := internal.Items, list; !reflect.DeepEqual(e, a) {
t.Log(string(decoded.(*ObjectTest).Items[2].(*runtime.Unknown).RawJSON))
t.Errorf("mismatched decoded: %s", util.ObjectDiff(e, a)) t.Errorf("mismatched decoded: %s", util.ObjectDiff(e, a))
} }
} }

View File

@ -23,7 +23,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
) )
// TODO: move me to pkg/api/meta // IsListType returns true if the provided Object has a slice called Items
func IsListType(obj Object) bool { func IsListType(obj Object) bool {
_, err := GetItemsPtr(obj) _, err := GetItemsPtr(obj)
return err == nil return err == nil
@ -33,7 +33,6 @@ func IsListType(obj Object) bool {
// If 'list' doesn't have an Items member, it's not really a list type // If 'list' doesn't have an Items member, it's not really a list type
// and an error will be returned. // and an error will be returned.
// This function will either return a pointer to a slice, or an error, but not both. // This function will either return a pointer to a slice, or an error, but not both.
// TODO: move me to pkg/api/meta
func GetItemsPtr(list Object) (interface{}, error) { func GetItemsPtr(list Object) (interface{}, error) {
v, err := conversion.EnforcePtr(list) v, err := conversion.EnforcePtr(list)
if err != nil { if err != nil {
@ -150,9 +149,36 @@ func FieldPtr(v reflect.Value, fieldName string, dest interface{}) error {
return fmt.Errorf("couldn't assign/convert %v to %v", field.Type(), v.Type()) return fmt.Errorf("couldn't assign/convert %v to %v", field.Type(), v.Type())
} }
// DecodeList alters the list in place, attempting to decode any objects found in
// the list that have the runtime.Unknown type. Any errors that occur are returned
// after the entire list is processed. Decoders are tried in order.
func DecodeList(objects []Object, decoders ...ObjectDecoder) []error {
errs := []error(nil)
for i, obj := range objects {
switch t := obj.(type) {
case *Unknown:
for _, decoder := range decoders {
if !decoder.Recognizes(t.APIVersion, t.Kind) {
continue
}
obj, err := decoder.Decode(t.RawJSON)
if err != nil {
errs = append(errs, err)
break
}
objects[i] = obj
break
}
}
}
return errs
}
// MultiObjectTyper returns the types of objects across multiple schemes in order. // MultiObjectTyper returns the types of objects across multiple schemes in order.
type MultiObjectTyper []ObjectTyper type MultiObjectTyper []ObjectTyper
var _ ObjectTyper = MultiObjectTyper{}
func (m MultiObjectTyper) DataVersionAndKind(data []byte) (version, kind string, err error) { func (m MultiObjectTyper) DataVersionAndKind(data []byte) (version, kind string, err error) {
for _, t := range m { for _, t := range m {
version, kind, err = t.DataVersionAndKind(data) version, kind, err = t.DataVersionAndKind(data)
@ -162,6 +188,7 @@ func (m MultiObjectTyper) DataVersionAndKind(data []byte) (version, kind string,
} }
return return
} }
func (m MultiObjectTyper) ObjectVersionAndKind(obj Object) (version, kind string, err error) { func (m MultiObjectTyper) ObjectVersionAndKind(obj Object) (version, kind string, err error) {
for _, t := range m { for _, t := range m {
version, kind, err = t.ObjectVersionAndKind(obj) version, kind, err = t.ObjectVersionAndKind(obj)
@ -171,3 +198,12 @@ func (m MultiObjectTyper) ObjectVersionAndKind(obj Object) (version, kind string
} }
return return
} }
func (m MultiObjectTyper) Recognizes(version, kind string) bool {
for _, t := range m {
if t.Recognizes(version, kind) {
return true
}
}
return false
}

View File

@ -21,6 +21,7 @@ import (
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
@ -132,6 +133,22 @@ func TestExtractListOfValuePtrs(t *testing.T) {
} }
} }
func TestDecodeList(t *testing.T) {
pl := &api.List{
Items: []runtime.Object{
&api.Pod{ObjectMeta: api.ObjectMeta{Name: "1"}},
&runtime.Unknown{TypeMeta: runtime.TypeMeta{Kind: "Pod", APIVersion: "v1beta3"}, RawJSON: []byte(`{"kind":"Pod","apiVersion":"v1beta3","metadata":{"name":"test"}}`)},
&runtime.Unstructured{TypeMeta: runtime.TypeMeta{Kind: "Foo", APIVersion: "Bar"}, Object: map[string]interface{}{"test": "value"}},
},
}
if errs := runtime.DecodeList(pl.Items, api.Scheme); len(errs) != 0 {
t.Fatalf("unexpected error %v", errs)
}
if pod, ok := pl.Items[1].(*api.Pod); !ok || pod.Name != "test" {
t.Errorf("object not converted: %#v", pl.Items[1])
}
}
func TestSetList(t *testing.T) { func TestSetList(t *testing.T) {
pl := &api.PodList{} pl := &api.PodList{}
list := []runtime.Object{ list := []runtime.Object{

View File

@ -16,6 +16,24 @@ limitations under the License.
package runtime package runtime
// ObjectScheme represents common conversions between formal external API versions
// and the internal Go structs. ObjectScheme is typically used with ObjectCodec to
// transform internal Go structs into serialized versions. There may be many valid
// ObjectCodecs for each ObjectScheme.
type ObjectScheme interface {
ObjectConvertor
ObjectTyper
ObjectCreater
ObjectCopier
}
// ObjectCodec represents the common mechanisms for converting to and from a particular
// binary representation of an object.
type ObjectCodec interface {
ObjectEncoder
Decoder
}
// Decoder defines methods for deserializing API objects into a given type // Decoder defines methods for deserializing API objects into a given type
type Decoder interface { type Decoder interface {
Decode(data []byte) (Object, error) Decode(data []byte) (Object, error)
@ -33,6 +51,22 @@ type Codec interface {
Encoder Encoder
} }
// ObjectCopier duplicates an object.
type ObjectCopier interface {
// Copy returns an exact copy of the provided Object, or an error if the
// copy could not be completed.
Copy(Object) (Object, error)
}
// ObjectEncoder turns an object into a byte array. This interface is a
// general form of the Encoder interface
type ObjectEncoder interface {
// EncodeToVersion convert and serializes an object in the internal format
// to a specified output version. An error is returned if the object
// cannot be converted for any reason.
EncodeToVersion(obj Object, outVersion string) ([]byte, error)
}
// ObjectConvertor converts an object to a different version. // ObjectConvertor converts an object to a different version.
type ObjectConvertor interface { type ObjectConvertor interface {
Convert(in, out interface{}) error Convert(in, out interface{}) error
@ -43,8 +77,17 @@ type ObjectConvertor interface {
// ObjectTyper contains methods for extracting the APIVersion and Kind // ObjectTyper contains methods for extracting the APIVersion and Kind
// of objects. // of objects.
type ObjectTyper interface { type ObjectTyper interface {
// DataVersionAndKind returns the version and kind of the provided data, or an error
// if another problem is detected. In many cases this method can be as expensive to
// invoke as the Decode method.
DataVersionAndKind([]byte) (version, kind string, err error) DataVersionAndKind([]byte) (version, kind string, err error)
// ObjectVersionAndKind returns the version and kind of the provided object, or an
// error if the object is not recognized (IsNotRegisteredError will return true).
ObjectVersionAndKind(Object) (version, kind string, err error) ObjectVersionAndKind(Object) (version, kind string, err error)
// Recognizes returns true if the scheme is able to handle the provided version and kind,
// or more precisely that the provided version is a possible conversion or decoding
// target.
Recognizes(version, kind string) bool
} }
// ObjectCreater contains methods for instantiating an object by kind and version. // ObjectCreater contains methods for instantiating an object by kind and version.
@ -52,6 +95,20 @@ type ObjectCreater interface {
New(version, kind string) (out Object, err error) New(version, kind string) (out Object, err error)
} }
// ObjectDecoder is a convenience interface for identifying serialized versions of objects
// and transforming them into Objects. It intentionally overlaps with ObjectTyper and
// Decoder for use in decode only paths.
type ObjectDecoder interface {
Decoder
// DataVersionAndKind returns the version and kind of the provided data, or an error
// if another problem is detected. In many cases this method can be as expensive to
// invoke as the Decode method.
DataVersionAndKind([]byte) (version, kind string, err error)
// Recognizes returns true if the scheme is able to handle the provided version and kind
// of an object.
Recognizes(version, kind string) bool
}
// ResourceVersioner provides methods for setting and retrieving // ResourceVersioner provides methods for setting and retrieving
// the resource version from an API object. // the resource version from an API object.
type ResourceVersioner interface { type ResourceVersioner interface {

View File

@ -17,6 +17,7 @@ limitations under the License.
package runtime package runtime
import ( import (
"encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"reflect" "reflect"
@ -164,7 +165,15 @@ func (self *Scheme) runtimeObjectToRawExtensionArray(in *[]Object, out *[]RawExt
for i := range src { for i := range src {
switch t := src[i].(type) { switch t := src[i].(type) {
case *Unknown: case *Unknown:
// TODO: this should be decoupled from the scheme (since it is JSON specific)
dest[i].RawJSON = t.RawJSON dest[i].RawJSON = t.RawJSON
case *Unstructured:
// TODO: this should be decoupled from the scheme (since it is JSON specific)
data, err := json.Marshal(t.Object)
if err != nil {
return err
}
dest[i].RawJSON = data
default: default:
version := outVersion version := outVersion
// if the object exists // if the object exists
@ -192,24 +201,17 @@ func (self *Scheme) rawExtensionToRuntimeObjectArray(in *[]RawExtension, out *[]
for i := range src { for i := range src {
data := src[i].RawJSON data := src[i].RawJSON
obj, err := scheme.Decode(data) version, kind, err := scheme.raw.DataVersionAndKind(data)
if err != nil { if err != nil {
if !IsNotRegisteredError(err) { return err
return err }
} dest[i] = &Unknown{
version, kind, err := scheme.raw.DataVersionAndKind(data) TypeMeta: TypeMeta{
if err != nil { APIVersion: version,
return err Kind: kind,
} },
obj = &Unknown{ RawJSON: data,
TypeMeta: TypeMeta{
APIVersion: version,
Kind: kind,
},
RawJSON: data,
}
} }
dest[i] = obj
} }
*out = dest *out = dest
return nil return nil
@ -275,6 +277,12 @@ func (s *Scheme) ObjectVersionAndKind(obj Object) (version, kind string, err err
return s.raw.ObjectVersionAndKind(obj) return s.raw.ObjectVersionAndKind(obj)
} }
// Recognizes returns true if the scheme is able to handle the provided version and kind
// of an object.
func (s *Scheme) Recognizes(version, kind string) bool {
return s.raw.Recognizes(version, kind)
}
// New returns a new API object of the given version ("" for internal // New returns a new API object of the given version ("" for internal
// representation) and name, or an error if it hasn't been registered. // representation) and name, or an error if it hasn't been registered.
func (s *Scheme) New(versionName, typeName string) (Object, error) { func (s *Scheme) New(versionName, typeName string) (Object, error) {

View File

@ -116,3 +116,17 @@ type Unknown struct {
} }
func (*Unknown) IsAnAPIObject() {} func (*Unknown) IsAnAPIObject() {}
// Unstructured allows objects that do not have Golang structs registered to be manipulated
// generically. This can be used to deal with the API objects from a plug-in. Unstructured
// objects still have functioning TypeMeta features-- kind, version, etc.
// TODO: Make this object have easy access to field based accessors and settors for
// metadata and field mutatation.
type Unstructured struct {
TypeMeta `json:",inline"`
// Object is a JSON compatible map with string, float, int, []interface{}, or map[string]interface{}
// children.
Object map[string]interface{}
}
func (*Unstructured) IsAnAPIObject() {}

View File

@ -0,0 +1,89 @@
/*
Copyright 2015 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 (
"encoding/json"
"fmt"
"reflect"
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
)
// UnstructuredJSONScheme is capable of converting JSON data into the Unstructured
// type, which can be used for generic access to objects without a predefined scheme.
var UnstructuredJSONScheme ObjectDecoder = unstructuredJSONScheme{}
type unstructuredJSONScheme struct{}
// Recognizes returns true for any version or kind that is specified (internal
// versions are specifically excluded).
func (unstructuredJSONScheme) Recognizes(version, kind string) bool {
return len(version) > 0 && len(kind) > 0
}
func (s unstructuredJSONScheme) Decode(data []byte) (Object, error) {
unstruct := &Unstructured{}
if err := s.DecodeInto(data, unstruct); err != nil {
return nil, err
}
return unstruct, nil
}
func (unstructuredJSONScheme) DecodeInto(data []byte, obj Object) error {
unstruct, ok := obj.(*Unstructured)
if !ok {
return fmt.Errorf("the unstructured JSON scheme does not recognize %v", reflect.TypeOf(obj))
}
m := make(map[string]interface{})
if err := json.Unmarshal(data, &m); err != nil {
return err
}
if v, ok := m["kind"]; ok {
if s, ok := v.(string); ok {
unstruct.Kind = s
}
}
if v, ok := m["apiVersion"]; ok {
if s, ok := v.(string); ok {
unstruct.APIVersion = s
}
}
if len(unstruct.APIVersion) == 0 {
return conversion.NewMissingVersionErr(string(data))
}
if len(unstruct.Kind) == 0 {
return conversion.NewMissingKindErr(string(data))
}
unstruct.Object = m
return nil
}
func (unstructuredJSONScheme) DataVersionAndKind(data []byte) (version, kind string, err error) {
obj := TypeMeta{}
if err := json.Unmarshal(data, &obj); err != nil {
return "", "", err
}
if len(obj.APIVersion) == 0 {
return "", "", conversion.NewMissingVersionErr(string(data))
}
if len(obj.Kind) == 0 {
return "", "", conversion.NewMissingKindErr(string(data))
}
return obj.APIVersion, obj.Kind, nil
}

View File

@ -0,0 +1,44 @@
/*
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 (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
func TestDecodeUnstructured(t *testing.T) {
pl := &api.List{
Items: []runtime.Object{
&api.Pod{ObjectMeta: api.ObjectMeta{Name: "1"}},
&runtime.Unknown{TypeMeta: runtime.TypeMeta{Kind: "Pod", APIVersion: "v1beta3"}, RawJSON: []byte(`{"kind":"Pod","apiVersion":"v1beta3","metadata":{"name":"test"}}`)},
&runtime.Unknown{TypeMeta: runtime.TypeMeta{Kind: "", APIVersion: "v1beta3"}, RawJSON: []byte(`{"kind":"Pod","apiVersion":"v1beta3","metadata":{"name":"test"}}`)},
&runtime.Unstructured{TypeMeta: runtime.TypeMeta{Kind: "Foo", APIVersion: "Bar"}, Object: map[string]interface{}{"test": "value"}},
},
}
if errs := runtime.DecodeList(pl.Items, runtime.UnstructuredJSONScheme); len(errs) == 1 {
t.Fatalf("unexpected error %v", errs)
}
if pod, ok := pl.Items[1].(*runtime.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" {
t.Errorf("object not converted: %#v", pl.Items[1])
}
if _, ok := pl.Items[2].(*runtime.Unknown); !ok {
t.Errorf("object should not have been converted: %#v", pl.Items[2])
}
}

View File

@ -153,7 +153,7 @@ func TestNewBuilder(t *testing.T) {
} }
for _, item := range tests { for _, item := range tests {
o := testclient.NewObjects(api.Scheme) o := testclient.NewObjects(api.Scheme, api.Scheme)
o.Add(item.pv) o.Add(item.pv)
o.Add(item.claim) o.Add(item.claim)
client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)} client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)}
@ -213,7 +213,7 @@ func TestNewBuilderClaimNotBound(t *testing.T) {
ClaimName: "claimC", ClaimName: "claimC",
}, },
} }
o := testclient.NewObjects(api.Scheme) o := testclient.NewObjects(api.Scheme, api.Scheme)
o.Add(pv) o.Add(pv)
o.Add(claim) o.Add(claim)
client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)} client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)}

View File

@ -29,7 +29,7 @@ import (
) )
func TestRunStop(t *testing.T) { func TestRunStop(t *testing.T) {
o := testclient.NewObjects(api.Scheme) o := testclient.NewObjects(api.Scheme, api.Scheme)
client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)} client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)}
binder := NewPersistentVolumeClaimBinder(client, 1*time.Second) binder := NewPersistentVolumeClaimBinder(client, 1*time.Second)
@ -111,8 +111,8 @@ func TestExampleObjects(t *testing.T) {
} }
for name, scenario := range scenarios { for name, scenario := range scenarios {
o := testclient.NewObjects(api.Scheme) o := testclient.NewObjects(api.Scheme, api.Scheme)
if err := testclient.AddObjectsFromPath("../../examples/persistent-volumes/"+name, o); err != nil { if err := testclient.AddObjectsFromPath("../../examples/persistent-volumes/"+name, o, api.Scheme); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -168,11 +168,11 @@ func TestExampleObjects(t *testing.T) {
func TestBindingWithExamples(t *testing.T) { func TestBindingWithExamples(t *testing.T) {
api.ForTesting_ReferencesAllowBlankSelfLinks = true api.ForTesting_ReferencesAllowBlankSelfLinks = true
o := testclient.NewObjects(api.Scheme) o := testclient.NewObjects(api.Scheme, api.Scheme)
if err := testclient.AddObjectsFromPath("../../examples/persistent-volumes/claims/claim-01.yaml", o); err != nil { if err := testclient.AddObjectsFromPath("../../examples/persistent-volumes/claims/claim-01.yaml", o, api.Scheme); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := testclient.AddObjectsFromPath("../../examples/persistent-volumes/volumes/local-01.yaml", o); err != nil { if err := testclient.AddObjectsFromPath("../../examples/persistent-volumes/volumes/local-01.yaml", o, api.Scheme); err != nil {
t.Fatal(err) t.Fatal(err)
} }