mirror of https://github.com/k3s-io/k3s
pkg/runtime now has a well defined api pluggability model.
parent
71e547124c
commit
52d2c221b8
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"gopkg.in/v1/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (re *RawExtension) UnmarshalJSON(in []byte) error {
|
||||||
|
re.RawJSON = in
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *RawExtension) MarshalJSON() ([]byte, error) {
|
||||||
|
return re.RawJSON, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetYAML implements the yaml.Setter interface.
|
||||||
|
func (re *RawExtension) SetYAML(tag string, value interface{}) bool {
|
||||||
|
if value == nil {
|
||||||
|
re.RawJSON = []byte("null")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Why does the yaml package send value as a map[interface{}]interface{}?
|
||||||
|
// It's especially frustrating because encoding/json does the right thing
|
||||||
|
// by giving a []byte. So here we do the embarrasing thing of re-encode and
|
||||||
|
// de-encode the right way.
|
||||||
|
// TODO: Write a version of Decode that uses reflect to turn this value
|
||||||
|
// into an API object.
|
||||||
|
b, err := yaml.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
panic("yaml can't reverse its own object")
|
||||||
|
}
|
||||||
|
re.RawJSON = b
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetYAML implements the yaml.Getter interface.
|
||||||
|
func (re *RawExtension) GetYAML() (tag string, value interface{}) {
|
||||||
|
return tag, re.RawJSON
|
||||||
|
}
|
|
@ -1,81 +0,0 @@
|
||||||
/*
|
|
||||||
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 (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEncode(t *testing.T) {
|
|
||||||
pod := &api.Pod{
|
|
||||||
Labels: map[string]string{"name": "foo"},
|
|
||||||
}
|
|
||||||
obj := runtime.Object(pod)
|
|
||||||
data, err := runtime.DefaultScheme.Encode(obj)
|
|
||||||
obj2, err2 := runtime.DefaultScheme.Decode(data)
|
|
||||||
if err != nil || err2 != nil {
|
|
||||||
t.Fatalf("Failure: '%v' '%v'", err, err2)
|
|
||||||
}
|
|
||||||
if _, ok := obj2.(*api.Pod); !ok {
|
|
||||||
t.Fatalf("Got wrong type")
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(obj2, pod) {
|
|
||||||
t.Errorf("Expected:\n %#v,\n Got:\n %#v", &pod, obj2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBadJSONRejection(t *testing.T) {
|
|
||||||
badJSONMissingKind := []byte(`{ }`)
|
|
||||||
if _, err := runtime.DefaultScheme.Decode(badJSONMissingKind); err == nil {
|
|
||||||
t.Errorf("Did not reject despite lack of kind field: %s", badJSONMissingKind)
|
|
||||||
}
|
|
||||||
badJSONUnknownType := []byte(`{"kind": "bar"}`)
|
|
||||||
if _, err1 := runtime.DefaultScheme.Decode(badJSONUnknownType); err1 == nil {
|
|
||||||
t.Errorf("Did not reject despite use of unknown type: %s", badJSONUnknownType)
|
|
||||||
}
|
|
||||||
/*badJSONKindMismatch := []byte(`{"kind": "Pod"}`)
|
|
||||||
if err2 := DecodeInto(badJSONKindMismatch, &Minion{}); err2 == nil {
|
|
||||||
t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch)
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExtractList(t *testing.T) {
|
|
||||||
pl := &api.PodList{
|
|
||||||
Items: []api.Pod{
|
|
||||||
{JSONBase: api.JSONBase{ID: "1"}},
|
|
||||||
{JSONBase: api.JSONBase{ID: "2"}},
|
|
||||||
{JSONBase: api.JSONBase{ID: "3"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
list, err := runtime.ExtractList(pl)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error %v", err)
|
|
||||||
}
|
|
||||||
if e, a := len(list), len(pl.Items); e != a {
|
|
||||||
t.Fatalf("Expected %v, got %v", e, a)
|
|
||||||
}
|
|
||||||
for i := range list {
|
|
||||||
if e, a := list[i].(*api.Pod).ID, pl.Items[i].ID; e != a {
|
|
||||||
t.Fatalf("Expected %v, got %v", e, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -34,6 +34,149 @@ type Scheme struct {
|
||||||
raw *conversion.Scheme
|
raw *conversion.Scheme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var namedSchemes map[string]*Scheme
|
||||||
|
|
||||||
|
// GetScheme returns the scheme with the given name, creating it if necessary.
|
||||||
|
// Important: You may not modify the returned *Scheme except from init() functions.
|
||||||
|
func GetScheme(schemeName string) *Scheme {
|
||||||
|
if namedSchemes == nil {
|
||||||
|
namedSchemes = map[string]*Scheme{}
|
||||||
|
}
|
||||||
|
if s, ok := namedSchemes[schemeName]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
s := NewScheme("", "")
|
||||||
|
namedSchemes[schemeName] = s
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromScope gets the input version, desired output version, and desired Scheme
|
||||||
|
// from a conversion.Scope.
|
||||||
|
func fromScope(s conversion.Scope) (inVersion, outVersion string, scheme *Scheme) {
|
||||||
|
scheme = DefaultScheme
|
||||||
|
inVersion = s.Meta()["srcVersion"].(string)
|
||||||
|
outVersion = s.Meta()["destVersion"].(string)
|
||||||
|
// If a scheme tag was provided, use it. Look at the struct tag corresponding
|
||||||
|
// to version "".
|
||||||
|
if name := s.SrcTag().Get("scheme"); inVersion == "" && name != "" {
|
||||||
|
scheme = GetScheme(name)
|
||||||
|
}
|
||||||
|
if name := s.DestTag().Get("scheme"); outVersion == "" && name != "" {
|
||||||
|
scheme = GetScheme(name)
|
||||||
|
}
|
||||||
|
return inVersion, outVersion, scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Set up a generic mapping between RawExtension and EmbeddedObject.
|
||||||
|
DefaultScheme.AddConversionFuncs(
|
||||||
|
embeddedObjectToRawExtension,
|
||||||
|
rawExtensionToEmbeddedObject,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// emptyPlugin is used to copy the Kind field to and from plugin objects.
|
||||||
|
type emptyPlugin struct {
|
||||||
|
PluginBase `json:",inline" yaml:",inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// embeddedObjectToRawExtension does the conversion you would expect from the name, using the information
|
||||||
|
// given in conversion.Scope. It's placed in the DefaultScheme as a ConversionFunc to enable plugins;
|
||||||
|
// see the comment for RawExtension.
|
||||||
|
func embeddedObjectToRawExtension(in *EmbeddedObject, out *RawExtension, s conversion.Scope) error {
|
||||||
|
if in.Object == nil {
|
||||||
|
out.RawJSON = []byte("null")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out the type and kind of the output object.
|
||||||
|
_, outVersion, scheme := fromScope(s)
|
||||||
|
_, kind, err := scheme.raw.ObjectVersionAndKind(in.Object)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manufacture an object of this type and kind.
|
||||||
|
outObj, err := scheme.New(outVersion, kind)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually do the conversion.
|
||||||
|
err = s.Convert(in.Object, outObj, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the kind field into the ouput object.
|
||||||
|
err = s.Convert(
|
||||||
|
&emptyPlugin{PluginBase: PluginBase{Kind: kind}},
|
||||||
|
outObj,
|
||||||
|
conversion.SourceToDest|conversion.IgnoreMissingFields|conversion.AllowDifferentFieldTypeNames,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Because we provide the correct version, EncodeToVersion will not attempt a conversion.
|
||||||
|
raw, err := scheme.EncodeToVersion(outObj, outVersion)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: if this fails, create an Unknown-- maybe some other
|
||||||
|
// component will understand it.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out.RawJSON = raw
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawExtensionToEmbeddedObject does the conversion you would expect from the name, using the information
|
||||||
|
// given in conversion.Scope. It's placed in the DefaultScheme as a ConversionFunc to enable plugins;
|
||||||
|
// see the comment for RawExtension.
|
||||||
|
func rawExtensionToEmbeddedObject(in *RawExtension, out *EmbeddedObject, s conversion.Scope) error {
|
||||||
|
if len(in.RawJSON) == 4 && string(in.RawJSON) == "null" {
|
||||||
|
out.Object = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Figure out the type and kind of the output object.
|
||||||
|
inVersion, outVersion, scheme := fromScope(s)
|
||||||
|
_, kind, err := scheme.raw.DataVersionAndKind(in.RawJSON)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to make this object ourselves because we don't store the version field for
|
||||||
|
// plugin objects.
|
||||||
|
inObj, err := scheme.New(inVersion, kind)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scheme.DecodeInto(in.RawJSON, inObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the desired internal version, and do the conversion.
|
||||||
|
outObj, err := scheme.New(outVersion, kind)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = scheme.Convert(inObj, outObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Last step, clear the Kind field; that should always be blank in memory.
|
||||||
|
err = s.Convert(
|
||||||
|
&emptyPlugin{PluginBase: PluginBase{Kind: ""}},
|
||||||
|
outObj,
|
||||||
|
conversion.SourceToDest|conversion.IgnoreMissingFields|conversion.AllowDifferentFieldTypeNames,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out.Object = outObj
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewScheme creates a new Scheme. A default scheme is provided and accessible
|
// NewScheme creates a new Scheme. A default scheme is provided and accessible
|
||||||
// as the "DefaultScheme" variable.
|
// as the "DefaultScheme" variable.
|
||||||
func NewScheme(internalVersion, externalVersion string) *Scheme {
|
func NewScheme(internalVersion, externalVersion string) *Scheme {
|
||||||
|
@ -54,6 +197,13 @@ func (s *Scheme) AddKnownTypes(version string, types ...Object) {
|
||||||
s.raw.AddKnownTypes(version, interfaces...)
|
s.raw.AddKnownTypes(version, interfaces...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddKnownTypeWithName is like AddKnownTypes, but it lets you specify what this type should
|
||||||
|
// be encoded as. Useful for testing when you don't want to make multiple packages to define
|
||||||
|
// your structs.
|
||||||
|
func (s *Scheme) AddKnownTypeWithName(version, kind string, obj Object) {
|
||||||
|
s.raw.AddKnownTypeWithName(version, kind, obj)
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
@ -153,6 +303,11 @@ func (s *Scheme) Encode(obj Object) (data []byte, err error) {
|
||||||
return s.raw.Encode(obj)
|
return s.raw.Encode(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EncodeToVersion is like Encode, but lets you specify the destination version.
|
||||||
|
func (s *Scheme) EncodeToVersion(obj Object, destVersion string) (data []byte, err error) {
|
||||||
|
return s.raw.EncodeToVersion(obj, destVersion)
|
||||||
|
}
|
||||||
|
|
||||||
// enforcePtr ensures that obj is a pointer of some sort. Returns a reflect.Value of the
|
// enforcePtr ensures that obj is a pointer of some sort. Returns a reflect.Value of the
|
||||||
// dereferenced pointer, ensuring that it is settable/addressable.
|
// dereferenced pointer, ensuring that it is settable/addressable.
|
||||||
// Returns an error if this is not possible.
|
// Returns an error if this is not possible.
|
|
@ -0,0 +1,257 @@
|
||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JSONBase struct {
|
||||||
|
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||||
|
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InternalSimple struct {
|
||||||
|
JSONBase `json:",inline" yaml:",inline"`
|
||||||
|
TestString string `json:"testString" yaml:"testString"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalSimple struct {
|
||||||
|
JSONBase `json:",inline" yaml:",inline"`
|
||||||
|
TestString string `json:"testString" yaml:"testString"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*InternalSimple) IsAnAPIObject() {}
|
||||||
|
func (*ExternalSimple) IsAnAPIObject() {}
|
||||||
|
|
||||||
|
func TestScheme(t *testing.T) {
|
||||||
|
runtime.DefaultScheme.AddKnownTypeWithName("", "Simple", &InternalSimple{})
|
||||||
|
runtime.DefaultScheme.AddKnownTypeWithName("externalVersion", "Simple", &ExternalSimple{})
|
||||||
|
|
||||||
|
internalToExternalCalls := 0
|
||||||
|
externalToInternalCalls := 0
|
||||||
|
|
||||||
|
// Register functions to verify that scope.Meta() gets set correctly.
|
||||||
|
err := runtime.DefaultScheme.AddConversionFuncs(
|
||||||
|
func(in *InternalSimple, out *ExternalSimple, scope conversion.Scope) error {
|
||||||
|
if e, a := "", scope.Meta()["srcVersion"].(string); e != a {
|
||||||
|
t.Errorf("Expected '%v', got '%v'", e, a)
|
||||||
|
}
|
||||||
|
if e, a := "externalVersion", scope.Meta()["destVersion"].(string); e != a {
|
||||||
|
t.Errorf("Expected '%v', got '%v'", e, a)
|
||||||
|
}
|
||||||
|
scope.Convert(&in.JSONBase, &out.JSONBase, 0)
|
||||||
|
scope.Convert(&in.TestString, &out.TestString, 0)
|
||||||
|
internalToExternalCalls++
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
func(in *ExternalSimple, out *InternalSimple, scope conversion.Scope) error {
|
||||||
|
if e, a := "externalVersion", scope.Meta()["srcVersion"].(string); e != a {
|
||||||
|
t.Errorf("Expected '%v', got '%v'", e, a)
|
||||||
|
}
|
||||||
|
if e, a := "", scope.Meta()["destVersion"].(string); e != a {
|
||||||
|
t.Errorf("Expected '%v', got '%v'", e, a)
|
||||||
|
}
|
||||||
|
scope.Convert(&in.JSONBase, &out.JSONBase, 0)
|
||||||
|
scope.Convert(&in.TestString, &out.TestString, 0)
|
||||||
|
externalToInternalCalls++
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
simple := &InternalSimple{
|
||||||
|
TestString: "foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Encode, Decode, and DecodeInto
|
||||||
|
obj := runtime.Object(simple)
|
||||||
|
data, err := runtime.DefaultScheme.EncodeToVersion(obj, "externalVersion")
|
||||||
|
obj2, err2 := runtime.DefaultScheme.Decode(data)
|
||||||
|
obj3 := &InternalSimple{}
|
||||||
|
err3 := runtime.DefaultScheme.DecodeInto(data, obj3)
|
||||||
|
if err != nil || err2 != nil {
|
||||||
|
t.Fatalf("Failure: '%v' '%v' '%v'", err, err2, err3)
|
||||||
|
}
|
||||||
|
if _, ok := obj2.(*InternalSimple); !ok {
|
||||||
|
t.Fatalf("Got wrong type")
|
||||||
|
}
|
||||||
|
if e, a := simple, obj2; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a)
|
||||||
|
}
|
||||||
|
if e, a := simple, obj3; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Convert
|
||||||
|
external := &ExternalSimple{}
|
||||||
|
err = runtime.DefaultScheme.Convert(simple, external)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if e, a := simple.TestString, external.TestString; e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode and Convert should each have caused an increment.
|
||||||
|
if e, a := 2, internalToExternalCalls; e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
// Decode and DecodeInto should each have caused an increment.
|
||||||
|
if e, a := 2, externalToInternalCalls; e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadJSONRejection(t *testing.T) {
|
||||||
|
badJSONMissingKind := []byte(`{ }`)
|
||||||
|
if _, err := runtime.DefaultScheme.Decode(badJSONMissingKind); err == nil {
|
||||||
|
t.Errorf("Did not reject despite lack of kind field: %s", badJSONMissingKind)
|
||||||
|
}
|
||||||
|
badJSONUnknownType := []byte(`{"kind": "bar"}`)
|
||||||
|
if _, err1 := runtime.DefaultScheme.Decode(badJSONUnknownType); err1 == nil {
|
||||||
|
t.Errorf("Did not reject despite use of unknown type: %s", badJSONUnknownType)
|
||||||
|
}
|
||||||
|
/*badJSONKindMismatch := []byte(`{"kind": "Pod"}`)
|
||||||
|
if err2 := DecodeInto(badJSONKindMismatch, &Minion{}); err2 == nil {
|
||||||
|
t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch)
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtensionA struct {
|
||||||
|
runtime.PluginBase `json:",inline" yaml:",inline"`
|
||||||
|
TestString string `json:"testString" yaml:"testString"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtensionB struct {
|
||||||
|
runtime.PluginBase `json:",inline" yaml:",inline"`
|
||||||
|
TestString string `json:"testString" yaml:"testString"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalExtensionType struct {
|
||||||
|
JSONBase `json:",inline" yaml:",inline"`
|
||||||
|
Extension runtime.RawExtension `json:"extension" yaml:"extension"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InternalExtensionType struct {
|
||||||
|
JSONBase `json:",inline" yaml:",inline"`
|
||||||
|
Extension runtime.EmbeddedObject `json:"extension" yaml:"extension" scheme:"testExtension"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InternalExtensionTypeNoScheme struct {
|
||||||
|
JSONBase `json:",inline" yaml:",inline"`
|
||||||
|
Extension runtime.EmbeddedObject `json:"extension" yaml:"extension"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ExtensionA) IsAnAPIObject() {}
|
||||||
|
func (*ExtensionB) IsAnAPIObject() {}
|
||||||
|
func (*ExternalExtensionType) IsAnAPIObject() {}
|
||||||
|
func (*InternalExtensionType) IsAnAPIObject() {}
|
||||||
|
func (*InternalExtensionTypeNoScheme) IsAnAPIObject() {}
|
||||||
|
|
||||||
|
func TestExtensionMappingWithScheme(t *testing.T) {
|
||||||
|
runtime.DefaultScheme.AddKnownTypeWithName("", "ExtensionType", &InternalExtensionType{})
|
||||||
|
runtime.GetScheme("testExtension").AddKnownTypeWithName("", "A", &ExtensionA{})
|
||||||
|
runtime.GetScheme("testExtension").AddKnownTypeWithName("", "B", &ExtensionB{})
|
||||||
|
runtime.DefaultScheme.AddKnownTypeWithName("testExternal", "ExtensionType", &ExternalExtensionType{})
|
||||||
|
runtime.GetScheme("testExtension").AddKnownTypeWithName("testExternal", "A", &ExtensionA{})
|
||||||
|
runtime.GetScheme("testExtension").AddKnownTypeWithName("testExternal", "B", &ExtensionB{})
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
obj runtime.Object
|
||||||
|
encoded string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
&InternalExtensionType{Extension: runtime.EmbeddedObject{&ExtensionA{TestString: "foo"}}},
|
||||||
|
`{"kind":"ExtensionType","apiVersion":"testExternal","extension":{"kind":"A","testString":"foo"}}`,
|
||||||
|
}, {
|
||||||
|
&InternalExtensionType{Extension: runtime.EmbeddedObject{&ExtensionB{TestString: "bar"}}},
|
||||||
|
`{"kind":"ExtensionType","apiVersion":"testExternal","extension":{"kind":"B","testString":"bar"}}`,
|
||||||
|
}, {
|
||||||
|
&InternalExtensionType{Extension: runtime.EmbeddedObject{nil}},
|
||||||
|
`{"kind":"ExtensionType","apiVersion":"testExternal","extension":null}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range table {
|
||||||
|
gotEncoded, err := runtime.DefaultScheme.EncodeToVersion(item.obj, "testExternal")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error '%v' (%#v)", err, item.obj)
|
||||||
|
} else if e, a := item.encoded, string(gotEncoded); e != a {
|
||||||
|
t.Errorf("expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotDecoded, err := runtime.DefaultScheme.Decode([]byte(item.encoded))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error '%v' (%v)", err, item.encoded)
|
||||||
|
} else if e, a := item.obj, gotDecoded; !reflect.DeepEqual(e, a) {
|
||||||
|
var eEx, aEx runtime.Object
|
||||||
|
if obj, ok := e.(*InternalExtensionType); ok {
|
||||||
|
eEx = obj.Extension.Object
|
||||||
|
}
|
||||||
|
if obj, ok := a.(*InternalExtensionType); ok {
|
||||||
|
aEx = obj.Extension.Object
|
||||||
|
}
|
||||||
|
t.Errorf("expected %#v, got %#v (%#v, %#v)", e, a, eEx, aEx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtensionMappingWithoutScheme(t *testing.T) {
|
||||||
|
runtime.DefaultScheme.AddKnownTypeWithName("", "ExtensionType", &InternalExtensionTypeNoScheme{})
|
||||||
|
runtime.DefaultScheme.AddKnownTypeWithName("", "ExA", &ExtensionA{})
|
||||||
|
runtime.DefaultScheme.AddKnownTypeWithName("testExternal", "ExtensionType", &ExternalExtensionType{})
|
||||||
|
runtime.DefaultScheme.AddKnownTypeWithName("testExternal", "ExA", &ExtensionA{})
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
obj runtime.Object
|
||||||
|
encoded string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
&InternalExtensionTypeNoScheme{Extension: runtime.EmbeddedObject{&ExtensionA{TestString: "foo"}}},
|
||||||
|
`{"kind":"ExtensionType","apiVersion":"testExternal","extension":{"kind":"ExA","testString":"foo"}}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range table {
|
||||||
|
gotEncoded, err := runtime.DefaultScheme.EncodeToVersion(item.obj, "testExternal")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error '%v' (%#v)", err, item.obj)
|
||||||
|
} else if e, a := item.encoded, string(gotEncoded); e != a {
|
||||||
|
t.Errorf("expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotDecoded, err := runtime.DefaultScheme.Decode([]byte(item.encoded))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error '%v' (%v)", err, item.encoded)
|
||||||
|
} else if e, a := item.obj, gotDecoded; !reflect.DeepEqual(e, a) {
|
||||||
|
var eEx, aEx runtime.Object
|
||||||
|
if obj, ok := e.(*InternalExtensionTypeNoScheme); ok {
|
||||||
|
eEx = obj.Extension.Object
|
||||||
|
}
|
||||||
|
if obj, ok := a.(*InternalExtensionTypeNoScheme); ok {
|
||||||
|
aEx = obj.Extension.Object
|
||||||
|
}
|
||||||
|
t.Errorf("expected %#v, got %#v (%#v, %#v)", e, a, eEx, aEx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,6 +43,12 @@ type JSONBase struct {
|
||||||
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
|
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PluginBase is like JSONBase, but it's intended for plugin objects that won't ever be encoded
|
||||||
|
// except while embedded in other objects.
|
||||||
|
type PluginBase struct {
|
||||||
|
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// EmbeddedObject has appropriate encoder and decoder functions, such that on the wire, it's
|
// EmbeddedObject has appropriate encoder and decoder functions, such that on the wire, it's
|
||||||
// stored as a []byte, but in memory, the contained object is accessable as an Object
|
// stored as a []byte, but in memory, the contained object is accessable as an Object
|
||||||
// via the Get() function. Only valid API objects may be stored via EmbeddedObject.
|
// via the Get() function. Only valid API objects may be stored via EmbeddedObject.
|
||||||
|
@ -51,18 +57,70 @@ type JSONBase struct {
|
||||||
//
|
//
|
||||||
// Note that object assumes that you've registered all of your api types with the api package.
|
// Note that object assumes that you've registered all of your api types with the api package.
|
||||||
//
|
//
|
||||||
// TODO(dbsmith): Stop using runtime.Codec, use the codec appropriate for the conversion (I have a plan).
|
// EmbeddedObject and RawExtension can be used together to allow for API object extensions:
|
||||||
|
// see the comment for RawExtension.
|
||||||
type EmbeddedObject struct {
|
type EmbeddedObject struct {
|
||||||
Object
|
Object
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extension allows api objects with unknown types to be passed-through. This can be used
|
// RawExtension is used with EmbeddedObject to do a two-phase encoding of extension objects.
|
||||||
// to deal with the API objects from a plug-in. Extension objects still have functioning
|
//
|
||||||
// JSONBase features-- kind, version, resourceVersion, etc.
|
// To use this, make a field which has RawExtension as its type in your external, versioned
|
||||||
// TODO: Not implemented yet
|
// struct, and EmbeddedObject in your internal struct. You also need to register your
|
||||||
type Extension struct {
|
// various plugin types.
|
||||||
JSONBase `yaml:",inline" json:",inline"`
|
//
|
||||||
// RawJSON to go here.
|
// // Internal package:
|
||||||
|
// type MyAPIObject struct {
|
||||||
|
// runtime.JSONBase `yaml:",inline" json:",inline"`
|
||||||
|
// // The "scheme" tag is optional; if absent, runtime.DefaultScheme will be used.
|
||||||
|
// MyPlugin runtime.EmbeddedObject `scheme:"pluginScheme"`
|
||||||
|
// }
|
||||||
|
// type PluginA struct {
|
||||||
|
// runtime.PluginBase `yaml:",inline" json:",inline"`
|
||||||
|
// AOption string `yaml:"aOption" json:"aOption"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // External package:
|
||||||
|
// type MyAPIObject struct {
|
||||||
|
// runtime.JSONBase `yaml:",inline" json:",inline"`
|
||||||
|
// MyPlugin runtime.RawExtension `json:"myPlugin" yaml:"myPlugin"`
|
||||||
|
// }
|
||||||
|
// type PluginA struct {
|
||||||
|
// runtime.PluginBase `yaml:",inline" json:",inline"`
|
||||||
|
// AOption string `yaml:"aOption" json:"aOption"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // On the wire, the JSON will look something like this:
|
||||||
|
// {
|
||||||
|
// "kind":"MyAPIObject",
|
||||||
|
// "apiVersion":"v1beta1",
|
||||||
|
// "myPlugin": {
|
||||||
|
// "kind":"PluginA",
|
||||||
|
// "aOption":"foo",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// So what happens? Decode first uses json or yaml to unmarshal the serialized data into
|
||||||
|
// your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked.
|
||||||
|
// The next step is to copy (using pkg/conversion) into the internal struct. The runtime
|
||||||
|
// package's DefaultScheme has conversion functions installed which will unpack the
|
||||||
|
// JSON stored in RawExtension, turning it into the correct object type, and storing it
|
||||||
|
// in the EmbeddedObject. (TODO: In the case where the object is of an unknown type, a
|
||||||
|
// runtime.Unknown object will be created and stored.)
|
||||||
|
type RawExtension struct {
|
||||||
|
RawJSON []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Extension) IsAnAPIObject() {}
|
// Unknown allows api objects with unknown types to be passed-through. This can be used
|
||||||
|
// to deal with the API objects from a plug-in. Unknown objects still have functioning
|
||||||
|
// JSONBase features-- kind, version, resourceVersion, etc.
|
||||||
|
// TODO: Not implemented yet!
|
||||||
|
type Unknown struct {
|
||||||
|
JSONBase `yaml:",inline" json:",inline"`
|
||||||
|
// RawJSON will hold the complete JSON of the object which couldn't be matched
|
||||||
|
// with a registered type. Most likely, nothing should be done with this
|
||||||
|
// except for passing it through the system.
|
||||||
|
RawJSON []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Unknown) IsAnAPIObject() {}
|
||||||
|
|
Loading…
Reference in New Issue