blob: 50468b5330de38050fb8686b704306a5e1ed7ba2 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package meta
18
19import (
David Bainbridge86971522019-09-26 22:09:39 +000020 "errors"
Zack Williamse940c7a2019-08-21 14:25:39 -070021 "fmt"
22 "reflect"
David Bainbridge86971522019-09-26 22:09:39 +000023 "sync"
Zack Williamse940c7a2019-08-21 14:25:39 -070024
25 "k8s.io/apimachinery/pkg/conversion"
26 "k8s.io/apimachinery/pkg/runtime"
27)
28
David Bainbridge86971522019-09-26 22:09:39 +000029var (
30 // isListCache maintains a cache of types that are checked for lists
31 // which is used by IsListType.
32 // TODO: remove and replace with an interface check
33 isListCache = struct {
34 lock sync.RWMutex
35 byType map[reflect.Type]bool
36 }{
37 byType: make(map[reflect.Type]bool, 1024),
38 }
39)
40
41// IsListType returns true if the provided Object has a slice called Items.
42// TODO: Replace the code in this check with an interface comparison by
43// creating and enforcing that lists implement a list accessor.
Zack Williamse940c7a2019-08-21 14:25:39 -070044func IsListType(obj runtime.Object) bool {
David Bainbridge86971522019-09-26 22:09:39 +000045 switch t := obj.(type) {
46 case runtime.Unstructured:
47 return t.IsList()
48 }
49 t := reflect.TypeOf(obj)
50
51 isListCache.lock.RLock()
52 ok, exists := isListCache.byType[t]
53 isListCache.lock.RUnlock()
54
55 if !exists {
56 _, err := getItemsPtr(obj)
57 ok = err == nil
58
59 // cache only the first 1024 types
60 isListCache.lock.Lock()
61 if len(isListCache.byType) < 1024 {
62 isListCache.byType[t] = ok
63 }
64 isListCache.lock.Unlock()
Zack Williamse940c7a2019-08-21 14:25:39 -070065 }
66
David Bainbridge86971522019-09-26 22:09:39 +000067 return ok
Zack Williamse940c7a2019-08-21 14:25:39 -070068}
69
David Bainbridge86971522019-09-26 22:09:39 +000070var (
71 errExpectFieldItems = errors.New("no Items field in this object")
72 errExpectSliceItems = errors.New("Items field must be a slice of objects")
73)
74
Zack Williamse940c7a2019-08-21 14:25:39 -070075// GetItemsPtr returns a pointer to the list object's Items member.
76// If 'list' doesn't have an Items member, it's not really a list type
77// and an error will be returned.
78// This function will either return a pointer to a slice, or an error, but not both.
David Bainbridge86971522019-09-26 22:09:39 +000079// TODO: this will be replaced with an interface in the future
Zack Williamse940c7a2019-08-21 14:25:39 -070080func GetItemsPtr(list runtime.Object) (interface{}, error) {
David Bainbridge86971522019-09-26 22:09:39 +000081 obj, err := getItemsPtr(list)
82 if err != nil {
83 return nil, fmt.Errorf("%T is not a list: %v", list, err)
84 }
85 return obj, nil
86}
87
88// getItemsPtr returns a pointer to the list object's Items member or an error.
89func getItemsPtr(list runtime.Object) (interface{}, error) {
Zack Williamse940c7a2019-08-21 14:25:39 -070090 v, err := conversion.EnforcePtr(list)
91 if err != nil {
92 return nil, err
93 }
94
95 items := v.FieldByName("Items")
96 if !items.IsValid() {
David Bainbridge86971522019-09-26 22:09:39 +000097 return nil, errExpectFieldItems
Zack Williamse940c7a2019-08-21 14:25:39 -070098 }
99 switch items.Kind() {
100 case reflect.Interface, reflect.Ptr:
101 target := reflect.TypeOf(items.Interface()).Elem()
102 if target.Kind() != reflect.Slice {
David Bainbridge86971522019-09-26 22:09:39 +0000103 return nil, errExpectSliceItems
Zack Williamse940c7a2019-08-21 14:25:39 -0700104 }
105 return items.Interface(), nil
106 case reflect.Slice:
107 return items.Addr().Interface(), nil
108 default:
David Bainbridge86971522019-09-26 22:09:39 +0000109 return nil, errExpectSliceItems
Zack Williamse940c7a2019-08-21 14:25:39 -0700110 }
111}
112
113// EachListItem invokes fn on each runtime.Object in the list. Any error immediately terminates
114// the loop.
115func EachListItem(obj runtime.Object, fn func(runtime.Object) error) error {
116 if unstructured, ok := obj.(runtime.Unstructured); ok {
117 return unstructured.EachListItem(fn)
118 }
119 // TODO: Change to an interface call?
120 itemsPtr, err := GetItemsPtr(obj)
121 if err != nil {
122 return err
123 }
124 items, err := conversion.EnforcePtr(itemsPtr)
125 if err != nil {
126 return err
127 }
128 len := items.Len()
129 if len == 0 {
130 return nil
131 }
132 takeAddr := false
133 if elemType := items.Type().Elem(); elemType.Kind() != reflect.Ptr && elemType.Kind() != reflect.Interface {
134 if !items.Index(0).CanAddr() {
135 return fmt.Errorf("unable to take address of items in %T for EachListItem", obj)
136 }
137 takeAddr = true
138 }
139
140 for i := 0; i < len; i++ {
141 raw := items.Index(i)
142 if takeAddr {
143 raw = raw.Addr()
144 }
145 switch item := raw.Interface().(type) {
146 case *runtime.RawExtension:
147 if err := fn(item.Object); err != nil {
148 return err
149 }
150 case runtime.Object:
151 if err := fn(item); err != nil {
152 return err
153 }
154 default:
155 obj, ok := item.(runtime.Object)
156 if !ok {
157 return fmt.Errorf("%v: item[%v]: Expected object, got %#v(%s)", obj, i, raw.Interface(), raw.Kind())
158 }
159 if err := fn(obj); err != nil {
160 return err
161 }
162 }
163 }
164 return nil
165}
166
167// ExtractList returns obj's Items element as an array of runtime.Objects.
168// Returns an error if obj is not a List type (does not have an Items member).
169func ExtractList(obj runtime.Object) ([]runtime.Object, error) {
170 itemsPtr, err := GetItemsPtr(obj)
171 if err != nil {
172 return nil, err
173 }
174 items, err := conversion.EnforcePtr(itemsPtr)
175 if err != nil {
176 return nil, err
177 }
178 list := make([]runtime.Object, items.Len())
179 for i := range list {
180 raw := items.Index(i)
181 switch item := raw.Interface().(type) {
182 case runtime.RawExtension:
183 switch {
184 case item.Object != nil:
185 list[i] = item.Object
186 case item.Raw != nil:
187 // TODO: Set ContentEncoding and ContentType correctly.
188 list[i] = &runtime.Unknown{Raw: item.Raw}
189 default:
190 list[i] = nil
191 }
192 case runtime.Object:
193 list[i] = item
194 default:
195 var found bool
196 if list[i], found = raw.Addr().Interface().(runtime.Object); !found {
197 return nil, fmt.Errorf("%v: item[%v]: Expected object, got %#v(%s)", obj, i, raw.Interface(), raw.Kind())
198 }
199 }
200 }
201 return list, nil
202}
203
204// objectSliceType is the type of a slice of Objects
205var objectSliceType = reflect.TypeOf([]runtime.Object{})
206
David Bainbridge86971522019-09-26 22:09:39 +0000207// LenList returns the length of this list or 0 if it is not a list.
208func LenList(list runtime.Object) int {
209 itemsPtr, err := GetItemsPtr(list)
210 if err != nil {
211 return 0
212 }
213 items, err := conversion.EnforcePtr(itemsPtr)
214 if err != nil {
215 return 0
216 }
217 return items.Len()
218}
219
Zack Williamse940c7a2019-08-21 14:25:39 -0700220// SetList sets the given list object's Items member have the elements given in
221// objects.
222// Returns an error if list is not a List type (does not have an Items member),
223// or if any of the objects are not of the right type.
224func SetList(list runtime.Object, objects []runtime.Object) error {
225 itemsPtr, err := GetItemsPtr(list)
226 if err != nil {
227 return err
228 }
229 items, err := conversion.EnforcePtr(itemsPtr)
230 if err != nil {
231 return err
232 }
233 if items.Type() == objectSliceType {
234 items.Set(reflect.ValueOf(objects))
235 return nil
236 }
237 slice := reflect.MakeSlice(items.Type(), len(objects), len(objects))
238 for i := range objects {
239 dest := slice.Index(i)
240 if dest.Type() == reflect.TypeOf(runtime.RawExtension{}) {
241 dest = dest.FieldByName("Object")
242 }
243
244 // check to see if you're directly assignable
245 if reflect.TypeOf(objects[i]).AssignableTo(dest.Type()) {
246 dest.Set(reflect.ValueOf(objects[i]))
247 continue
248 }
249
250 src, err := conversion.EnforcePtr(objects[i])
251 if err != nil {
252 return err
253 }
254 if src.Type().AssignableTo(dest.Type()) {
255 dest.Set(src)
256 } else if src.Type().ConvertibleTo(dest.Type()) {
257 dest.Set(src.Convert(dest.Type()))
258 } else {
259 return fmt.Errorf("item[%d]: can't assign or convert %v into %v", i, src.Type(), dest.Type())
260 }
261 }
262 items.Set(slice)
263 return nil
264}