blob: 3425055f6ec127f5e99c77db10bc9d9751952ac8 [file] [log] [blame]
Scott Bakere7144bc2019-10-01 14:16:47 -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 (
20 "fmt"
21 "reflect"
22
23 "k8s.io/apimachinery/pkg/conversion"
24 "k8s.io/apimachinery/pkg/runtime"
25)
26
27// IsListType returns true if the provided Object has a slice called Items
28func IsListType(obj runtime.Object) bool {
29 // if we're a runtime.Unstructured, check whether this is a list.
30 // TODO: refactor GetItemsPtr to use an interface that returns []runtime.Object
31 if unstructured, ok := obj.(runtime.Unstructured); ok {
32 return unstructured.IsList()
33 }
34
35 _, err := GetItemsPtr(obj)
36 return err == nil
37}
38
39// GetItemsPtr returns a pointer to the list object's Items member.
40// If 'list' doesn't have an Items member, it's not really a list type
41// and an error will be returned.
42// This function will either return a pointer to a slice, or an error, but not both.
43func GetItemsPtr(list runtime.Object) (interface{}, error) {
44 v, err := conversion.EnforcePtr(list)
45 if err != nil {
46 return nil, err
47 }
48
49 items := v.FieldByName("Items")
50 if !items.IsValid() {
51 return nil, fmt.Errorf("no Items field in %#v", list)
52 }
53 switch items.Kind() {
54 case reflect.Interface, reflect.Ptr:
55 target := reflect.TypeOf(items.Interface()).Elem()
56 if target.Kind() != reflect.Slice {
57 return nil, fmt.Errorf("items: Expected slice, got %s", target.Kind())
58 }
59 return items.Interface(), nil
60 case reflect.Slice:
61 return items.Addr().Interface(), nil
62 default:
63 return nil, fmt.Errorf("items: Expected slice, got %s", items.Kind())
64 }
65}
66
67// EachListItem invokes fn on each runtime.Object in the list. Any error immediately terminates
68// the loop.
69func EachListItem(obj runtime.Object, fn func(runtime.Object) error) error {
70 if unstructured, ok := obj.(runtime.Unstructured); ok {
71 return unstructured.EachListItem(fn)
72 }
73 // TODO: Change to an interface call?
74 itemsPtr, err := GetItemsPtr(obj)
75 if err != nil {
76 return err
77 }
78 items, err := conversion.EnforcePtr(itemsPtr)
79 if err != nil {
80 return err
81 }
82 len := items.Len()
83 if len == 0 {
84 return nil
85 }
86 takeAddr := false
87 if elemType := items.Type().Elem(); elemType.Kind() != reflect.Ptr && elemType.Kind() != reflect.Interface {
88 if !items.Index(0).CanAddr() {
89 return fmt.Errorf("unable to take address of items in %T for EachListItem", obj)
90 }
91 takeAddr = true
92 }
93
94 for i := 0; i < len; i++ {
95 raw := items.Index(i)
96 if takeAddr {
97 raw = raw.Addr()
98 }
99 switch item := raw.Interface().(type) {
100 case *runtime.RawExtension:
101 if err := fn(item.Object); err != nil {
102 return err
103 }
104 case runtime.Object:
105 if err := fn(item); err != nil {
106 return err
107 }
108 default:
109 obj, ok := item.(runtime.Object)
110 if !ok {
111 return fmt.Errorf("%v: item[%v]: Expected object, got %#v(%s)", obj, i, raw.Interface(), raw.Kind())
112 }
113 if err := fn(obj); err != nil {
114 return err
115 }
116 }
117 }
118 return nil
119}
120
121// ExtractList returns obj's Items element as an array of runtime.Objects.
122// Returns an error if obj is not a List type (does not have an Items member).
123func ExtractList(obj runtime.Object) ([]runtime.Object, error) {
124 itemsPtr, err := GetItemsPtr(obj)
125 if err != nil {
126 return nil, err
127 }
128 items, err := conversion.EnforcePtr(itemsPtr)
129 if err != nil {
130 return nil, err
131 }
132 list := make([]runtime.Object, items.Len())
133 for i := range list {
134 raw := items.Index(i)
135 switch item := raw.Interface().(type) {
136 case runtime.RawExtension:
137 switch {
138 case item.Object != nil:
139 list[i] = item.Object
140 case item.Raw != nil:
141 // TODO: Set ContentEncoding and ContentType correctly.
142 list[i] = &runtime.Unknown{Raw: item.Raw}
143 default:
144 list[i] = nil
145 }
146 case runtime.Object:
147 list[i] = item
148 default:
149 var found bool
150 if list[i], found = raw.Addr().Interface().(runtime.Object); !found {
151 return nil, fmt.Errorf("%v: item[%v]: Expected object, got %#v(%s)", obj, i, raw.Interface(), raw.Kind())
152 }
153 }
154 }
155 return list, nil
156}
157
158// objectSliceType is the type of a slice of Objects
159var objectSliceType = reflect.TypeOf([]runtime.Object{})
160
161// LenList returns the length of this list or 0 if it is not a list.
162func LenList(list runtime.Object) int {
163 itemsPtr, err := GetItemsPtr(list)
164 if err != nil {
165 return 0
166 }
167 items, err := conversion.EnforcePtr(itemsPtr)
168 if err != nil {
169 return 0
170 }
171 return items.Len()
172}
173
174// SetList sets the given list object's Items member have the elements given in
175// objects.
176// Returns an error if list is not a List type (does not have an Items member),
177// or if any of the objects are not of the right type.
178func SetList(list runtime.Object, objects []runtime.Object) error {
179 itemsPtr, err := GetItemsPtr(list)
180 if err != nil {
181 return err
182 }
183 items, err := conversion.EnforcePtr(itemsPtr)
184 if err != nil {
185 return err
186 }
187 if items.Type() == objectSliceType {
188 items.Set(reflect.ValueOf(objects))
189 return nil
190 }
191 slice := reflect.MakeSlice(items.Type(), len(objects), len(objects))
192 for i := range objects {
193 dest := slice.Index(i)
194 if dest.Type() == reflect.TypeOf(runtime.RawExtension{}) {
195 dest = dest.FieldByName("Object")
196 }
197
198 // check to see if you're directly assignable
199 if reflect.TypeOf(objects[i]).AssignableTo(dest.Type()) {
200 dest.Set(reflect.ValueOf(objects[i]))
201 continue
202 }
203
204 src, err := conversion.EnforcePtr(objects[i])
205 if err != nil {
206 return err
207 }
208 if src.Type().AssignableTo(dest.Type()) {
209 dest.Set(src)
210 } else if src.Type().ConvertibleTo(dest.Type()) {
211 dest.Set(src.Convert(dest.Type()))
212 } else {
213 return fmt.Errorf("item[%d]: can't assign or convert %v into %v", i, src.Type(), dest.Type())
214 }
215 }
216 items.Set(slice)
217 return nil
218}