forked from kubernetes/kubernetes
-
Notifications
You must be signed in to change notification settings - Fork 1
/
meta.go
119 lines (102 loc) · 4.22 KB
/
meta.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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
}