Introduce GroupVersioner for capturing desired target version

Convert single GV and lists of GVs into an interface that can handle
more complex scenarios (everything internal, nothing supported). Pass
the interface down into conversion.
This commit is contained in:
Clayton Coleman
2016-05-22 19:10:42 -04:00
parent 9d2a5fe5e8
commit 12a5eeea17
24 changed files with 617 additions and 332 deletions

View File

@@ -237,7 +237,7 @@ func (s *Scheme) ObjectKinds(obj Object) ([]unversioned.GroupVersionKind, bool,
gvks, ok := s.typeToGVK[t]
if !ok {
return nil, false, &notRegisteredErr{t: t}
return nil, false, NewNotRegisteredErr(unversioned.GroupVersionKind{}, t)
}
_, unversionedType := s.unversionedTypes[t]
@@ -275,7 +275,7 @@ func (s *Scheme) New(kind unversioned.GroupVersionKind) (Object, error) {
if t, exists := s.unversionedKinds[kind.Kind]; exists {
return reflect.New(t).Interface().(Object), nil
}
return nil, &notRegisteredErr{gvk: kind}
return nil, NewNotRegisteredErr(kind, nil)
}
// AddGenericConversionFunc adds a function that accepts the ConversionFunc call pattern
@@ -438,23 +438,13 @@ func (s *Scheme) DeepCopy(src interface{}) (interface{}, error) {
// 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. You can call this with types that haven't been registered (for example,
// a to test conversion of types that are nested within registered types), but in
// that case, the conversion.Scope object passed to your conversion functions won't
// have SrcVersion or DestVersion fields set correctly in Meta().
func (s *Scheme) Convert(in, out interface{}) error {
inVersion := unversioned.GroupVersion{Group: "unknown", Version: "unknown"}
outVersion := unversioned.GroupVersion{Group: "unknown", Version: "unknown"}
if inObj, ok := in.(Object); ok {
if gvks, _, err := s.ObjectKinds(inObj); err == nil {
inVersion = gvks[0].GroupVersion()
}
}
if outObj, ok := out.(Object); ok {
if gvks, _, err := s.ObjectKinds(outObj); err == nil {
outVersion = gvks[0].GroupVersion()
}
}
flags, meta := s.generateConvertMeta(inVersion, outVersion, in)
// a to test conversion of types that are nested within registered types). The
// context interface is passed to the convertor.
// TODO: identify whether context should be hidden, or behind a formal context/scope
// interface
func (s *Scheme) Convert(in, out interface{}, context interface{}) error {
flags, meta := s.generateConvertMeta(in)
meta.Context = context
if flags == 0 {
flags = conversion.AllowDifferentFieldTypeNames
}
@@ -478,73 +468,20 @@ func (s *Scheme) ConvertFieldLabel(version, kind, label, value string) (string,
// version within this scheme. Will return an error if the provided version does not
// contain the inKind (or a mapping by name defined with AddKnownTypeWithName). Will also
// return an error if the conversion does not result in a valid Object being
// returned. The serializer handles loading/serializing nested objects.
func (s *Scheme) ConvertToVersion(in Object, outVersion unversioned.GroupVersion) (Object, error) {
switch in.(type) {
case *Unknown, *Unstructured, *UnstructuredList:
old := in.GetObjectKind().GroupVersionKind()
defer in.GetObjectKind().SetGroupVersionKind(old)
setTargetVersion(in, s, outVersion)
return in, nil
}
t := reflect.TypeOf(in)
if t.Kind() != reflect.Ptr {
return nil, fmt.Errorf("only pointer types may be converted: %v", t)
}
t = t.Elem()
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t)
}
var kind unversioned.GroupVersionKind
if unversionedKind, ok := s.unversionedTypes[t]; ok {
kind = unversionedKind
} else {
kinds, ok := s.typeToGVK[t]
if !ok || len(kinds) == 0 {
return nil, fmt.Errorf("%v is not a registered type and cannot be converted into version %q", t, outVersion)
}
kind = kinds[0]
}
outKind := outVersion.WithKind(kind.Kind)
inKinds, _, err := s.ObjectKinds(in)
if err != nil {
return nil, err
}
out, err := s.New(outKind)
if err != nil {
return nil, err
}
flags, meta := s.generateConvertMeta(inKinds[0].GroupVersion(), outVersion, in)
if err := s.converter.Convert(in, out, flags, meta); err != nil {
return nil, err
}
setTargetVersion(out, s, outVersion)
return out, nil
// returned. Passes target down to the conversion methods as the Context on the scope.
func (s *Scheme) ConvertToVersion(in Object, target GroupVersioner) (Object, error) {
return s.convertToVersion(true, in, target)
}
// UnsafeConvertToVersion will convert in to the provided outVersion if such a conversion is possible,
// UnsafeConvertToVersion will convert in to the provided target if such a conversion is possible,
// but does not guarantee the output object does not share fields with the input object. It attempts to be as
// efficient as possible when doing conversion.
func (s *Scheme) UnsafeConvertToVersion(in Object, outVersion unversioned.GroupVersion) (Object, error) {
switch t := in.(type) {
case *Unknown:
t.APIVersion = outVersion.String()
return t, nil
case *Unstructured:
t.SetAPIVersion(outVersion.String())
return t, nil
case *UnstructuredList:
t.SetAPIVersion(outVersion.String())
return t, nil
}
func (s *Scheme) UnsafeConvertToVersion(in Object, target GroupVersioner) (Object, error) {
return s.convertToVersion(false, in, target)
}
// convertToVersion handles conversion with an optional copy.
func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (Object, error) {
// determine the incoming kinds with as few allocations as possible.
t := reflect.TypeOf(in)
if t.Kind() != reflect.Ptr {
@@ -556,64 +493,69 @@ func (s *Scheme) UnsafeConvertToVersion(in Object, outVersion unversioned.GroupV
}
kinds, ok := s.typeToGVK[t]
if !ok || len(kinds) == 0 {
return nil, fmt.Errorf("%v is not a registered type and cannot be converted into version %q", t, outVersion)
return nil, NewNotRegisteredErr(unversioned.GroupVersionKind{}, t)
}
// if the Go type is also registered to the destination kind, no conversion is necessary
for i := range kinds {
if kinds[i].Version == outVersion.Version && kinds[i].Group == outVersion.Group {
setTargetKind(in, kinds[i])
return in, nil
gvk, ok := target.KindForGroupVersionKinds(kinds)
if !ok {
// TODO: should this be a typed error?
return nil, fmt.Errorf("%v is not suitable for converting to %q", t, target)
}
// target wants to use the existing type, set kind and return (no conversion necessary)
for _, kind := range kinds {
if gvk == kind {
return copyAndSetTargetKind(copy, s, in, gvk)
}
}
// type is unversioned, no conversion necessary
// it should be possible to avoid this allocation
if unversionedKind, ok := s.unversionedTypes[t]; ok {
kind := unversionedKind
outKind := outVersion.WithKind(kind.Kind)
setTargetKind(in, outKind)
return in, nil
if gvk, ok := target.KindForGroupVersionKinds([]unversioned.GroupVersionKind{unversionedKind}); ok {
return copyAndSetTargetKind(copy, s, in, gvk)
}
return copyAndSetTargetKind(copy, s, in, unversionedKind)
}
// allocate a new object as the target using the target kind
// TODO: this should look in the target group version and find the first kind that matches, rather than the
// first kind registered in typeToGVK
kind := kinds[0]
kind.Version = outVersion.Version
kind.Group = outVersion.Group
out, err := s.New(kind)
out, err := s.New(gvk)
if err != nil {
return nil, err
}
// TODO: try to avoid the allocations here - in fast paths we are not likely to need these flags or meta
flags, meta := s.converter.DefaultMeta(t)
if copy {
copied, err := s.Copy(in)
if err != nil {
return nil, err
}
in = copied
}
flags, meta := s.generateConvertMeta(in)
meta.Context = target
if err := s.converter.Convert(in, out, flags, meta); err != nil {
return nil, err
}
setTargetKind(out, kind)
setTargetKind(out, gvk)
return out, nil
}
// generateConvertMeta constructs the meta value we pass to Convert.
func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversioned.GroupVersion, in interface{}) (conversion.FieldMatchingFlags, *conversion.Meta) {
func (s *Scheme) generateConvertMeta(in interface{}) (conversion.FieldMatchingFlags, *conversion.Meta) {
return s.converter.DefaultMeta(reflect.TypeOf(in))
}
// setTargetVersion is deprecated and should be replaced by use of setTargetKind
func setTargetVersion(obj Object, raw *Scheme, gv unversioned.GroupVersion) {
if gv.Version == APIVersionInternal {
// internal is a special case
obj.GetObjectKind().SetGroupVersionKind(unversioned.GroupVersionKind{})
return
}
if gvks, _, _ := raw.ObjectKinds(obj); len(gvks) > 0 {
obj.GetObjectKind().SetGroupVersionKind(unversioned.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: gvks[0].Kind})
} else {
obj.GetObjectKind().SetGroupVersionKind(unversioned.GroupVersionKind{Group: gv.Group, Version: gv.Version})
// copyAndSetTargetKind performs a conditional copy before returning the object, or an error if copy was not successful.
func copyAndSetTargetKind(copy bool, copier ObjectCopier, obj Object, kind unversioned.GroupVersionKind) (Object, error) {
if copy {
copied, err := copier.Copy(obj)
if err != nil {
return nil, err
}
obj = copied
}
setTargetKind(obj, kind)
return obj, nil
}
// setTargetKind sets the kind on an object, taking into account whether the target kind is the internal version.