Fork API types.

This commit is contained in:
Brendan Burns
2014-07-16 14:30:28 -07:00
parent cb28f25b1b
commit bf5ae4bb9d
15 changed files with 1251 additions and 76 deletions

View File

@@ -21,13 +21,18 @@ import (
"fmt"
"reflect"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"gopkg.in/v1/yaml"
)
var knownTypes = map[string]reflect.Type{}
type ConversionFunc func(input interface{}) (output interface{}, err error)
var versionMap = map[string]map[string]reflect.Type{}
var externalFuncs = map[string]ConversionFunc{}
var internalFuncs = map[string]ConversionFunc{}
func init() {
AddKnownTypes(
AddKnownTypes("",
PodList{},
Pod{},
ReplicationControllerList{},
@@ -40,17 +45,43 @@ func init() {
ServerOpList{},
ServerOp{},
)
AddKnownTypes("v1beta1",
v1beta1.PodList{},
v1beta1.Pod{},
v1beta1.ReplicationControllerList{},
v1beta1.ReplicationController{},
v1beta1.ServiceList{},
v1beta1.Service{},
v1beta1.MinionList{},
v1beta1.Minion{},
v1beta1.Status{},
v1beta1.ServerOpList{},
v1beta1.ServerOp{},
)
}
// 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 AddKnownTypes(types ...interface{}) {
func AddKnownTypes(version string, types ...interface{}) {
knownTypes, found := versionMap[version]
if !found {
knownTypes = map[string]reflect.Type{}
versionMap[version] = knownTypes
}
for _, obj := range types {
t := reflect.TypeOf(obj)
knownTypes[t.Name()] = t
}
}
func AddExternalConversion(name string, fn ConversionFunc) {
externalFuncs[name] = fn
}
func AddInternalConversion(name string, fn ConversionFunc) {
internalFuncs[name] = fn
}
// FindJSONBase takes an arbitary api type, returns pointer to its JSONBase field.
// obj must be a pointer to an api type.
func FindJSONBase(obj interface{}) (*JSONBase, error) {
@@ -85,11 +116,32 @@ func FindJSONBaseRO(obj interface{}) (JSONBase, error) {
// format.
func Encode(obj interface{}) (data []byte, err error) {
obj = checkPtr(obj)
jsonBase, err := prepareEncode(obj)
base, err := prepareEncode(obj)
if err != nil {
return nil, err
}
if len(base.APIVersion) == 0 {
out, err := externalize(obj)
if err != nil {
return nil, err
}
_, jsonBase, err := nameAndJSONBase(obj)
if err != nil {
return nil, err
}
jsonBase.Kind = ""
obj = out
_, err = prepareEncode(out)
if err != nil {
return nil, err
}
}
data, err = json.MarshalIndent(obj, "", " ")
_, jsonBase, err := nameAndJSONBase(obj)
if err != nil {
return nil, err
}
jsonBase.Kind = ""
return data, err
}
@@ -109,8 +161,12 @@ func prepareEncode(obj interface{}) (*JSONBase, error) {
if err != nil {
return nil, err
}
knownTypes, found := versionMap[jsonBase.APIVersion]
if !found {
return nil, fmt.Errorf("struct %s, %v won't be unmarshalable because its not in known versions", jsonBase.APIVersion, obj)
}
if _, contains := knownTypes[name]; !contains {
return nil, fmt.Errorf("struct %v won't be unmarshalable because it's not in knownTypes", name)
return nil, fmt.Errorf("struct %s won't be unmarshalable because it's not in knownTypes", name)
}
jsonBase.Kind = name
return jsonBase, nil
@@ -131,29 +187,48 @@ func nameAndJSONBase(obj interface{}) (string, *JSONBase, error) {
if !jsonBase.IsValid() {
return "", nil, fmt.Errorf("struct %v lacks embedded JSON type", name)
}
return name, jsonBase.Addr().Interface().(*JSONBase), nil
output, ok := jsonBase.Addr().Interface().(*JSONBase)
if !ok {
internal, err := internalize(jsonBase.Addr().Interface())
if err != nil {
return name, nil, err
}
output = internal.(*JSONBase)
}
return name, output, nil
}
// Decode converts a JSON string back into a pointer to an api object. Deduces the type
// based upon the Kind field (set by encode).
func Decode(data []byte) (interface{}, error) {
findKind := struct {
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
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 nil, fmt.Errorf("couldn't get kind: %#v", err)
}
knownTypes, found := versionMap[findKind.APIVersion]
if !found {
return nil, fmt.Errorf("Unknown api verson: %s", findKind.APIVersion)
}
objType, found := knownTypes[findKind.Kind]
if !found {
return nil, fmt.Errorf("%v is not a known type", findKind.Kind)
return nil, fmt.Errorf("%#v is not a known type for decoding", findKind)
}
obj := reflect.New(objType).Interface()
err = yaml.Unmarshal(data, obj)
if err != nil {
return nil, err
}
if len(findKind.APIVersion) != 0 {
obj, err = internalize(obj)
if err != nil {
return nil, err
}
}
_, jsonBase, err := nameAndJSONBase(obj)
if err != nil {
return nil, err
@@ -167,10 +242,16 @@ func Decode(data []byte) (interface{}, error) {
// if data.Kind is set and doesn't match the type of obj. Obj should be a
// pointer to an api type.
func DecodeInto(data []byte, obj interface{}) error {
err := yaml.Unmarshal(data, obj)
internal, err := Decode(data)
if err != nil {
return err
}
v := reflect.ValueOf(obj)
iv := reflect.ValueOf(internal)
if !iv.Type().AssignableTo(v.Type()) {
return fmt.Errorf("%s is not assignable to %s", v.Type(), iv.Type())
}
v.Elem().Set(iv.Elem())
name, jsonBase, err := nameAndJSONBase(obj)
if err != nil {
return err
@@ -182,3 +263,665 @@ func DecodeInto(data []byte, obj interface{}) error {
jsonBase.Kind = ""
return nil
}
// TODO: Switch to registered functions for each type.
func internalize(obj interface{}) (interface{}, error) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
value := reflect.New(v.Type())
value.Elem().Set(v)
result, err := internalize(value.Interface())
if err != nil {
return nil, err
}
return reflect.ValueOf(result).Elem().Interface(), nil
}
switch cObj := obj.(type) {
case *v1beta1.JSONBase:
obj := JSONBase(*cObj)
return &obj, nil
case *v1beta1.PodList:
var items []Pod
if cObj.Items != nil {
items = make([]Pod, len(cObj.Items))
for ix := range cObj.Items {
iObj, err := internalize(cObj.Items[ix])
if err != nil {
return nil, err
}
items[ix] = iObj.(Pod)
}
}
result := PodList{
JSONBase: JSONBase(cObj.JSONBase),
Items: items,
}
result.APIVersion = ""
return &result, nil
case *v1beta1.Pod:
current, err := internalize(cObj.CurrentState)
if err != nil {
return nil, err
}
desired, err := internalize(cObj.DesiredState)
if err != nil {
return nil, err
}
result := Pod{
JSONBase: JSONBase(cObj.JSONBase),
Labels: cObj.Labels,
CurrentState: current.(PodState),
DesiredState: desired.(PodState),
}
result.APIVersion = ""
return &result, nil
case *v1beta1.PodState:
manifest, err := internalize(cObj.Manifest)
if err != nil {
return nil, err
}
result := PodState{
Manifest: manifest.(ContainerManifest),
Status: PodStatus(cObj.Status),
Host: cObj.Host,
HostIP: cObj.HostIP,
PodIP: cObj.PodIP,
Info: PodInfo(cObj.Info),
}
return &result, nil
case *v1beta1.ContainerManifest:
var volumes []Volume
if cObj.Volumes != nil {
volumes = make([]Volume, len(cObj.Volumes))
for ix := range cObj.Volumes {
v, err := internalize(cObj.Volumes[ix])
if err != nil {
return nil, err
}
volumes[ix] = *(v.(*Volume))
}
}
var containers []Container
if cObj.Containers != nil {
containers = make([]Container, len(cObj.Containers))
for ix := range cObj.Containers {
v, err := internalize(cObj.Containers[ix])
if err != nil {
return nil, err
}
containers[ix] = v.(Container)
}
}
result := ContainerManifest{
Version: cObj.Version,
ID: cObj.ID,
Volumes: volumes,
Containers: containers,
}
return &result, nil
case *v1beta1.Volume:
var src *VolumeSource
if cObj.Source != nil {
obj, err := internalize(cObj.Source)
if err != nil {
return nil, err
}
src = obj.(*VolumeSource)
}
result := &Volume{
Name: cObj.Name,
Source: src,
}
return &result, nil
case *v1beta1.VolumeSource:
var hostDir *HostDirectory
if cObj.HostDirectory != nil {
hostDir = &HostDirectory{
Path: cObj.HostDirectory.Path,
}
}
var emptyDir *EmptyDirectory
if cObj.EmptyDirectory != nil {
emptyDir = &EmptyDirectory{}
}
result := VolumeSource{
HostDirectory: hostDir,
EmptyDirectory: emptyDir,
}
return &result, nil
case *v1beta1.Container:
ports := make([]Port, len(cObj.Ports))
for ix := range cObj.Ports {
p, err := internalize(cObj.Ports[ix])
if err != nil {
return nil, err
}
ports[ix] = (p.(Port))
}
env := make([]EnvVar, len(cObj.Env))
for ix := range cObj.Env {
e, err := internalize(cObj.Env[ix])
if err != nil {
return nil, err
}
env[ix] = e.(EnvVar)
}
mounts := make([]VolumeMount, len(cObj.VolumeMounts))
for ix := range cObj.VolumeMounts {
v, err := internalize(cObj.VolumeMounts[ix])
if err != nil {
return nil, err
}
mounts[ix] = v.(VolumeMount)
}
var liveness *LivenessProbe
if cObj.LivenessProbe != nil {
probe, err := internalize(*cObj.LivenessProbe)
if err != nil {
return nil, err
}
live := probe.(LivenessProbe)
liveness = &live
}
result := Container{
Name: cObj.Name,
Image: cObj.Image,
Command: cObj.Command,
WorkingDir: cObj.WorkingDir,
Ports: ports,
Env: env,
Memory: cObj.Memory,
CPU: cObj.CPU,
VolumeMounts: mounts,
LivenessProbe: liveness,
}
return &result, nil
case *v1beta1.Port:
result := Port(*cObj)
return &result, nil
case *v1beta1.EnvVar:
result := EnvVar(*cObj)
return &result, nil
case *v1beta1.VolumeMount:
result := VolumeMount(*cObj)
return &result, nil
case *v1beta1.LivenessProbe:
var http *HTTPGetProbe
if cObj.HTTPGet != nil {
httpProbe := HTTPGetProbe(*cObj.HTTPGet)
http = &httpProbe
}
result := LivenessProbe{
Type: cObj.Type,
HTTPGet: http,
InitialDelaySeconds: cObj.InitialDelaySeconds,
}
return &result, nil
case *v1beta1.ReplicationControllerList:
var items []ReplicationController
if cObj.Items != nil {
items := make([]ReplicationController, len(cObj.Items))
for ix := range cObj.Items {
rc, err := internalize(cObj.Items[ix])
if err != nil {
return nil, err
}
items[ix] = rc.(ReplicationController)
}
}
result := ReplicationControllerList{
JSONBase: JSONBase(cObj.JSONBase),
Items: items,
}
result.APIVersion = ""
return &result, nil
case *v1beta1.ReplicationController:
desired, err := internalize(cObj.DesiredState)
if err != nil {
return nil, err
}
result := ReplicationController{
JSONBase: JSONBase(cObj.JSONBase),
DesiredState: desired.(ReplicationControllerState),
Labels: cObj.Labels,
}
result.APIVersion = ""
return &result, nil
case *v1beta1.ReplicationControllerState:
template, err := internalize(cObj.PodTemplate)
if err != nil {
return nil, err
}
result := ReplicationControllerState{
Replicas: cObj.Replicas,
ReplicaSelector: cObj.ReplicaSelector,
PodTemplate: template.(PodTemplate),
}
return &result, nil
case *v1beta1.PodTemplate:
desired, err := internalize(cObj.DesiredState)
if err != nil {
return nil, err
}
return &PodTemplate{
DesiredState: desired.(PodState),
Labels: cObj.Labels,
}, nil
case *v1beta1.ServiceList:
var services []Service
if cObj.Items != nil {
services = make([]Service, len(cObj.Items))
for ix := range cObj.Items {
s, err := internalize(cObj.Items[ix])
if err != nil {
return nil, err
}
services[ix] = s.(Service)
services[ix].APIVersion = ""
}
}
result := ServiceList{
JSONBase: JSONBase(cObj.JSONBase),
Items: services,
}
result.APIVersion = ""
return &result, nil
case *v1beta1.Service:
result := Service{
JSONBase: JSONBase(cObj.JSONBase),
Port: cObj.Port,
Labels: cObj.Labels,
Selector: cObj.Selector,
CreateExternalLoadBalancer: cObj.CreateExternalLoadBalancer,
ContainerPort: cObj.ContainerPort,
}
result.APIVersion = ""
return &result, nil
case *v1beta1.MinionList:
minions := make([]Minion, len(cObj.Items))
for ix := range cObj.Items {
m, err := internalize(cObj.Items[ix])
if err != nil {
return nil, err
}
minions[ix] = m.(Minion)
}
result := MinionList{
JSONBase: JSONBase(cObj.JSONBase),
Items: minions,
}
result.APIVersion = ""
return &result, nil
case *v1beta1.Minion:
result := Minion{
JSONBase: JSONBase(cObj.JSONBase),
HostIP: cObj.HostIP,
}
result.APIVersion = ""
return &result, nil
case *v1beta1.Status:
result := Status{
JSONBase: JSONBase(cObj.JSONBase),
Status: cObj.Status,
Details: cObj.Details,
Code: cObj.Code,
}
result.APIVersion = ""
return &result, nil
case *v1beta1.ServerOpList:
ops := make([]ServerOp, len(cObj.Items))
for ix := range cObj.Items {
o, err := internalize(cObj.Items[ix])
if err != nil {
return nil, err
}
ops[ix] = o.(ServerOp)
}
result := ServerOpList{
JSONBase: JSONBase(cObj.JSONBase),
Items: ops,
}
result.APIVersion = ""
return &result, nil
case *v1beta1.ServerOp:
result := ServerOp{
JSONBase: JSONBase(cObj.JSONBase),
}
result.APIVersion = ""
return &result, nil
default:
fn, ok := internalFuncs[reflect.ValueOf(cObj).Elem().Type().Name()]
if !ok {
fmt.Printf("unknown object to internalize: %s", reflect.ValueOf(cObj).Type().Name())
panic(fmt.Sprintf("unknown object to internalize: %s", reflect.ValueOf(cObj).Type().Name()))
}
return fn(cObj)
}
return obj, nil
}
// TODO: switch to registered functions for each type.
func externalize(obj interface{}) (interface{}, error) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
value := reflect.New(v.Type())
value.Elem().Set(v)
result, err := externalize(value.Interface())
if err != nil {
return nil, err
}
return reflect.ValueOf(result).Elem().Interface(), nil
}
switch cObj := obj.(type) {
case *PodList:
var items []v1beta1.Pod
if cObj.Items != nil {
items = make([]v1beta1.Pod, len(cObj.Items))
for ix := range cObj.Items {
iObj, err := externalize(cObj.Items[ix])
if err != nil {
return nil, err
}
items[ix] = iObj.(v1beta1.Pod)
}
}
result := v1beta1.PodList{
JSONBase: v1beta1.JSONBase(cObj.JSONBase),
Items: items,
}
result.APIVersion = "v1beta1"
return &result, nil
case *Pod:
current, err := externalize(cObj.CurrentState)
if err != nil {
return nil, err
}
desired, err := externalize(cObj.DesiredState)
if err != nil {
return nil, err
}
result := v1beta1.Pod{
JSONBase: v1beta1.JSONBase(cObj.JSONBase),
Labels: cObj.Labels,
CurrentState: current.(v1beta1.PodState),
DesiredState: desired.(v1beta1.PodState),
}
result.APIVersion = "v1beta1"
return &result, nil
case *PodState:
manifest, err := externalize(cObj.Manifest)
if err != nil {
return nil, err
}
result := v1beta1.PodState{
Manifest: manifest.(v1beta1.ContainerManifest),
Status: v1beta1.PodStatus(cObj.Status),
Host: cObj.Host,
HostIP: cObj.HostIP,
PodIP: cObj.PodIP,
Info: v1beta1.PodInfo(cObj.Info),
}
return &result, nil
case *ContainerManifest:
var volumes []v1beta1.Volume
if cObj.Volumes != nil {
volumes = make([]v1beta1.Volume, len(cObj.Volumes))
for ix := range cObj.Volumes {
v, err := externalize(cObj.Volumes[ix])
if err != nil {
return nil, err
}
volumes[ix] = *(v.(*v1beta1.Volume))
}
}
var containers []v1beta1.Container
if cObj.Containers != nil {
containers = make([]v1beta1.Container, len(cObj.Containers))
for ix := range cObj.Containers {
v, err := externalize(cObj.Containers[ix])
if err != nil {
return nil, err
}
containers[ix] = v.(v1beta1.Container)
}
}
result := v1beta1.ContainerManifest{
Version: cObj.Version,
ID: cObj.ID,
Volumes: volumes,
Containers: containers,
}
return &result, nil
case *Volume:
var src *v1beta1.VolumeSource
if cObj.Source != nil {
obj, err := externalize(cObj.Source)
if err != nil {
return nil, err
}
src = obj.(*v1beta1.VolumeSource)
}
result := &v1beta1.Volume{
Name: cObj.Name,
Source: src,
}
return &result, nil
case *VolumeSource:
var hostDir *v1beta1.HostDirectory
if cObj.HostDirectory != nil {
hostDir = &v1beta1.HostDirectory{
Path: cObj.HostDirectory.Path,
}
}
var emptyDir *v1beta1.EmptyDirectory
if cObj.EmptyDirectory != nil {
emptyDir = &v1beta1.EmptyDirectory{}
}
result := v1beta1.VolumeSource{
HostDirectory: hostDir,
EmptyDirectory: emptyDir,
}
return &result, nil
case *Container:
ports := make([]v1beta1.Port, len(cObj.Ports))
for ix := range cObj.Ports {
p, err := externalize(cObj.Ports[ix])
if err != nil {
return nil, err
}
ports[ix] = p.(v1beta1.Port)
}
env := make([]v1beta1.EnvVar, len(cObj.Env))
for ix := range cObj.Env {
e, err := externalize(cObj.Env[ix])
if err != nil {
return nil, err
}
env[ix] = e.(v1beta1.EnvVar)
}
mounts := make([]v1beta1.VolumeMount, len(cObj.VolumeMounts))
for ix := range cObj.VolumeMounts {
v, err := externalize(cObj.VolumeMounts[ix])
if err != nil {
return nil, err
}
mounts[ix] = v.(v1beta1.VolumeMount)
}
var liveness *v1beta1.LivenessProbe
if cObj.LivenessProbe != nil {
probe, err := externalize(*cObj.LivenessProbe)
if err != nil {
return nil, err
}
live := probe.(v1beta1.LivenessProbe)
liveness = &live
}
result := v1beta1.Container{
Name: cObj.Name,
Image: cObj.Image,
Command: cObj.Command,
WorkingDir: cObj.WorkingDir,
Ports: ports,
Env: env,
Memory: cObj.Memory,
CPU: cObj.CPU,
VolumeMounts: mounts,
LivenessProbe: liveness,
}
return &result, nil
case *Port:
result := v1beta1.Port(*cObj)
return &result, nil
case *EnvVar:
result := v1beta1.EnvVar(*cObj)
return &result, nil
case *VolumeMount:
result := v1beta1.VolumeMount(*cObj)
return &result, nil
case *LivenessProbe:
var http *v1beta1.HTTPGetProbe
if cObj.HTTPGet != nil {
httpProbe := v1beta1.HTTPGetProbe(*cObj.HTTPGet)
http = &httpProbe
}
result := v1beta1.LivenessProbe{
Type: cObj.Type,
HTTPGet: http,
InitialDelaySeconds: cObj.InitialDelaySeconds,
}
return &result, nil
case *ReplicationControllerList:
items := make([]v1beta1.ReplicationController, len(cObj.Items))
for ix := range cObj.Items {
rc, err := externalize(cObj.Items[ix])
if err != nil {
return nil, err
}
items[ix] = rc.(v1beta1.ReplicationController)
}
result := v1beta1.ReplicationControllerList{
JSONBase: v1beta1.JSONBase(cObj.JSONBase),
Items: items,
}
result.APIVersion = "v1beta1"
return &result, nil
case *ReplicationController:
desired, err := externalize(cObj.DesiredState)
if err != nil {
return nil, err
}
result := v1beta1.ReplicationController{
JSONBase: v1beta1.JSONBase(cObj.JSONBase),
DesiredState: desired.(v1beta1.ReplicationControllerState),
Labels: cObj.Labels,
}
result.APIVersion = "v1beta1"
return &result, nil
case *ReplicationControllerState:
template, err := externalize(cObj.PodTemplate)
if err != nil {
return nil, err
}
result := v1beta1.ReplicationControllerState{
Replicas: cObj.Replicas,
ReplicaSelector: cObj.ReplicaSelector,
PodTemplate: template.(v1beta1.PodTemplate),
}
return &result, nil
case *PodTemplate:
desired, err := externalize(cObj.DesiredState)
if err != nil {
return nil, err
}
return &v1beta1.PodTemplate{
DesiredState: desired.(v1beta1.PodState),
Labels: cObj.Labels,
}, nil
case *ServiceList:
services := make([]v1beta1.Service, len(cObj.Items))
for ix := range cObj.Items {
s, err := externalize(cObj.Items[ix])
if err != nil {
return nil, err
}
services[ix] = s.(v1beta1.Service)
}
result := v1beta1.ServiceList{
JSONBase: v1beta1.JSONBase(cObj.JSONBase),
Items: services,
}
result.APIVersion = "v1beta1"
return &result, nil
case *Service:
result := v1beta1.Service{
JSONBase: v1beta1.JSONBase(cObj.JSONBase),
Port: cObj.Port,
Labels: cObj.Labels,
Selector: cObj.Selector,
CreateExternalLoadBalancer: cObj.CreateExternalLoadBalancer,
ContainerPort: cObj.ContainerPort,
}
result.APIVersion = "v1beta1"
return &result, nil
case *MinionList:
minions := make([]v1beta1.Minion, len(cObj.Items))
for ix := range cObj.Items {
m, err := externalize(cObj.Items[ix])
if err != nil {
return nil, err
}
minions[ix] = m.(v1beta1.Minion)
}
result := v1beta1.MinionList{
JSONBase: v1beta1.JSONBase(cObj.JSONBase),
Items: minions,
}
result.APIVersion = "v1beta1"
return &result, nil
case *Minion:
result := v1beta1.Minion{
JSONBase: v1beta1.JSONBase(cObj.JSONBase),
HostIP: cObj.HostIP,
}
result.APIVersion = "v1beta1"
return &result, nil
case *Status:
result := v1beta1.Status{
JSONBase: v1beta1.JSONBase(cObj.JSONBase),
Status: cObj.Status,
Details: cObj.Details,
Code: cObj.Code,
}
result.APIVersion = "v1beta1"
return &result, nil
case *ServerOpList:
ops := make([]v1beta1.ServerOp, len(cObj.Items))
for ix := range cObj.Items {
o, err := externalize(cObj.Items[ix])
if err != nil {
return nil, err
}
ops[ix] = o.(v1beta1.ServerOp)
}
result := v1beta1.ServerOpList{
JSONBase: v1beta1.JSONBase(cObj.JSONBase),
Items: ops,
}
result.APIVersion = "v1beta1"
return &result, nil
case *ServerOp:
result := v1beta1.ServerOp{
JSONBase: v1beta1.JSONBase(cObj.JSONBase),
}
result.APIVersion = "v1beta1"
return &result, nil
default:
fn, ok := externalFuncs[reflect.ValueOf(cObj).Elem().Type().Name()]
if !ok {
panic(fmt.Sprintf("Unknown object to externalize: %#v %s", cObj, reflect.ValueOf(cObj).Type().Name()))
}
return fn(cObj)
}
panic(fmt.Sprintf("This should never happen %#v", obj))
return obj, nil
}