mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-30 21:30:16 +00:00 
			
		
		
		
	Separate generic parts of api library into conversion package.
This commit is contained in:
		
							
								
								
									
										235
									
								
								pkg/conversion/converter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								pkg/conversion/converter.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,235 @@ | |||||||
|  | /* | ||||||
|  | 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" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type typePair struct { | ||||||
|  | 	source reflect.Type | ||||||
|  | 	dest   reflect.Type | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DebugLogger allows you to get debugging messages if necessary. | ||||||
|  | type DebugLogger interface { | ||||||
|  | 	Logf(format string, args ...interface{}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Converter knows how to convert one type to another. | ||||||
|  | type Converter struct { | ||||||
|  | 	// Map from the conversion pair to a function which can | ||||||
|  | 	// do the conversion. | ||||||
|  | 	funcs map[typePair]reflect.Value | ||||||
|  |  | ||||||
|  | 	// If true, print helpful debugging info. Quite verbose. | ||||||
|  | 	Debug DebugLogger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewConverter makes a new Converter object. | ||||||
|  | func NewConverter() *Converter { | ||||||
|  | 	return &Converter{ | ||||||
|  | 		funcs: map[typePair]reflect.Value{}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Register registers a conversion func with the Converter. conversionFunc must take | ||||||
|  | // two parameters, the input and output type. It must take a pointer to each. It must | ||||||
|  | // return an error. | ||||||
|  | // | ||||||
|  | // Example: | ||||||
|  | // c.Register(func(in *Pod, out *v1beta1.Pod) error { ... return nil }) | ||||||
|  | func (c *Converter) Register(conversionFunc interface{}) error { | ||||||
|  | 	fv := reflect.ValueOf(conversionFunc) | ||||||
|  | 	ft := fv.Type() | ||||||
|  | 	if ft.Kind() != reflect.Func { | ||||||
|  | 		return fmt.Errorf("expected func, got: %v", ft) | ||||||
|  | 	} | ||||||
|  | 	if ft.NumIn() != 2 { | ||||||
|  | 		return fmt.Errorf("expected two in params, got: %v", ft) | ||||||
|  | 	} | ||||||
|  | 	if ft.NumOut() != 1 { | ||||||
|  | 		return fmt.Errorf("expected one out param, got: %v", ft) | ||||||
|  | 	} | ||||||
|  | 	if ft.In(0).Kind() != reflect.Ptr { | ||||||
|  | 		return fmt.Errorf("expected pointer arg for in param 0, got: %v", ft) | ||||||
|  | 	} | ||||||
|  | 	if ft.In(1).Kind() != reflect.Ptr { | ||||||
|  | 		return fmt.Errorf("expected pointer arg for in param 1, got: %v", ft) | ||||||
|  | 	} | ||||||
|  | 	var forErrorType error | ||||||
|  | 	// This convolution is necessary, otherwise TypeOf picks up on the fact | ||||||
|  | 	// that forErrorType is nil. | ||||||
|  | 	errorType := reflect.TypeOf(&forErrorType).Elem() | ||||||
|  | 	if ft.Out(0) != errorType { | ||||||
|  | 		return fmt.Errorf("expected error return, got: %v", ft) | ||||||
|  | 	} | ||||||
|  | 	c.funcs[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // FieldMatchingType contains a list of ways in which struct fields could be | ||||||
|  | // copied. These constants may be | combined. | ||||||
|  | type FieldMatchingFlags int | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// Loop through source fields, search for matching dest field | ||||||
|  | 	// to copy it into. Destination fields with no corresponding | ||||||
|  | 	// source field will be ignored. | ||||||
|  | 	SourceToDest FieldMatchingFlags = 1 << iota | ||||||
|  | 	// Loop through destiation fields, search for matching source | ||||||
|  | 	// field to copy it from. Source fields with no corresponding | ||||||
|  | 	// destination field will be ignored. If SourceToDest is | ||||||
|  | 	// specified, this flag is ignored. If niether is specified, | ||||||
|  | 	// this flag is the default. | ||||||
|  | 	DestFromSource | ||||||
|  | 	// Don't treat it as an error if the corresponding source or | ||||||
|  | 	// dest field can't be found. | ||||||
|  | 	IgnoreMissingFields | ||||||
|  | 	// Don't require type names to match. | ||||||
|  | 	AllowDifferentFieldNames | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Returns true if the given flag or combination of flags is set. | ||||||
|  | func (f FieldMatchingFlags) IsSet(flag FieldMatchingFlags) bool { | ||||||
|  | 	return f&flag == flag | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Convert will translate src to dest if it knows how. Both must be pointers. | ||||||
|  | // If no conversion func is registered and the default copying mechanism | ||||||
|  | // doesn't work on this type pair, an error will be returned. | ||||||
|  | // Not safe for objects with cyclic references! | ||||||
|  | func (c *Converter) Convert(src, dest interface{}, flags FieldMatchingFlags) error { | ||||||
|  | 	dv, sv := reflect.ValueOf(dest), reflect.ValueOf(src) | ||||||
|  | 	if dv.Kind() != reflect.Ptr { | ||||||
|  | 		return fmt.Errorf("Need pointer, but got %#v", dest) | ||||||
|  | 	} | ||||||
|  | 	if sv.Kind() != reflect.Ptr { | ||||||
|  | 		return fmt.Errorf("Need pointer, but got %#v", src) | ||||||
|  | 	} | ||||||
|  | 	dv = dv.Elem() | ||||||
|  | 	sv = sv.Elem() | ||||||
|  | 	if !dv.CanAddr() { | ||||||
|  | 		return fmt.Errorf("Can't write to dest") | ||||||
|  | 	} | ||||||
|  | 	return c.convert(sv, dv, flags) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // convert recursively copies sv into dv, calling an appropriate conversion function if | ||||||
|  | // one is registered. | ||||||
|  | func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) error { | ||||||
|  | 	dt, st := dv.Type(), sv.Type() | ||||||
|  | 	if fv, ok := c.funcs[typePair{st, dt}]; ok { | ||||||
|  | 		if c.Debug != nil { | ||||||
|  | 			c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt) | ||||||
|  | 		} | ||||||
|  | 		ret := fv.Call([]reflect.Value{sv.Addr(), dv.Addr()})[0].Interface() | ||||||
|  | 		// This convolution is necssary because nil interfaces won't convert | ||||||
|  | 		// to errors. | ||||||
|  | 		if ret == nil { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		return ret.(error) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !flags.IsSet(AllowDifferentFieldNames) && dt.Name() != st.Name() { | ||||||
|  | 		return fmt.Errorf("Can't convert %v to %v because type names don't match.", st, dt) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// This should handle all simple types. | ||||||
|  | 	if st.AssignableTo(dt) { | ||||||
|  | 		dv.Set(sv) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	if st.ConvertibleTo(dt) { | ||||||
|  | 		dv.Set(sv.Convert(dt)) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if c.Debug != nil { | ||||||
|  | 		c.Debug.Logf("Trying to convert '%v' to '%v'", st, dt) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch dv.Kind() { | ||||||
|  | 	case reflect.Struct: | ||||||
|  | 		listType := dt | ||||||
|  | 		if flags.IsSet(SourceToDest) { | ||||||
|  | 			listType = st | ||||||
|  | 		} | ||||||
|  | 		for i := 0; i < listType.NumField(); i++ { | ||||||
|  | 			f := listType.Field(i) | ||||||
|  | 			df := dv.FieldByName(f.Name) | ||||||
|  | 			sf := sv.FieldByName(f.Name) | ||||||
|  | 			if !df.IsValid() || !sf.IsValid() { | ||||||
|  | 				switch { | ||||||
|  | 				case flags.IsSet(IgnoreMissingFields): | ||||||
|  | 					// No error. | ||||||
|  | 				case flags.IsSet(SourceToDest): | ||||||
|  | 					return fmt.Errorf("%v not present in dest (%v to %v)", f.Name, st, dt) | ||||||
|  | 				default: | ||||||
|  | 					return fmt.Errorf("%v not present in src (%v to %v)", f.Name, st, dt) | ||||||
|  | 				} | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			if err := c.convert(sf, df, flags); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case reflect.Slice: | ||||||
|  | 		if sv.IsNil() { | ||||||
|  | 			// Don't make a zero-length slice. | ||||||
|  | 			dv.Set(reflect.Zero(dt)) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		dv.Set(reflect.MakeSlice(dt, sv.Len(), sv.Cap())) | ||||||
|  | 		for i := 0; i < sv.Len(); i++ { | ||||||
|  | 			if err := c.convert(sv.Index(i), dv.Index(i), flags); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case reflect.Ptr: | ||||||
|  | 		if sv.IsNil() { | ||||||
|  | 			// Don't copy a nil ptr! | ||||||
|  | 			dv.Set(reflect.Zero(dt)) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		dv.Set(reflect.New(dt.Elem())) | ||||||
|  | 		return c.convert(sv.Elem(), dv.Elem(), flags) | ||||||
|  | 	case reflect.Map: | ||||||
|  | 		if sv.IsNil() { | ||||||
|  | 			// Don't copy a nil ptr! | ||||||
|  | 			dv.Set(reflect.Zero(dt)) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		dv.Set(reflect.MakeMap(dt)) | ||||||
|  | 		for _, sk := range sv.MapKeys() { | ||||||
|  | 			dk := reflect.New(dt.Key()).Elem() | ||||||
|  | 			if err := c.convert(sk, dk, flags); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			dkv := reflect.New(dt.Elem()).Elem() | ||||||
|  | 			if err := c.convert(sv.MapIndex(sk), dkv, flags); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			dv.SetMapIndex(dk, dkv) | ||||||
|  | 		} | ||||||
|  | 	default: | ||||||
|  | 		return fmt.Errorf("Couldn't copy '%v' into '%v'", st, dt) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										81
									
								
								pkg/conversion/converter_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								pkg/conversion/converter_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | /* | ||||||
|  | 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" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestConverter(t *testing.T) { | ||||||
|  | 	type A struct { | ||||||
|  | 		Foo string | ||||||
|  | 	} | ||||||
|  | 	type B struct { | ||||||
|  | 		Bar string | ||||||
|  | 	} | ||||||
|  | 	type C struct{} | ||||||
|  | 	c := NewConverter() | ||||||
|  | 	err := c.Register(func(in *A, out *B) error { | ||||||
|  | 		out.Bar = in.Foo | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error %v", err) | ||||||
|  | 	} | ||||||
|  | 	err = c.Register(func(in *B, out *A) error { | ||||||
|  | 		out.Foo = in.Bar | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	x := A{"hello, intrepid test reader!"} | ||||||
|  | 	y := B{} | ||||||
|  |  | ||||||
|  | 	err = c.Convert(&x, &y, 0) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error %v", err) | ||||||
|  | 	} | ||||||
|  | 	if e, a := x.Foo, y.Bar; e != a { | ||||||
|  | 		t.Errorf("expected %v, got %v", e, a) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	z := B{"all your test are belong to us"} | ||||||
|  | 	w := A{} | ||||||
|  |  | ||||||
|  | 	err = c.Convert(&z, &w, 0) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error %v", err) | ||||||
|  | 	} | ||||||
|  | 	if e, a := z.Bar, w.Foo; e != a { | ||||||
|  | 		t.Errorf("expected %v, got %v", e, a) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = c.Register(func(in *A, out *C) error { | ||||||
|  | 		return fmt.Errorf("C can't store an A, silly") | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = c.Convert(&A{}, &C{}, 0) | ||||||
|  | 	if err == nil { | ||||||
|  | 		t.Errorf("unexpected non-error") | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										129
									
								
								pkg/conversion/decode.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								pkg/conversion/decode.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | |||||||
|  | /* | ||||||
|  | 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" | ||||||
|  |  | ||||||
|  | 	"gopkg.in/v1/yaml" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Decode converts a YAML or JSON string back into a pointer to an api object. | ||||||
|  | // Deduces the type based upon the fields added by the MetaInsertionFactory | ||||||
|  | // technique. The object will be converted, if necessary, into the | ||||||
|  | // s.InternalVersion type before being returned. Decode will refuse to decode | ||||||
|  | // objects without a version, because that's probably an error. | ||||||
|  | func (s *Scheme) Decode(data []byte) (interface{}, error) { | ||||||
|  | 	version, kind, err := s.DataAPIVersionAndKind(data) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if version == "" { | ||||||
|  | 		return nil, fmt.Errorf("API Version not set in '%s'", string(data)) | ||||||
|  | 	} | ||||||
|  | 	obj, err := s.NewObject(version, kind) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	// yaml is a superset of json, so we use it to decode here. That way, | ||||||
|  | 	// we understand both. | ||||||
|  | 	err = yaml.Unmarshal(data, obj) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Version and Kind should be blank in memory. | ||||||
|  | 	blankVersionAndKind := s.MetaInsertionFactory.Create("", "") | ||||||
|  | 	err = s.converter.Convert(blankVersionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldNames) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Convert if needed. | ||||||
|  | 	if s.InternalVersion != version { | ||||||
|  | 		objOut, err := s.NewObject(s.InternalVersion, kind) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		err = s.converter.Convert(obj, objOut, 0) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		obj = objOut | ||||||
|  | 	} | ||||||
|  | 	return obj, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DecodeInto parses a YAML or JSON string and stores it in obj. Returns an error | ||||||
|  | // if data.Kind is set and doesn't match the type of obj. Obj should be a | ||||||
|  | // pointer to an api type. | ||||||
|  | // If obj's APIVersion doesn't match that in data, an attempt will be made to convert | ||||||
|  | // data into obj's version. | ||||||
|  | func (s *Scheme) DecodeInto(data []byte, obj interface{}) error { | ||||||
|  | 	dataVersion, dataKind, err := s.DataAPIVersionAndKind(data) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	objVersion, objKind, err := s.ObjectAPIVersionAndKind(obj) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if dataKind == "" { | ||||||
|  | 		// Assume objects with unset Kind fields are being unmarshalled into the | ||||||
|  | 		// correct type. | ||||||
|  | 		dataKind = objKind | ||||||
|  | 	} | ||||||
|  | 	if dataKind != objKind { | ||||||
|  | 		return fmt.Errorf("data of kind '%v', obj of type '%v'", dataKind, objKind) | ||||||
|  | 	} | ||||||
|  | 	if dataVersion == "" { | ||||||
|  | 		// Assume objects with unset Version fields are being unmarshalled into the | ||||||
|  | 		// correct type. | ||||||
|  | 		dataVersion = objVersion | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if objVersion == dataVersion { | ||||||
|  | 		// Easy case! | ||||||
|  | 		err = yaml.Unmarshal(data, obj) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		external, err := s.NewObject(dataVersion, dataKind) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("Unable to create new object of type ('%s', '%s')", dataVersion, dataKind) | ||||||
|  | 		} | ||||||
|  | 		// yaml is a superset of json, so we use it to decode here. That way, | ||||||
|  | 		// we understand both. | ||||||
|  | 		err = yaml.Unmarshal(data, external) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		err = s.converter.Convert(external, obj, 0) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Version and Kind should be blank in memory. | ||||||
|  | 	blankVersionAndKind := s.MetaInsertionFactory.Create("", "") | ||||||
|  | 	err = s.converter.Convert(blankVersionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldNames) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										136
									
								
								pkg/conversion/encode.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								pkg/conversion/encode.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | |||||||
|  | /* | ||||||
|  | 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 ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests. | ||||||
|  | func (s *Scheme) EncodeOrDie(obj interface{}) string { | ||||||
|  | 	bytes, err := s.Encode(obj) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return string(bytes) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Encode turns the given api object into an appropriate JSON string. | ||||||
|  | // Obj may be a pointer to a struct, or a struct. If a struct, a copy | ||||||
|  | // will be made, therefore it's recommended to pass a pointer to a | ||||||
|  | // struct. The type must have been registered. | ||||||
|  | // | ||||||
|  | // Memory/wire format differences: | ||||||
|  | //  * Having to keep track of the Kind and APIVersion fields makes tests | ||||||
|  | //    very annoying, so the rule is that they are set only in wire format | ||||||
|  | //    (json), not when in native (memory) format. This is possible because | ||||||
|  | //    both pieces of information are implicit in the go typed object. | ||||||
|  | //     * An exception: note that, if there are embedded API objects of known | ||||||
|  | //       type, for example, PodList{... Items []Pod ...}, these embedded | ||||||
|  | //       objects must be of the same version of the object they are embedded | ||||||
|  | //       within, and their APIVersion and Kind must both be empty. | ||||||
|  | //     * Note that the exception does not apply to the APIObject type, which | ||||||
|  | //       recursively does Encode()/Decode(), and is capable of expressing any | ||||||
|  | //       API object. | ||||||
|  | //  * Only versioned objects should be encoded. This means that, if you pass | ||||||
|  | //    a native object, Encode will convert it to a versioned object. For | ||||||
|  | //    example, an api.Pod will get converted to a v1beta1.Pod. However, if | ||||||
|  | //    you pass in an object that's already versioned (v1beta1.Pod), Encode | ||||||
|  | //    will not modify it. | ||||||
|  | // | ||||||
|  | // The purpose of the above complex conversion behavior is to allow us to | ||||||
|  | // change the memory format yet not break compatibility with any stored | ||||||
|  | // objects, whether they be in our storage layer (e.g., etcd), or in user's | ||||||
|  | // config files. | ||||||
|  | // | ||||||
|  | func (s *Scheme) Encode(obj interface{}) (data []byte, err error) { | ||||||
|  | 	return s.EncodeToVersion(obj, s.ExternalVersion) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // EncodeToVersion is like Encode, but you may choose the version. | ||||||
|  | func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []byte, err error) { | ||||||
|  | 	obj = maybeCopy(obj) | ||||||
|  | 	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()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	objVersion, objKind, err := s.ObjectAPIVersionAndKind(obj) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Perform a conversion if necessary. | ||||||
|  | 	if objVersion != destVersion { | ||||||
|  | 		objOut, err := s.NewObject(destVersion, objKind) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		err = s.converter.Convert(obj, objOut, 0) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		obj = objOut | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Version and Kind should be set on the wire. | ||||||
|  | 	setVersionAndKind := s.MetaInsertionFactory.Create(destVersion, objKind) | ||||||
|  | 	err = s.converter.Convert(setVersionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldNames) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// To add metadata, do some simple surgery on the JSON. | ||||||
|  | 	data, err = json.Marshal(obj) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Version and Kind should be blank in memory. | ||||||
|  | 	blankVersionAndKind := s.MetaInsertionFactory.Create("", "") | ||||||
|  | 	err = s.converter.Convert(blankVersionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldNames) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return data, nil | ||||||
|  |  | ||||||
|  | 	meta, err := json.Marshal(s.MetaInsertionFactory.Create(destVersion, objKind)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	// Stick these together, omitting the last } from meta and the first { from | ||||||
|  | 	// data. Add a comma to meta if necessary. | ||||||
|  | 	metaN := len(meta) | ||||||
|  | 	if len(data) > 2 { | ||||||
|  | 		meta[metaN-1] = ',' // Add comma | ||||||
|  | 	} else { | ||||||
|  | 		meta = meta[:metaN-1] // Just remove } | ||||||
|  | 	} | ||||||
|  | 	together := append(meta, data[1:]...) | ||||||
|  | 	if s.Indent { | ||||||
|  | 		var out bytes.Buffer | ||||||
|  | 		err := json.Indent(&out, together, "", "	") | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return out.Bytes(), nil | ||||||
|  | 	} | ||||||
|  | 	return together, nil | ||||||
|  | } | ||||||
							
								
								
									
										223
									
								
								pkg/conversion/scheme.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								pkg/conversion/scheme.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,223 @@ | |||||||
|  | /* | ||||||
|  | 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" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // MetaInsertionFactory is used to create an object to store and retrieve | ||||||
|  | // the version and kind information for all objects. The default uses the | ||||||
|  | // keys "apiVersion" 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(apiVersion, 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{}) (apiVersion, kind string) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Default is a global scheme. | ||||||
|  | var Default = NewScheme() | ||||||
|  |  | ||||||
|  | // Scheme defines an entire encoding and decoding scheme. | ||||||
|  | type Scheme struct { | ||||||
|  | 	// versionMap allows one to figure out the go type of an object with | ||||||
|  | 	// the given version and name. | ||||||
|  | 	versionMap map[string]map[string]reflect.Type | ||||||
|  |  | ||||||
|  | 	// typeToVersion allows one to figure out the version for a given go object. | ||||||
|  | 	// The reflect.Type we index by should *not* be a pointer. If the same type | ||||||
|  | 	// is registered for multiple versions, the last one wins. | ||||||
|  | 	typeToVersion map[reflect.Type]string | ||||||
|  |  | ||||||
|  | 	// converter stores all registered conversion functions. It also has | ||||||
|  | 	// default coverting behavior. | ||||||
|  | 	converter *Converter | ||||||
|  |  | ||||||
|  | 	// Indent will cause the JSON output from Encode to be indented, iff it is true. | ||||||
|  | 	Indent bool | ||||||
|  |  | ||||||
|  | 	// InternalVersion is the default internal version. It is recommended that | ||||||
|  | 	// you use "" for the internal version. | ||||||
|  | 	InternalVersion string | ||||||
|  |  | ||||||
|  | 	// ExternalVersion is the default external version. | ||||||
|  | 	ExternalVersion string | ||||||
|  |  | ||||||
|  | 	// MetaInsertionFactory is used to create an object to store and retrieve | ||||||
|  | 	// the version and kind information for all objects. The default uses the | ||||||
|  | 	// keys "apiVersion" and "kind" respectively. | ||||||
|  | 	MetaInsertionFactory MetaInsertionFactory | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewScheme manufactures a new scheme. | ||||||
|  | func NewScheme() *Scheme { | ||||||
|  | 	return &Scheme{ | ||||||
|  | 		versionMap:           map[string]map[string]reflect.Type{}, | ||||||
|  | 		typeToVersion:        map[reflect.Type]string{}, | ||||||
|  | 		converter:            NewConverter(), | ||||||
|  | 		InternalVersion:      "", | ||||||
|  | 		ExternalVersion:      "v1", | ||||||
|  | 		MetaInsertionFactory: metaInsertion{}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AddKnownTypes registers the types of the arguments to the marshaller of the package api. | ||||||
|  | // Encode() refuses the object unless its type is registered with AddKnownTypes. | ||||||
|  | func (s *Scheme) AddKnownTypes(version string, types ...interface{}) { | ||||||
|  | 	knownTypes, found := s.versionMap[version] | ||||||
|  | 	if !found { | ||||||
|  | 		knownTypes = map[string]reflect.Type{} | ||||||
|  | 		s.versionMap[version] = knownTypes | ||||||
|  | 	} | ||||||
|  | 	for _, obj := range types { | ||||||
|  | 		t := reflect.TypeOf(obj) | ||||||
|  | 		if t.Kind() != reflect.Struct { | ||||||
|  | 			panic("All types must be structs.") | ||||||
|  | 		} | ||||||
|  | 		knownTypes[t.Name()] = t | ||||||
|  | 		s.typeToVersion[t] = version | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewObject returns a new object of the given version and name, | ||||||
|  | // or an error if it hasn't been registered. | ||||||
|  | func (s *Scheme) NewObject(versionName, typeName string) (interface{}, error) { | ||||||
|  | 	if types, ok := s.versionMap[versionName]; ok { | ||||||
|  | 		if t, ok := types[typeName]; ok { | ||||||
|  | 			return reflect.New(t).Interface(), nil | ||||||
|  | 		} | ||||||
|  | 		return nil, fmt.Errorf("No type '%v' for version '%v'", typeName, versionName) | ||||||
|  | 	} | ||||||
|  | 	return nil, fmt.Errorf("No version '%v'", versionName) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AddConversionFuncs adds functions to the list of conversion functions. The given | ||||||
|  | // functions should know how to convert between two API objects. We deduce how to call | ||||||
|  | // it from the types of its two parameters; see the comment for Converter.Register. | ||||||
|  | // | ||||||
|  | // Note that, if you need to copy sub-objects that didn't change, it's safe to call | ||||||
|  | // Convert() inside your conversionFuncs, as long as you don't start a conversion | ||||||
|  | // chain that's infinitely recursive. | ||||||
|  | // | ||||||
|  | // Also note that the default behavior, if you don't add a conversion function, is to | ||||||
|  | // sanely copy fields that have the same names. It's OK if the destination type has | ||||||
|  | // extra fields, but it must not remove any. So you only need to add a conversion | ||||||
|  | // function for things with changed/removed fields. | ||||||
|  | func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error { | ||||||
|  | 	for _, f := range conversionFuncs { | ||||||
|  | 		err := s.converter.Register(f) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Convert will attempt to convert in into out. Both must be pointers. | ||||||
|  | // For easy testing of conversion functions. Returns an error if the conversion isn't | ||||||
|  | // possible. | ||||||
|  | func (s *Scheme) Convert(in, out interface{}) error { | ||||||
|  | 	return s.converter.Convert(in, out, 0) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // metaInsertion provides a default implementation of MetaInsertionFactory. | ||||||
|  | type metaInsertion struct { | ||||||
|  | 	JSONBase struct { | ||||||
|  | 		APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` | ||||||
|  | 		Kind       string `json:"kind,omitempty" yaml:"kind,omitempty"` | ||||||
|  | 	} `json:",inline" yaml:",inline"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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(apiVersion, kind string) interface{} { | ||||||
|  | 	m := metaInsertion{} | ||||||
|  | 	m.JSONBase.APIVersion = apiVersion | ||||||
|  | 	m.JSONBase.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{}) (apiVersion, kind string) { | ||||||
|  | 	m := in.(*metaInsertion) | ||||||
|  | 	return m.JSONBase.APIVersion, m.JSONBase.Kind | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DataAPIVersionAndKind will return the APIVersion and Kind of the given wire-format | ||||||
|  | // enconding of an API Object, or an error. | ||||||
|  | func (s *Scheme) DataAPIVersionAndKind(data []byte) (apiVersion, 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) | ||||||
|  | 	} | ||||||
|  | 	apiVersion, kind = s.MetaInsertionFactory.Interpret(findKind) | ||||||
|  | 	return apiVersion, kind, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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) ObjectAPIVersionAndKind(obj interface{}) (apiVersion, kind string, err error) { | ||||||
|  | 	v, err := enforcePtr(obj) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", "", err | ||||||
|  | 	} | ||||||
|  | 	t := v.Type() | ||||||
|  | 	if version, ok := s.typeToVersion[t]; !ok { | ||||||
|  | 		return "", "", fmt.Errorf("Unregistered type: %v", t) | ||||||
|  | 	} else { | ||||||
|  | 		return version, t.Name(), nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // maybeCopy copies obj if it is not a pointer, to get a settable/addressable | ||||||
|  | // object. Guaranteed to return a pointer. | ||||||
|  | func maybeCopy(obj interface{}) interface{} { | ||||||
|  | 	v := reflect.ValueOf(obj) | ||||||
|  | 	if v.Kind() == reflect.Ptr { | ||||||
|  | 		return obj | ||||||
|  | 	} | ||||||
|  | 	v2 := reflect.New(v.Type()) | ||||||
|  | 	v2.Elem().Set(v) | ||||||
|  | 	return v2.Interface() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
|  | } | ||||||
							
								
								
									
										248
									
								
								pkg/conversion/scheme_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								pkg/conversion/scheme_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,248 @@ | |||||||
|  | /* | ||||||
|  | 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 ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"flag" | ||||||
|  | 	"fmt" | ||||||
|  | 	"reflect" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var fuzzIters = flag.Int("fuzz_iters", 10, "How many fuzzing iterations to do.") | ||||||
|  |  | ||||||
|  | // Intended to be compatible with the default implementation of MetaInsertionFactory | ||||||
|  | type JSONBase struct { | ||||||
|  | 	ID         string `yaml:"ID,omitempty" json:"ID,omitempty"` | ||||||
|  | 	APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` | ||||||
|  | 	Kind       string `json:"kind,omitempty" yaml:"kind,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TestType1 struct { | ||||||
|  | 	JSONBase `json:",inline" yaml:",inline"` | ||||||
|  | 	A        string               `yaml:"A,omitempty" json:"A,omitempty"` | ||||||
|  | 	B        int                  `yaml:"B,omitempty" json:"B,omitempty"` | ||||||
|  | 	C        int8                 `yaml:"C,omitempty" json:"C,omitempty"` | ||||||
|  | 	D        int16                `yaml:"D,omitempty" json:"D,omitempty"` | ||||||
|  | 	E        int32                `yaml:"E,omitempty" json:"E,omitempty"` | ||||||
|  | 	F        int64                `yaml:"F,omitempty" json:"F,omitempty"` | ||||||
|  | 	G        uint                 `yaml:"G,omitempty" json:"G,omitempty"` | ||||||
|  | 	H        uint8                `yaml:"H,omitempty" json:"H,omitempty"` | ||||||
|  | 	I        uint16               `yaml:"I,omitempty" json:"I,omitempty"` | ||||||
|  | 	J        uint32               `yaml:"J,omitempty" json:"J,omitempty"` | ||||||
|  | 	K        uint64               `yaml:"K,omitempty" json:"K,omitempty"` | ||||||
|  | 	L        bool                 `yaml:"L,omitempty" json:"L,omitempty"` | ||||||
|  | 	M        map[string]int       `yaml:"M,omitempty" json:"M,omitempty"` | ||||||
|  | 	N        map[string]TestType2 `yaml:"N,omitempty" json:"N,omitempty"` | ||||||
|  | 	O        *TestType2           `yaml:"O,omitempty" json:"O,omitempty"` | ||||||
|  | 	P        []TestType2          `yaml:"Q,omitempty" json:"Q,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TestType2 struct { | ||||||
|  | 	A string `yaml:"A,omitempty" json:"A,omitempty"` | ||||||
|  | 	B int    `yaml:"B,omitempty" json:"B,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // We depend on the name of the external and internal types matching. Ordinarily, | ||||||
|  | // we'd accomplish this with an additional package, but since this is a test, we | ||||||
|  | // can just enclose stuff in a function to simulate that. | ||||||
|  | func externalTypeReturn() interface{} { | ||||||
|  | 	type TestType2 struct { | ||||||
|  | 		A string `yaml:"A,omitempty" json:"A,omitempty"` | ||||||
|  | 		B int    `yaml:"B,omitempty" json:"B,omitempty"` | ||||||
|  | 	} | ||||||
|  | 	type TestType1 struct { | ||||||
|  | 		JSONBase `json:",inline" yaml:",inline"` | ||||||
|  | 		A        string               `yaml:"A,omitempty" json:"A,omitempty"` | ||||||
|  | 		B        int                  `yaml:"B,omitempty" json:"B,omitempty"` | ||||||
|  | 		C        int8                 `yaml:"C,omitempty" json:"C,omitempty"` | ||||||
|  | 		D        int16                `yaml:"D,omitempty" json:"D,omitempty"` | ||||||
|  | 		E        int32                `yaml:"E,omitempty" json:"E,omitempty"` | ||||||
|  | 		F        int64                `yaml:"F,omitempty" json:"F,omitempty"` | ||||||
|  | 		G        uint                 `yaml:"G,omitempty" json:"G,omitempty"` | ||||||
|  | 		H        uint8                `yaml:"H,omitempty" json:"H,omitempty"` | ||||||
|  | 		I        uint16               `yaml:"I,omitempty" json:"I,omitempty"` | ||||||
|  | 		J        uint32               `yaml:"J,omitempty" json:"J,omitempty"` | ||||||
|  | 		K        uint64               `yaml:"K,omitempty" json:"K,omitempty"` | ||||||
|  | 		L        bool                 `yaml:"L,omitempty" json:"L,omitempty"` | ||||||
|  | 		M        map[string]int       `yaml:"M,omitempty" json:"M,omitempty"` | ||||||
|  | 		N        map[string]TestType2 `yaml:"N,omitempty" json:"N,omitempty"` | ||||||
|  | 		O        *TestType2           `yaml:"O,omitempty" json:"O,omitempty"` | ||||||
|  | 		P        []TestType2          `yaml:"Q,omitempty" json:"Q,omitempty"` | ||||||
|  | 	} | ||||||
|  | 	return TestType1{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ExternalInternalSame struct { | ||||||
|  | 	JSONBase `json:",inline" yaml:",inline"` | ||||||
|  | 	A        TestType2 `yaml:"A,omitempty" json:"A,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TestObjectFuzzer can randomly populate all the above objects. | ||||||
|  | var TestObjectFuzzer = util.NewFuzzer( | ||||||
|  | 	func(j *JSONBase) { | ||||||
|  | 		// We have to customize the randomization of JSONBases because their | ||||||
|  | 		// APIVersion and Kind must remain blank in memory. | ||||||
|  | 		j.APIVersion = "" | ||||||
|  | 		j.Kind = "" | ||||||
|  | 		j.ID = util.RandString() | ||||||
|  | 	}, | ||||||
|  | 	func(u *uint64) { | ||||||
|  | 		// TODO: Fix JSON/YAML packages and/or write custom encoding | ||||||
|  | 		// for uint64's. Somehow the LS *byte* of this is lost, but | ||||||
|  | 		// only when all 8 bytes are set. | ||||||
|  | 		*u = util.RandUint64() >> 8 | ||||||
|  | 	}, | ||||||
|  | 	func(u *uint) { | ||||||
|  | 		// TODO: Fix JSON/YAML packages and/or write custom encoding | ||||||
|  | 		// for uint64's. Somehow the LS *byte* of this is lost, but | ||||||
|  | 		// only when all 8 bytes are set. | ||||||
|  | 		*u = uint(util.RandUint64() >> 8) | ||||||
|  | 	}, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Returns a new Scheme set up with the test objects. | ||||||
|  | func GetTestScheme() *Scheme { | ||||||
|  | 	s := NewScheme() | ||||||
|  | 	s.AddKnownTypes("", TestType1{}, ExternalInternalSame{}) | ||||||
|  | 	s.AddKnownTypes("v1", externalTypeReturn(), ExternalInternalSame{}) | ||||||
|  | 	s.ExternalVersion = "v1" | ||||||
|  | 	s.InternalVersion = "" | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func objDiff(a, b interface{}) string { | ||||||
|  | 	ab, err := json.Marshal(a) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic("a") | ||||||
|  | 	} | ||||||
|  | 	bb, err := json.Marshal(b) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic("b") | ||||||
|  | 	} | ||||||
|  | 	return util.StringDiff(string(ab), string(bb)) | ||||||
|  |  | ||||||
|  | 	// An alternate diff attempt, in case json isn't showing you | ||||||
|  | 	// the difference. (reflect.DeepEqual makes a distinction between | ||||||
|  | 	// nil and empty slices, for example.) | ||||||
|  | 	return util.StringDiff( | ||||||
|  | 		fmt.Sprintf("%#v", a), | ||||||
|  | 		fmt.Sprintf("%#v", b), | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func runTest(t *testing.T, source interface{}) { | ||||||
|  | 	name := reflect.TypeOf(source).Elem().Name() | ||||||
|  | 	TestObjectFuzzer.Fuzz(source) | ||||||
|  |  | ||||||
|  | 	s := GetTestScheme() | ||||||
|  | 	data, err := s.Encode(source) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("%v: %v (%#v)", name, err, source) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	obj2, err := s.Decode(data) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("%v: %v (%v)", name, err, string(data)) | ||||||
|  | 		return | ||||||
|  | 	} else { | ||||||
|  | 		if !reflect.DeepEqual(source, obj2) { | ||||||
|  | 			t.Errorf("1: %v: diff: %v", name, objDiff(source, obj2)) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface() | ||||||
|  | 	err = s.DecodeInto(data, obj3) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("2: %v: %v", name, err) | ||||||
|  | 		return | ||||||
|  | 	} else { | ||||||
|  | 		if !reflect.DeepEqual(source, obj3) { | ||||||
|  | 			t.Errorf("3: %v: diff: %v", name, objDiff(source, obj3)) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestTypes(t *testing.T) { | ||||||
|  | 	table := []interface{}{ | ||||||
|  | 		&TestType1{}, | ||||||
|  | 		&ExternalInternalSame{}, | ||||||
|  | 	} | ||||||
|  | 	for _, item := range table { | ||||||
|  | 		// Try a few times, since runTest uses random values. | ||||||
|  | 		for i := 0; i < *fuzzIters; i++ { | ||||||
|  | 			runTest(t, item) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestEncode_NonPtr(t *testing.T) { | ||||||
|  | 	s := GetTestScheme() | ||||||
|  | 	tt := TestType1{A: "I'm not a pointer object"} | ||||||
|  | 	obj := interface{}(tt) | ||||||
|  | 	data, err := s.Encode(obj) | ||||||
|  | 	obj2, err2 := s.Decode(data) | ||||||
|  | 	if err != nil || err2 != nil { | ||||||
|  | 		t.Fatalf("Failure: '%v' '%v'", err, err2) | ||||||
|  | 	} | ||||||
|  | 	if _, ok := obj2.(*TestType1); !ok { | ||||||
|  | 		t.Fatalf("Got wrong type") | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(obj2, &tt) { | ||||||
|  | 		t.Errorf("Expected:\n %#v,\n Got:\n %#v", &tt, obj2) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestEncode_Ptr(t *testing.T) { | ||||||
|  | 	s := GetTestScheme() | ||||||
|  | 	tt := &TestType1{A: "I am a pointer object"} | ||||||
|  | 	obj := interface{}(tt) | ||||||
|  | 	data, err := s.Encode(obj) | ||||||
|  | 	obj2, err2 := s.Decode(data) | ||||||
|  | 	if err != nil || err2 != nil { | ||||||
|  | 		t.Fatalf("Failure: '%v' '%v'", err, err2) | ||||||
|  | 	} | ||||||
|  | 	if _, ok := obj2.(*TestType1); !ok { | ||||||
|  | 		t.Fatalf("Got wrong type") | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(obj2, tt) { | ||||||
|  | 		t.Errorf("Expected:\n %#v,\n Got:\n %#v", &tt, obj2) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestBadJSONRejection(t *testing.T) { | ||||||
|  | 	s := GetTestScheme() | ||||||
|  | 	badJSONs := [][]byte{ | ||||||
|  | 		[]byte(`{"apiVersion":"v1"}`),                     // Missing kind | ||||||
|  | 		[]byte(`{"kind":"TestType1"}`),                    // Missing version | ||||||
|  | 		[]byte(`{"apiVersion":"v1","kind":"bar"}`),        // Unknown kind | ||||||
|  | 		[]byte(`{"apiVersion":"bar","kind":"TestType1"}`), // Unknown version | ||||||
|  | 	} | ||||||
|  | 	for _, b := range badJSONs { | ||||||
|  | 		if _, err := s.Decode(b); err == nil { | ||||||
|  | 			t.Errorf("Did not reject bad json: %s", string(b)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	badJSONKindMismatch := []byte(`{"apiVersion":"v1","kind": "ExternalInternalSame"}`) | ||||||
|  | 	if err := s.DecodeInto(badJSONKindMismatch, &TestType1{}); err == nil { | ||||||
|  | 		t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch) | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user