diff --git a/pkg/runtime/extension.go b/pkg/runtime/extension.go new file mode 100644 index 00000000000..4d731cb4838 --- /dev/null +++ b/pkg/runtime/extension.go @@ -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 +} diff --git a/pkg/runtime/helper_test.go b/pkg/runtime/helper_test.go deleted file mode 100644 index b68178a3227..00000000000 --- a/pkg/runtime/helper_test.go +++ /dev/null @@ -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) - } - } -} diff --git a/pkg/runtime/helper.go b/pkg/runtime/scheme.go similarity index 67% rename from pkg/runtime/helper.go rename to pkg/runtime/scheme.go index 840a9fddcca..a90c67da31e 100644 --- a/pkg/runtime/helper.go +++ b/pkg/runtime/scheme.go @@ -34,6 +34,149 @@ type Scheme struct { 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 // as the "DefaultScheme" variable. func NewScheme(internalVersion, externalVersion string) *Scheme { @@ -54,6 +197,13 @@ func (s *Scheme) AddKnownTypes(version string, types ...Object) { 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 // representation) and name, or an error if it hasn't been registered. 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) } +// 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 // dereferenced pointer, ensuring that it is settable/addressable. // Returns an error if this is not possible. diff --git a/pkg/runtime/scheme_test.go b/pkg/runtime/scheme_test.go new file mode 100644 index 00000000000..b44454ba42d --- /dev/null +++ b/pkg/runtime/scheme_test.go @@ -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) + } + } +} diff --git a/pkg/runtime/types.go b/pkg/runtime/types.go index 17148733950..c361906d2cb 100644 --- a/pkg/runtime/types.go +++ b/pkg/runtime/types.go @@ -43,6 +43,12 @@ type JSONBase struct { 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 // 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. @@ -51,18 +57,70 @@ type JSONBase struct { // // 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 { Object } -// Extension allows api objects with unknown types to be passed-through. This can be used -// to deal with the API objects from a plug-in. Extension objects still have functioning -// JSONBase features-- kind, version, resourceVersion, etc. -// TODO: Not implemented yet -type Extension struct { - JSONBase `yaml:",inline" json:",inline"` - // RawJSON to go here. +// RawExtension is used with EmbeddedObject to do a two-phase encoding of extension objects. +// +// To use this, make a field which has RawExtension as its type in your external, versioned +// struct, and EmbeddedObject in your internal struct. You also need to register your +// various plugin types. +// +// // 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() {}