diff --git a/pkg/api/v1beta1/conversion.go b/pkg/api/v1beta1/conversion.go index 20c42e999a3..a56fc2ddcab 100644 --- a/pkg/api/v1beta1/conversion.go +++ b/pkg/api/v1beta1/conversion.go @@ -19,22 +19,20 @@ package v1beta1 import ( // Alias this so it can be easily changed when we cut the next version. newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) func init() { - // Shortcut for sub-conversions. TODO: This should possibly be refactored - // such that this convert function is passed to each conversion func. - Convert := runtime.Convert runtime.AddConversionFuncs( // EnvVar's Key is deprecated in favor of Name. - func(in *newer.EnvVar, out *EnvVar) error { + func(in *newer.EnvVar, out *EnvVar, s conversion.Scope) error { out.Value = in.Value out.Key = in.Name out.Name = in.Name return nil }, - func(in *EnvVar, out *newer.EnvVar) error { + func(in *EnvVar, out *newer.EnvVar, s conversion.Scope) error { out.Value = in.Value if in.Name != "" { out.Name = in.Name @@ -45,7 +43,7 @@ func init() { }, // Path & MountType are deprecated. - func(in *newer.VolumeMount, out *VolumeMount) error { + func(in *newer.VolumeMount, out *VolumeMount, s conversion.Scope) error { out.Name = in.Name out.ReadOnly = in.ReadOnly out.MountPath = in.MountPath @@ -53,7 +51,7 @@ func init() { out.MountType = "" // MountType is ignored. return nil }, - func(in *VolumeMount, out *newer.VolumeMount) error { + func(in *VolumeMount, out *newer.VolumeMount, s conversion.Scope) error { out.Name = in.Name out.ReadOnly = in.ReadOnly if in.MountPath == "" { @@ -65,18 +63,18 @@ func init() { }, // MinionList.Items had a wrong name in v1beta1 - func(in *newer.MinionList, out *MinionList) error { - Convert(&in.JSONBase, &out.JSONBase) - Convert(&in.Items, &out.Items) + func(in *newer.MinionList, out *MinionList, s conversion.Scope) error { + s.Convert(&in.JSONBase, &out.JSONBase, 0) + s.Convert(&in.Items, &out.Items, 0) out.Minions = out.Items return nil }, - func(in *MinionList, out *newer.MinionList) error { - Convert(&in.JSONBase, &out.JSONBase) + func(in *MinionList, out *newer.MinionList, s conversion.Scope) error { + s.Convert(&in.JSONBase, &out.JSONBase, 0) if len(in.Items) == 0 { - Convert(&in.Minions, &out.Items) + s.Convert(&in.Minions, &out.Items, 0) } else { - Convert(&in.Items, &out.Items) + s.Convert(&in.Items, &out.Items, 0) } return nil }, diff --git a/pkg/conversion/converter.go b/pkg/conversion/converter.go index 32cb04da971..b5f7f93c9d5 100644 --- a/pkg/conversion/converter.go +++ b/pkg/conversion/converter.go @@ -37,7 +37,7 @@ type Converter struct { // do the conversion. funcs map[typePair]reflect.Value - // If true, print helpful debugging info. Quite verbose. + // If non-nil, will be called to print helpful debugging info. Quite verbose. Debug DebugLogger } @@ -48,29 +48,43 @@ func NewConverter() *Converter { } } +// Scope is passed to conversion funcs to allow them to continue an ongoing conversion. +// If multiple converters exist in the system, Scope will allow you to use the correct one +// from a conversion function--that is, the one your conversion function was called by. +type Scope interface { + // Call Convert to convert sub-objects. Note that if you call it with your own exact + // parameters, you'll run out of stack space before anything useful happens. + Convert(src, dest interface{}, flags FieldMatchingFlags) error +} + // 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. +// three parameters: a pointer to the input type, a pointer to the output type, and +// a conversion.Scope (which should be used if recursive conversion calls are desired). +// It must return an error. // // Example: -// c.Register(func(in *Pod, out *v1beta1.Pod) error { ... return nil }) +// c.Register(func(in *Pod, out *v1beta1.Pod, s Scope) 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.NumIn() != 3 { + return fmt.Errorf("expected three 'in' params, got: %v", ft) } if ft.NumOut() != 1 { - return fmt.Errorf("expected one out param, got: %v", ft) + 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) + 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) + return fmt.Errorf("expected pointer arg for 'in' param 1, got: %v", ft) + } + scopeType := Scope(c) + if e, a := reflect.TypeOf(&scopeType).Elem(), ft.In(2); e != a { + return fmt.Errorf("expected '%v' arg for 'in' param 2, got '%v' (%v)", e, a, ft) } var forErrorType error // This convolution is necessary, otherwise TypeOf picks up on the fact @@ -138,7 +152,8 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro 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() + args := []reflect.Value{sv.Addr(), dv.Addr(), reflect.ValueOf(Scope(c))} + ret := fv.Call(args)[0].Interface() // This convolution is necssary because nil interfaces won't convert // to errors. if ret == nil { diff --git a/pkg/conversion/converter_test.go b/pkg/conversion/converter_test.go index 8e33e7e94e9..ca2b2c657d2 100644 --- a/pkg/conversion/converter_test.go +++ b/pkg/conversion/converter_test.go @@ -27,28 +27,30 @@ import ( func TestConverter_CallsRegisteredFunctions(t *testing.T) { type A struct { Foo string + Baz int } type B struct { Bar string + Baz int } type C struct{} c := NewConverter() - err := c.Register(func(in *A, out *B) error { + err := c.Register(func(in *A, out *B, s Scope) error { out.Bar = in.Foo - return nil + return s.Convert(&in.Baz, &out.Baz, 0) }) if err != nil { t.Fatalf("unexpected error %v", err) } - err = c.Register(func(in *B, out *A) error { + err = c.Register(func(in *B, out *A, s Scope) error { out.Foo = in.Bar - return nil + return s.Convert(&in.Baz, &out.Baz, 0) }) if err != nil { t.Fatalf("unexpected error %v", err) } - x := A{"hello, intrepid test reader!"} + x := A{"hello, intrepid test reader!", 3} y := B{} err = c.Convert(&x, &y, 0) @@ -58,8 +60,11 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) { if e, a := x.Foo, y.Bar; e != a { t.Errorf("expected %v, got %v", e, a) } + if e, a := x.Baz, y.Baz; e != a { + t.Errorf("expected %v, got %v", e, a) + } - z := B{"all your test are belong to us"} + z := B{"all your test are belong to us", 42} w := A{} err = c.Convert(&z, &w, 0) @@ -69,8 +74,11 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) { if e, a := z.Bar, w.Foo; e != a { t.Errorf("expected %v, got %v", e, a) } + if e, a := z.Baz, w.Baz; e != a { + t.Errorf("expected %v, got %v", e, a) + } - err = c.Register(func(in *A, out *C) error { + err = c.Register(func(in *A, out *C, s Scope) error { return fmt.Errorf("C can't store an A, silly") }) if err != nil {