mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 15:05:27 +00:00
Merge pull request #7490 from smarterclayton/alter_list
Do not automatically decode runtime.RawExtension
This commit is contained in:
commit
1a8845af61
@ -115,12 +115,14 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
|
||||
},
|
||||
func(j *api.List, c fuzz.Continue) {
|
||||
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{}
|
||||
}
|
||||
},
|
||||
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{
|
||||
TypeMeta: runtime.TypeMeta{Kind: "Something", APIVersion: "unknown"},
|
||||
RawJSON: []byte(`{"apiVersion":"unknown","kind":"Something","someKey":"someValue"}`),
|
||||
|
@ -81,7 +81,7 @@ func ObjectReaction(o ObjectRetriever, mapper meta.RESTMapper) ReactionFunc {
|
||||
|
||||
// AddObjectsFromPath loads the JSON or YAML file containing Kubernetes API resources
|
||||
// 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)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -90,7 +90,7 @@ func AddObjectsFromPath(path string, o ObjectRetriever) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj, err := api.Codec.Decode(data)
|
||||
obj, err := decoder.Decode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -103,17 +103,12 @@ func AddObjectsFromPath(path string, o ObjectRetriever) error {
|
||||
type objects struct {
|
||||
types map[string][]runtime.Object
|
||||
last map[string]int
|
||||
typer runtime.ObjectTyper
|
||||
creater runtime.ObjectCreater
|
||||
copier copier
|
||||
scheme runtime.ObjectScheme
|
||||
decoder runtime.ObjectDecoder
|
||||
}
|
||||
|
||||
var _ ObjectRetriever = &objects{}
|
||||
|
||||
type copier interface {
|
||||
Copy(obj runtime.Object) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// NewObjects implements the ObjectRetriever interface by introspecting the
|
||||
// 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
|
||||
@ -124,18 +119,17 @@ type copier interface {
|
||||
// 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
|
||||
// subsequent calls.
|
||||
func NewObjects(scheme *runtime.Scheme) ObjectRetriever {
|
||||
func NewObjects(scheme runtime.ObjectScheme, decoder runtime.ObjectDecoder) ObjectRetriever {
|
||||
return objects{
|
||||
types: make(map[string][]runtime.Object),
|
||||
last: make(map[string]int),
|
||||
typer: scheme,
|
||||
creater: scheme,
|
||||
copier: scheme,
|
||||
scheme: scheme,
|
||||
decoder: decoder,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
arr, ok := o.types[kind]
|
||||
@ -146,14 +140,14 @@ func (o objects) Kind(kind, name string) (runtime.Object, error) {
|
||||
if !ok {
|
||||
return empty, nil
|
||||
}
|
||||
out, err := o.creater.New("", kind)
|
||||
out, err := o.scheme.New("", kind)
|
||||
if err != nil {
|
||||
return nilValue, err
|
||||
}
|
||||
if err := runtime.SetList(out, arr); err != nil {
|
||||
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 out, nil
|
||||
@ -168,7 +162,7 @@ func (o objects) Kind(kind, name string) (runtime.Object, error) {
|
||||
if index < 0 {
|
||||
return nilValue, errors.NewNotFound(kind, name)
|
||||
}
|
||||
out, err := o.copier.Copy(arr[index])
|
||||
out, err := o.scheme.Copy(arr[index])
|
||||
if err != nil {
|
||||
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 {
|
||||
_, kind, err := o.typer.ObjectVersionAndKind(obj)
|
||||
_, kind, err := o.scheme.ObjectVersionAndKind(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -202,6 +196,9 @@ func (o objects) Add(obj runtime.Object) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if errs := runtime.DecodeList(list, o.decoder); len(errs) > 0 {
|
||||
return errs[0]
|
||||
}
|
||||
for _, obj := range list {
|
||||
if err := o.Add(obj); err != nil {
|
||||
return err
|
||||
|
@ -27,7 +27,7 @@ import (
|
||||
|
||||
// NewSimpleFake returns a client that will respond with the provided objects
|
||||
func NewSimpleFake(objects ...runtime.Object) *Fake {
|
||||
o := NewObjects(api.Scheme)
|
||||
o := NewObjects(api.Scheme, api.Scheme)
|
||||
for _, obj := range objects {
|
||||
if err := o.Add(obj); err != nil {
|
||||
panic(err)
|
||||
|
@ -27,8 +27,8 @@ import (
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
o := NewObjects(api.Scheme)
|
||||
if err := AddObjectsFromPath("../../../examples/guestbook/frontend-service.json", o); err != nil {
|
||||
o := NewObjects(api.Scheme, api.Scheme)
|
||||
if err := AddObjectsFromPath("../../../examples/guestbook/frontend-service.json", o, api.Scheme); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := &Fake{ReactFn: ObjectReaction(o, latest.RESTMapper)}
|
||||
@ -52,7 +52,7 @@ func TestNewClient(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
o := NewObjects(api.Scheme)
|
||||
o := NewObjects(api.Scheme, api.Scheme)
|
||||
o.Add(&api.List{
|
||||
Items: []runtime.Object{
|
||||
// This first call to List will return this error
|
||||
|
@ -54,6 +54,10 @@ type missingKindErr struct {
|
||||
data string
|
||||
}
|
||||
|
||||
func NewMissingKindErr(data string) error {
|
||||
return &missingKindErr{data}
|
||||
}
|
||||
|
||||
func (k *missingKindErr) Error() string {
|
||||
return fmt.Sprintf("Object 'Kind' is missing in '%s'", k.data)
|
||||
}
|
||||
@ -70,6 +74,10 @@ type missingVersionErr struct {
|
||||
data string
|
||||
}
|
||||
|
||||
func NewMissingVersionErr(data string) error {
|
||||
return &missingVersionErr{data}
|
||||
}
|
||||
|
||||
func (k *missingVersionErr) Error() string {
|
||||
return fmt.Sprintf("Object 'apiVersion' is missing in '%s'", k.data)
|
||||
}
|
||||
|
@ -238,6 +238,17 @@ func (s *Scheme) AddDefaultingFuncs(defaultingFuncs ...interface{}) error {
|
||||
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
|
||||
// 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
|
||||
|
@ -393,6 +393,17 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
||||
if err != nil {
|
||||
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{
|
||||
Items: []runtime.Object{
|
||||
&pods.Items[0],
|
||||
|
@ -347,6 +347,12 @@ func (v FlattenListVisitor) Visit(fn VisitorFunc) error {
|
||||
if err != nil {
|
||||
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 {
|
||||
item, err := v.InfoForObject(items[i])
|
||||
if err != nil {
|
||||
|
@ -135,7 +135,7 @@ func TestSyncNamespaceThatIsActive(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)}
|
||||
nsMgr := NewNamespaceManager(client, 1*time.Second)
|
||||
|
||||
|
@ -21,8 +21,8 @@ import (
|
||||
)
|
||||
|
||||
// CodecFor returns a Codec that invokes Encode with the provided version.
|
||||
func CodecFor(scheme *Scheme, version string) Codec {
|
||||
return &codecWrapper{scheme, version}
|
||||
func CodecFor(codec ObjectCodec, version string) Codec {
|
||||
return &codecWrapper{codec, version}
|
||||
}
|
||||
|
||||
// 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
|
||||
// default version for a scheme.
|
||||
type codecWrapper struct {
|
||||
*Scheme
|
||||
ObjectCodec
|
||||
version string
|
||||
}
|
||||
|
||||
// Encode implements Codec
|
||||
func (c *codecWrapper) Encode(obj Object) ([]byte, error) {
|
||||
return c.Scheme.EncodeToVersion(obj, c.version)
|
||||
return c.EncodeToVersion(obj, c.version)
|
||||
}
|
||||
|
@ -63,9 +63,22 @@ func TestDecodeEmptyRawExtensionAsObject(t *testing.T) {
|
||||
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")
|
||||
obj, err := s.Decode([]byte(`{"kind":"ObjectTest","apiVersion":"v1test","items":[{}]}`))
|
||||
if err != nil {
|
||||
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 {
|
||||
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)
|
||||
if err != nil {
|
||||
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).APIVersion = "unknown"
|
||||
if e, a := internal, decoded; !reflect.DeepEqual(e, a) {
|
||||
t.Log(string(decoded.(*ObjectTest).Items[2].(*runtime.Unknown).RawJSON))
|
||||
if e, a := internal.Items, list; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("mismatched decoded: %s", util.ObjectDiff(e, a))
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import (
|
||||
"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 {
|
||||
_, err := GetItemsPtr(obj)
|
||||
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
|
||||
// and an error will be returned.
|
||||
// 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) {
|
||||
v, err := conversion.EnforcePtr(list)
|
||||
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())
|
||||
}
|
||||
|
||||
// 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.
|
||||
type MultiObjectTyper []ObjectTyper
|
||||
|
||||
var _ ObjectTyper = MultiObjectTyper{}
|
||||
|
||||
func (m MultiObjectTyper) DataVersionAndKind(data []byte) (version, kind string, err error) {
|
||||
for _, t := range m {
|
||||
version, kind, err = t.DataVersionAndKind(data)
|
||||
@ -162,6 +188,7 @@ func (m MultiObjectTyper) DataVersionAndKind(data []byte) (version, kind string,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m MultiObjectTyper) ObjectVersionAndKind(obj Object) (version, kind string, err error) {
|
||||
for _, t := range m {
|
||||
version, kind, err = t.ObjectVersionAndKind(obj)
|
||||
@ -171,3 +198,12 @@ func (m MultiObjectTyper) ObjectVersionAndKind(obj Object) (version, kind string
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m MultiObjectTyper) Recognizes(version, kind string) bool {
|
||||
for _, t := range m {
|
||||
if t.Recognizes(version, kind) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"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) {
|
||||
pl := &api.PodList{}
|
||||
list := []runtime.Object{
|
||||
|
@ -16,6 +16,24 @@ limitations under the License.
|
||||
|
||||
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
|
||||
type Decoder interface {
|
||||
Decode(data []byte) (Object, error)
|
||||
@ -33,6 +51,22 @@ type Codec interface {
|
||||
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.
|
||||
type ObjectConvertor interface {
|
||||
Convert(in, out interface{}) error
|
||||
@ -43,8 +77,17 @@ type ObjectConvertor interface {
|
||||
// ObjectTyper contains methods for extracting the APIVersion and Kind
|
||||
// of objects.
|
||||
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)
|
||||
// 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)
|
||||
// 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.
|
||||
@ -52,6 +95,20 @@ type ObjectCreater interface {
|
||||
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
|
||||
// the resource version from an API object.
|
||||
type ResourceVersioner interface {
|
||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
@ -164,7 +165,15 @@ func (self *Scheme) runtimeObjectToRawExtensionArray(in *[]Object, out *[]RawExt
|
||||
for i := range src {
|
||||
switch t := src[i].(type) {
|
||||
case *Unknown:
|
||||
// TODO: this should be decoupled from the scheme (since it is JSON specific)
|
||||
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:
|
||||
version := outVersion
|
||||
// if the object exists
|
||||
@ -192,16 +201,11 @@ func (self *Scheme) rawExtensionToRuntimeObjectArray(in *[]RawExtension, out *[]
|
||||
|
||||
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{
|
||||
dest[i] = &Unknown{
|
||||
TypeMeta: TypeMeta{
|
||||
APIVersion: version,
|
||||
Kind: kind,
|
||||
@ -209,8 +213,6 @@ func (self *Scheme) rawExtensionToRuntimeObjectArray(in *[]RawExtension, out *[]
|
||||
RawJSON: data,
|
||||
}
|
||||
}
|
||||
dest[i] = obj
|
||||
}
|
||||
*out = dest
|
||||
return nil
|
||||
}
|
||||
@ -275,6 +277,12 @@ func (s *Scheme) ObjectVersionAndKind(obj Object) (version, kind string, err err
|
||||
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
|
||||
// representation) and name, or an error if it hasn't been registered.
|
||||
func (s *Scheme) New(versionName, typeName string) (Object, error) {
|
||||
|
@ -116,3 +116,17 @@ type Unknown struct {
|
||||
}
|
||||
|
||||
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() {}
|
||||
|
89
pkg/runtime/unstructured.go
Normal file
89
pkg/runtime/unstructured.go
Normal 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
|
||||
}
|
44
pkg/runtime/unstructured_test.go
Normal file
44
pkg/runtime/unstructured_test.go
Normal 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])
|
||||
}
|
||||
}
|
@ -153,7 +153,7 @@ func TestNewBuilder(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, item := range tests {
|
||||
o := testclient.NewObjects(api.Scheme)
|
||||
o := testclient.NewObjects(api.Scheme, api.Scheme)
|
||||
o.Add(item.pv)
|
||||
o.Add(item.claim)
|
||||
client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)}
|
||||
@ -213,7 +213,7 @@ func TestNewBuilderClaimNotBound(t *testing.T) {
|
||||
ClaimName: "claimC",
|
||||
},
|
||||
}
|
||||
o := testclient.NewObjects(api.Scheme)
|
||||
o := testclient.NewObjects(api.Scheme, api.Scheme)
|
||||
o.Add(pv)
|
||||
o.Add(claim)
|
||||
client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)}
|
||||
|
@ -29,7 +29,7 @@ import (
|
||||
)
|
||||
|
||||
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)}
|
||||
binder := NewPersistentVolumeClaimBinder(client, 1*time.Second)
|
||||
|
||||
@ -111,8 +111,8 @@ func TestExampleObjects(t *testing.T) {
|
||||
}
|
||||
|
||||
for name, scenario := range scenarios {
|
||||
o := testclient.NewObjects(api.Scheme)
|
||||
if err := testclient.AddObjectsFromPath("../../examples/persistent-volumes/"+name, o); err != nil {
|
||||
o := testclient.NewObjects(api.Scheme, api.Scheme)
|
||||
if err := testclient.AddObjectsFromPath("../../examples/persistent-volumes/"+name, o, api.Scheme); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@ -168,11 +168,11 @@ func TestExampleObjects(t *testing.T) {
|
||||
|
||||
func TestBindingWithExamples(t *testing.T) {
|
||||
api.ForTesting_ReferencesAllowBlankSelfLinks = true
|
||||
o := testclient.NewObjects(api.Scheme)
|
||||
if err := testclient.AddObjectsFromPath("../../examples/persistent-volumes/claims/claim-01.yaml", o); err != nil {
|
||||
o := testclient.NewObjects(api.Scheme, api.Scheme)
|
||||
if err := testclient.AddObjectsFromPath("../../examples/persistent-volumes/claims/claim-01.yaml", o, api.Scheme); err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user