diff --git a/pkg/conversion/encode.go b/pkg/conversion/encode.go index 0f17f6057ab..8b29c1841d7 100644 --- a/pkg/conversion/encode.go +++ b/pkg/conversion/encode.go @@ -51,7 +51,7 @@ import ( // func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []byte, err error) { obj = maybeCopy(obj) - v, _ := enforcePtr(obj) // maybeCopy guarantees a pointer + v, _ := EnforcePtr(obj) // maybeCopy guarantees a pointer if _, registered := s.typeToVersion[v.Type()]; !registered { return nil, fmt.Errorf("type %v is not registered and it will be impossible to Decode it, therefore Encode will refuse to encode it.", v.Type()) } diff --git a/pkg/conversion/meta.go b/pkg/conversion/meta.go new file mode 100644 index 00000000000..d82968bae8d --- /dev/null +++ b/pkg/conversion/meta.go @@ -0,0 +1,119 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package conversion + +import ( + "fmt" + "reflect" + + "gopkg.in/v1/yaml" +) + +// MetaFactory is used to store and retrieve the version and kind +// information for all objects in a scheme. +type MetaFactory interface { + // Update sets the given version and kind onto the object. + Update(version, kind string, obj interface{}) error + // Interpret should return the version and kind of the wire-format of + // the object. + Interpret(data []byte) (version, kind string, err error) +} + +// DefaultMetaFactory is a default factory for versioning objects in JSON/YAML. The object +// in memory and in the default JSON serialization will use the "kind" and "apiVersion" +// fields. +var DefaultMetaFactory = SimpleMetaFactory{KindField: "Kind", VersionField: "APIVersion"} + +// SimpleMetaFactory provides default methods for retrieving the type and version of objects +// that are identified with an "apiVersion" and "kind" fields in their JSON/YAML +// serialization. It may be parameterized with the names of the fields in memory, or an +// optional list of base structs to search for those fields in memory. +type SimpleMetaFactory struct { + // The name of the API version field in memory of the struct + VersionField string + // The name of the kind field in memory of the struct. + KindField string + // Optional, if set will look in the named inline structs to find the fields to set. + BaseFields []string +} + +// Interpret will return the APIVersion and Kind of the JSON/YAML wire-format +// encoding of an object, or an error. +func (SimpleMetaFactory) Interpret(data []byte) (version, kind string, err error) { + findKind := struct { + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + }{} + // yaml is a superset of json, so we use it to decode here. That way, + // we understand both. + err = yaml.Unmarshal(data, &findKind) + if err != nil { + return "", "", fmt.Errorf("couldn't get version/kind: %v", err) + } + return findKind.APIVersion, findKind.Kind, nil +} + +func (f SimpleMetaFactory) Update(version, kind string, obj interface{}) error { + return UpdateVersionAndKind(f.BaseFields, f.VersionField, version, f.KindField, kind, obj) +} + +// UpdateVersionAndKind uses reflection to find and set the versionField and kindField fields +// on a pointer to a struct to version and kind. Provided as a convenience for others +// implementing MetaFactory. Pass an array to baseFields to check one or more nested structs +// for the named fields. The version field is treated as optional if it is not present in the struct. +func UpdateVersionAndKind(baseFields []string, versionField, version, kindField, kind string, obj interface{}) error { + v, err := EnforcePtr(obj) + if err != nil { + return err + } + t := v.Type() + name := t.Name() + if v.Kind() != reflect.Struct { + return fmt.Errorf("expected struct, but got %v: %v (%#v)", v.Kind(), name, v.Interface()) + } + + for i := range baseFields { + base := v.FieldByName(baseFields[i]) + if !base.IsValid() { + continue + } + v = base + } + + field := v.FieldByName(kindField) + if !field.IsValid() { + return fmt.Errorf("couldn't find %v field in %#v", kindField, v.Interface()) + } + field.SetString(kind) + + if field := v.FieldByName(versionField); field.IsValid() { + field.SetString(version) + } + + return nil +} + +// 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. +func EnforcePtr(obj interface{}) (reflect.Value, error) { + v := reflect.ValueOf(obj) + if v.Kind() != reflect.Ptr { + return reflect.Value{}, fmt.Errorf("expected pointer, but got %v", v.Type().Name()) + } + return v.Elem(), nil +} diff --git a/pkg/conversion/meta_test.go b/pkg/conversion/meta_test.go new file mode 100644 index 00000000000..b4c6c0771b2 --- /dev/null +++ b/pkg/conversion/meta_test.go @@ -0,0 +1,244 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package conversion + +import ( + "reflect" + "testing" +) + +func TestSimpleMetaFactoryInterpret(t *testing.T) { + factory := SimpleMetaFactory{} + version, kind, err := factory.Interpret([]byte(`{"apiVersion":"1","kind":"object"}`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if version != "1" || kind != "object" { + t.Errorf("unexpected interpret: %s %s", version, kind) + } + + // no kind or version + version, kind, err = factory.Interpret([]byte(`{}`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if version != "" || kind != "" { + t.Errorf("unexpected interpret: %s %s", version, kind) + } + + // unparsable + version, kind, err = factory.Interpret([]byte(`{`)) + if err == nil { + t.Errorf("unexpected non-error") + } +} + +func TestSimpleMetaFactoryUpdate(t *testing.T) { + factory := SimpleMetaFactory{VersionField: "V", KindField: "K"} + + obj := struct { + V string + K string + }{"1", "2"} + + // must pass a pointer + if err := factory.Update("test", "other", obj); err == nil { + t.Errorf("unexpected non-error") + } + if obj.V != "1" || obj.K != "2" { + t.Errorf("unexpected update: %v", obj) + } + + // updates + if err := factory.Update("test", "other", &obj); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if obj.V != "test" || obj.K != "other" { + t.Errorf("unexpected update: %v", obj) + } +} + +func TestSimpleMetaFactoryUpdateStruct(t *testing.T) { + factory := SimpleMetaFactory{BaseFields: []string{"Test"}, VersionField: "V", KindField: "K"} + + type Inner struct { + V string + K string + } + obj := struct { + Test Inner + }{Test: Inner{"1", "2"}} + + // updates + if err := factory.Update("test", "other", &obj); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if obj.Test.V != "test" || obj.Test.K != "other" { + t.Errorf("unexpected update: %v", obj) + } +} + +func TestMetaValues(t *testing.T) { + type InternalSimple struct { + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + TestString string `json:"testString" yaml:"testString"` + } + type ExternalSimple struct { + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + TestString string `json:"testString" yaml:"testString"` + } + s := NewScheme() + s.AddKnownTypeWithName("", "Simple", &InternalSimple{}) + s.AddKnownTypeWithName("externalVersion", "Simple", &ExternalSimple{}) + + internalToExternalCalls := 0 + externalToInternalCalls := 0 + + // Register functions to verify that scope.Meta() gets set correctly. + err := s.AddConversionFuncs( + func(in *InternalSimple, out *ExternalSimple, scope Scope) error { + t.Logf("internal -> external") + if e, a := "", scope.Meta().SrcVersion; e != a { + t.Fatalf("Expected '%v', got '%v'", e, a) + } + if e, a := "externalVersion", scope.Meta().DestVersion; e != a { + t.Fatalf("Expected '%v', got '%v'", e, a) + } + scope.Convert(&in.TestString, &out.TestString, 0) + internalToExternalCalls++ + return nil + }, + func(in *ExternalSimple, out *InternalSimple, scope Scope) error { + t.Logf("external -> internal") + if e, a := "externalVersion", scope.Meta().SrcVersion; e != a { + t.Errorf("Expected '%v', got '%v'", e, a) + } + if e, a := "", scope.Meta().DestVersion; e != a { + t.Fatalf("Expected '%v', got '%v'", e, a) + } + scope.Convert(&in.TestString, &out.TestString, 0) + externalToInternalCalls++ + return nil + }, + ) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + simple := &InternalSimple{ + TestString: "foo", + } + + s.Log(t) + + // Test Encode, Decode, and DecodeInto + data, err := s.EncodeToVersion(simple, "externalVersion") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + t.Logf(string(data)) + obj2, err := s.Decode(data) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + 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) + } + + obj3 := &InternalSimple{} + if err := s.DecodeInto(data, obj3); err != nil { + t.Fatalf("unexpected error: %v", err) + } + 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 = s.Convert(simple, external) + if err != nil { + t.Fatalf("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 TestMetaValuesUnregisteredConvert(t *testing.T) { + type InternalSimple struct { + Version string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + TestString string `json:"testString" yaml:"testString"` + } + type ExternalSimple struct { + Version string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + TestString string `json:"testString" yaml:"testString"` + } + s := NewScheme() + s.InternalVersion = "" + // We deliberately don't register the types. + + internalToExternalCalls := 0 + + // Register functions to verify that scope.Meta() gets set correctly. + err := s.AddConversionFuncs( + func(in *InternalSimple, out *ExternalSimple, scope Scope) error { + if e, a := "unknown", scope.Meta().SrcVersion; e != a { + t.Fatalf("Expected '%v', got '%v'", e, a) + } + if e, a := "unknown", scope.Meta().DestVersion; e != a { + t.Fatalf("Expected '%v', got '%v'", e, a) + } + scope.Convert(&in.TestString, &out.TestString, 0) + internalToExternalCalls++ + return nil + }, + ) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + simple := &InternalSimple{TestString: "foo"} + external := &ExternalSimple{} + err = s.Convert(simple, external) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if e, a := simple.TestString, external.TestString; e != a { + t.Errorf("Expected %v, got %v", e, a) + } + + // Verify that our conversion handler got called. + if e, a := 1, internalToExternalCalls; e != a { + t.Errorf("Expected %v, got %v", e, a) + } +} diff --git a/pkg/conversion/scheme.go b/pkg/conversion/scheme.go index d5300378777..cad4c4ff2db 100644 --- a/pkg/conversion/scheme.go +++ b/pkg/conversion/scheme.go @@ -19,27 +19,8 @@ package conversion import ( "fmt" "reflect" - - "gopkg.in/v1/yaml" ) -// MetaInsertionFactory is used to create an object to store and retrieve -// the version and kind information for all objects. The default uses the -// keys "version" and "kind" respectively. The object produced by this -// factory is used to clear the version and kind fields in memory, so it -// must match the layout of your actual api structs. (E.g., if you have your -// version and kind field inside an inlined struct, this must produce an -// inlined struct with the same field name.) -type MetaInsertionFactory interface { - // Create should make a new object with two fields. - // This object will be used to encode this metadata along with your - // API objects, so the tags on the fields you use shouldn't conflict. - Create(version, kind string) interface{} - // Interpret should take the same type of object that Create creates. - // It should return the version and kind information from this object. - Interpret(interface{}) (version, kind string) -} - // Scheme defines an entire encoding and decoding scheme. type Scheme struct { // versionMap allows one to figure out the go type of an object with @@ -68,19 +49,19 @@ type Scheme struct { // MetaInsertionFactory is used to create an object to store and retrieve // the version and kind information for all objects. The default uses the - // keys "version" and "kind" respectively. - MetaInsertionFactory MetaInsertionFactory + // keys "apiVersion" and "kind" respectively. + MetaFactory MetaFactory } // NewScheme manufactures a new scheme. func NewScheme() *Scheme { s := &Scheme{ - versionMap: map[string]map[string]reflect.Type{}, - typeToVersion: map[reflect.Type]string{}, - typeToKind: map[reflect.Type][]string{}, - converter: NewConverter(), - InternalVersion: "", - MetaInsertionFactory: metaInsertion{}, + versionMap: map[string]map[string]reflect.Type{}, + typeToVersion: map[reflect.Type]string{}, + typeToKind: map[reflect.Type][]string{}, + converter: NewConverter(), + InternalVersion: "", + MetaFactory: DefaultMetaFactory, } s.converter.NameFunc = s.nameFunc return s @@ -238,47 +219,16 @@ func (s *Scheme) generateConvertMeta(srcVersion, destVersion string) *Meta { } } -// metaInsertion provides a default implementation of MetaInsertionFactory. -type metaInsertion struct { - Version string `json:"version,omitempty" yaml:"version,omitempty"` - Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` -} - -// Create should make a new object with two fields. -// This object will be used to encode this metadata along with your -// API objects, so the tags on the fields you use shouldn't conflict. -func (metaInsertion) Create(version, kind string) interface{} { - m := metaInsertion{} - m.Version = version - m.Kind = kind - return &m -} - -// Interpret should take the same type of object that Create creates. -// It should return the version and kind information from this object. -func (metaInsertion) Interpret(in interface{}) (version, kind string) { - m := in.(*metaInsertion) - return m.Version, m.Kind -} - // DataVersionAndKind will return the APIVersion and Kind of the given wire-format // enconding of an API Object, or an error. func (s *Scheme) DataVersionAndKind(data []byte) (version, kind string, err error) { - findKind := s.MetaInsertionFactory.Create("", "") - // yaml is a superset of json, so we use it to decode here. That way, - // we understand both. - err = yaml.Unmarshal(data, findKind) - if err != nil { - return "", "", fmt.Errorf("couldn't get version/kind: %v", err) - } - version, kind = s.MetaInsertionFactory.Interpret(findKind) - return version, kind, nil + return s.MetaFactory.Interpret(data) } // ObjectVersionAndKind returns the API version and kind of the go object, // or an error if it's not a pointer or is unregistered. func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string, err error) { - v, err := enforcePtr(obj) + v, err := EnforcePtr(obj) if err != nil { return "", "", err } @@ -297,8 +247,7 @@ func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string, // MetaInsertionFactory). Returns an error if this isn't possible. obj // must be a pointer. func (s *Scheme) SetVersionAndKind(version, kind string, obj interface{}) error { - versionAndKind := s.MetaInsertionFactory.Create(version, kind) - return s.converter.Convert(versionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldTypeNames, nil) + return s.MetaFactory.Update(version, kind, obj) } // maybeCopy copies obj if it is not a pointer, to get a settable/addressable @@ -312,14 +261,3 @@ func maybeCopy(obj interface{}) interface{} { v2.Elem().Set(v) return v2.Interface() } - -// 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. -func enforcePtr(obj interface{}) (reflect.Value, error) { - v := reflect.ValueOf(obj) - if v.Kind() != reflect.Ptr { - return reflect.Value{}, fmt.Errorf("expected pointer, but got %v", v.Type().Name()) - } - return v.Elem(), nil -} diff --git a/pkg/conversion/scheme_test.go b/pkg/conversion/scheme_test.go index 16157e866a0..b299299e545 100644 --- a/pkg/conversion/scheme_test.go +++ b/pkg/conversion/scheme_test.go @@ -25,7 +25,9 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/google/gofuzz" + "gopkg.in/v1/yaml" ) var fuzzIters = flag.Int("fuzz_iters", 50, "How many fuzzing iterations to do.") @@ -129,30 +131,28 @@ func GetTestScheme() *Scheme { s.AddKnownTypeWithName("", "TestType3", &TestType1{}) s.AddKnownTypeWithName("v1", "TestType3", &ExternalTestType1{}) s.InternalVersion = "" - s.MetaInsertionFactory = testMetaInsertionFactory{} + s.MetaFactory = testMetaFactory{} return s } -type testMetaInsertionFactory struct { - MyWeirdCustomEmbeddedVersionKindField struct { +type testMetaFactory struct{} + +func (testMetaFactory) Interpret(data []byte) (version, kind string, err error) { + findKind := struct { APIVersion string `json:"myVersionKey,omitempty" yaml:"myVersionKey,omitempty"` ObjectKind string `json:"myKindKey,omitempty" yaml:"myKindKey,omitempty"` - } `json:",inline" yaml:",inline"` + }{} + // yaml is a superset of json, so we use it to decode here. That way, + // we understand both. + err = yaml.Unmarshal(data, &findKind) + if err != nil { + return "", "", fmt.Errorf("couldn't get version/kind: %v", err) + } + return findKind.APIVersion, findKind.ObjectKind, nil } -// Create returns a new testMetaInsertionFactory with the version and kind fields set. -func (testMetaInsertionFactory) Create(version, kind string) interface{} { - m := testMetaInsertionFactory{} - m.MyWeirdCustomEmbeddedVersionKindField.APIVersion = version - m.MyWeirdCustomEmbeddedVersionKindField.ObjectKind = kind - return &m -} - -// Interpret returns the version and kind information from in, which must be -// a testMetaInsertionFactory pointer object. -func (testMetaInsertionFactory) Interpret(in interface{}) (version, kind string) { - m := in.(*testMetaInsertionFactory) - return m.MyWeirdCustomEmbeddedVersionKindField.APIVersion, m.MyWeirdCustomEmbeddedVersionKindField.ObjectKind +func (testMetaFactory) Update(version, kind string, obj interface{}) error { + return UpdateVersionAndKind(nil, "APIVersion", version, "ObjectKind", kind, obj) } func objDiff(a, b interface{}) string { @@ -308,143 +308,3 @@ func TestBadJSONRejectionForSetInternalVersion(t *testing.T) { t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch) } } - -func TestMetaValues(t *testing.T) { - type InternalSimple struct { - Version string `json:"version,omitempty" yaml:"version,omitempty"` - Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` - TestString string `json:"testString" yaml:"testString"` - } - type ExternalSimple struct { - Version string `json:"version,omitempty" yaml:"version,omitempty"` - Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` - TestString string `json:"testString" yaml:"testString"` - } - s := NewScheme() - s.InternalVersion = "" - s.AddKnownTypeWithName("", "Simple", &InternalSimple{}) - s.AddKnownTypeWithName("externalVersion", "Simple", &ExternalSimple{}) - - internalToExternalCalls := 0 - externalToInternalCalls := 0 - - // Register functions to verify that scope.Meta() gets set correctly. - err := s.AddConversionFuncs( - func(in *InternalSimple, out *ExternalSimple, scope Scope) error { - if e, a := "", scope.Meta().SrcVersion; e != a { - t.Errorf("Expected '%v', got '%v'", e, a) - } - if e, a := "externalVersion", scope.Meta().DestVersion; e != a { - t.Errorf("Expected '%v', got '%v'", e, a) - } - scope.Convert(&in.TestString, &out.TestString, 0) - internalToExternalCalls++ - return nil - }, - func(in *ExternalSimple, out *InternalSimple, scope Scope) error { - if e, a := "externalVersion", scope.Meta().SrcVersion; e != a { - t.Errorf("Expected '%v', got '%v'", e, a) - } - if e, a := "", scope.Meta().DestVersion; e != a { - t.Errorf("Expected '%v', got '%v'", e, a) - } - 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 - data, err := s.EncodeToVersion(simple, "externalVersion") - obj2, err2 := s.Decode(data) - obj3 := &InternalSimple{} - err3 := s.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 = s.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 TestMetaValuesUnregisteredConvert(t *testing.T) { - type InternalSimple struct { - Version string `json:"version,omitempty" yaml:"version,omitempty"` - Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` - TestString string `json:"testString" yaml:"testString"` - } - type ExternalSimple struct { - Version string `json:"version,omitempty" yaml:"version,omitempty"` - Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` - TestString string `json:"testString" yaml:"testString"` - } - s := NewScheme() - s.InternalVersion = "" - // We deliberately don't register the types. - - internalToExternalCalls := 0 - - // Register functions to verify that scope.Meta() gets set correctly. - err := s.AddConversionFuncs( - func(in *InternalSimple, out *ExternalSimple, scope Scope) error { - if e, a := "unknown", scope.Meta().SrcVersion; e != a { - t.Errorf("Expected '%v', got '%v'", e, a) - } - if e, a := "unknown", scope.Meta().DestVersion; e != a { - t.Errorf("Expected '%v', got '%v'", e, a) - } - scope.Convert(&in.TestString, &out.TestString, 0) - internalToExternalCalls++ - return nil - }, - ) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - simple := &InternalSimple{TestString: "foo"} - external := &ExternalSimple{} - err = s.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) - } - - // Verify that our conversion handler got called. - if e, a := 1, internalToExternalCalls; e != a { - t.Errorf("Expected %v, got %v", e, a) - } -} diff --git a/pkg/runtime/scheme.go b/pkg/runtime/scheme.go index 58291356bce..08458961b95 100644 --- a/pkg/runtime/scheme.go +++ b/pkg/runtime/scheme.go @@ -17,11 +17,9 @@ limitations under the License. package runtime import ( - "fmt" "reflect" "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" - "gopkg.in/v1/yaml" ) // Scheme defines methods for serializing and deserializing API objects. It @@ -145,7 +143,7 @@ func (self *Scheme) rawExtensionToEmbeddedObject(in *RawExtension, out *Embedded func NewScheme() *Scheme { s := &Scheme{conversion.NewScheme()} s.raw.InternalVersion = "" - s.raw.MetaInsertionFactory = metaInsertion{} + s.raw.MetaFactory = conversion.SimpleMetaFactory{BaseFields: []string{"TypeMeta"}, VersionField: "APIVersion", KindField: "Kind"} s.raw.AddConversionFuncs( s.embeddedObjectToRawExtension, s.rawExtensionToEmbeddedObject, @@ -219,29 +217,6 @@ func (s *Scheme) Convert(in, out interface{}) error { return s.raw.Convert(in, out) } -// FindTypeMeta takes an arbitary api type, returns pointer to its TypeMeta field. -// obj must be a pointer to an api type. -func FindTypeMeta(obj Object) (TypeMetaInterface, error) { - v, err := enforcePtr(obj) - if err != nil { - return nil, err - } - t := v.Type() - name := t.Name() - if v.Kind() != reflect.Struct { - return nil, fmt.Errorf("expected struct, but got %v: %v (%#v)", v.Kind(), name, v.Interface()) - } - jsonBase := v.FieldByName("TypeMeta") - if !jsonBase.IsValid() { - return nil, fmt.Errorf("struct %v lacks embedded JSON type", name) - } - g, err := newGenericTypeMeta(jsonBase) - if err != nil { - return nil, err - } - return g, nil -} - // EncodeToVersion turns the given api object into an appropriate JSON string. // Will return an error if the object doesn't have an embedded TypeMeta. // Obj may be a pointer to a struct, or a struct. If a struct, a copy @@ -274,33 +249,6 @@ func (s *Scheme) EncodeToVersion(obj Object, destVersion string) (data []byte, e 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. -func enforcePtr(obj Object) (reflect.Value, error) { - v := reflect.ValueOf(obj) - if v.Kind() != reflect.Ptr { - return reflect.Value{}, fmt.Errorf("expected pointer, but got %v", v.Type().Name()) - } - return v.Elem(), nil -} - -// VersionAndKind will return the APIVersion and Kind of the given wire-format -// enconding of an APIObject, or an error. -func VersionAndKind(data []byte) (version, kind string, err error) { - findKind := struct { - Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` - APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` - }{} - // yaml is a superset of json, so we use it to decode here. That way, - // we understand both. - err = yaml.Unmarshal(data, &findKind) - if err != nil { - return "", "", fmt.Errorf("couldn't get version/kind: %v", err) - } - return findKind.APIVersion, findKind.Kind, nil -} - // Decode converts a YAML or JSON string back into a pointer to an api object. // Deduces the type based upon the APIVersion and Kind fields, which are set // by Encode. Only versioned objects (APIVersion != "") are accepted. The object @@ -339,28 +287,3 @@ func (s *Scheme) CopyOrDie(obj Object) Object { } return newObj } - -// metaInsertion implements conversion.MetaInsertionFactory, which lets the conversion -// package figure out how to encode our object's types and versions. These fields are -// located in our TypeMeta. -type metaInsertion struct { - TypeMeta struct { - APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` - Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` - } `json:",inline" yaml:",inline"` -} - -// Create returns a new metaInsertion with the version and kind fields set. -func (metaInsertion) Create(version, kind string) interface{} { - m := metaInsertion{} - m.TypeMeta.APIVersion = version - m.TypeMeta.Kind = kind - return &m -} - -// Interpret returns the version and kind information from in, which must be -// a metaInsertion pointer object. -func (metaInsertion) Interpret(in interface{}) (version, kind string) { - m := in.(*metaInsertion) - return m.TypeMeta.APIVersion, m.TypeMeta.Kind -} diff --git a/pkg/runtime/jsonbase.go b/pkg/runtime/typemeta.go similarity index 86% rename from pkg/runtime/jsonbase.go rename to pkg/runtime/typemeta.go index f6f2af174b6..0cecbbb5ec0 100644 --- a/pkg/runtime/jsonbase.go +++ b/pkg/runtime/typemeta.go @@ -19,8 +19,33 @@ package runtime import ( "fmt" "reflect" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" ) +// FindTypeMeta takes an arbitary api type, returns pointer to its TypeMeta field. +// obj must be a pointer to an api type. +func FindTypeMeta(obj Object) (TypeMetaInterface, error) { + v, err := conversion.EnforcePtr(obj) + if err != nil { + return nil, err + } + t := v.Type() + name := t.Name() + if v.Kind() != reflect.Struct { + return nil, fmt.Errorf("expected struct, but got %v: %v (%#v)", v.Kind(), name, v.Interface()) + } + typeMeta := v.FieldByName("TypeMeta") + if !typeMeta.IsValid() { + return nil, fmt.Errorf("struct %v lacks embedded JSON type", name) + } + g, err := newGenericTypeMeta(typeMeta) + if err != nil { + return nil, err + } + return g, nil +} + // NewTypeMetaResourceVersioner returns a ResourceVersioner that can set or // retrieve ResourceVersion on objects derived from TypeMeta. func NewTypeMetaResourceVersioner() ResourceVersioner { diff --git a/pkg/runtime/jsonbase_test.go b/pkg/runtime/typemeta_test.go similarity index 100% rename from pkg/runtime/jsonbase_test.go rename to pkg/runtime/typemeta_test.go