1
16
17
18
19
20 package apiutil
21
22 import (
23 "errors"
24 "fmt"
25 "net/http"
26 "reflect"
27 "sync"
28
29 "k8s.io/apimachinery/pkg/api/meta"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/runtime"
32 "k8s.io/apimachinery/pkg/runtime/schema"
33 "k8s.io/apimachinery/pkg/runtime/serializer"
34 "k8s.io/client-go/dynamic"
35 clientgoscheme "k8s.io/client-go/kubernetes/scheme"
36 "k8s.io/client-go/rest"
37 )
38
39 var (
40 protobufScheme = runtime.NewScheme()
41 protobufSchemeLock sync.RWMutex
42 )
43
44 func init() {
45
46
47
48 if err := clientgoscheme.AddToScheme(protobufScheme); err != nil {
49 panic(err)
50 }
51 }
52
53
54
55 func AddToProtobufScheme(addToScheme func(*runtime.Scheme) error) error {
56 protobufSchemeLock.Lock()
57 defer protobufSchemeLock.Unlock()
58 return addToScheme(protobufScheme)
59 }
60
61
62
63 func IsObjectNamespaced(obj runtime.Object, scheme *runtime.Scheme, restmapper meta.RESTMapper) (bool, error) {
64 gvk, err := GVKForObject(obj, scheme)
65 if err != nil {
66 return false, err
67 }
68
69 return IsGVKNamespaced(gvk, restmapper)
70 }
71
72
73
74 func IsGVKNamespaced(gvk schema.GroupVersionKind, restmapper meta.RESTMapper) (bool, error) {
75 restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind})
76 if err != nil {
77 return false, fmt.Errorf("failed to get restmapping: %w", err)
78 }
79
80 scope := restmapping.Scope.Name()
81 if scope == "" {
82 return false, errors.New("scope cannot be identified, empty scope returned")
83 }
84
85 if scope != meta.RESTScopeNameRoot {
86 return true, nil
87 }
88 return false, nil
89 }
90
91
92 func GVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) {
93
94
95
96
97
98
99
100 _, isPartial := obj.(*metav1.PartialObjectMetadata)
101 _, isPartialList := obj.(*metav1.PartialObjectMetadataList)
102 if isPartial || isPartialList {
103
104 gvk := obj.GetObjectKind().GroupVersionKind()
105 if len(gvk.Kind) == 0 {
106 return schema.GroupVersionKind{}, runtime.NewMissingKindErr("unstructured object has no kind")
107 }
108 if len(gvk.Version) == 0 {
109 return schema.GroupVersionKind{}, runtime.NewMissingVersionErr("unstructured object has no version")
110 }
111 return gvk, nil
112 }
113
114
115 gvks, isUnversioned, err := scheme.ObjectKinds(obj)
116 if err != nil {
117 return schema.GroupVersionKind{}, err
118 }
119 if isUnversioned {
120 return schema.GroupVersionKind{}, fmt.Errorf("cannot create group-version-kind for unversioned type %T", obj)
121 }
122
123 switch {
124 case len(gvks) < 1:
125
126
127 return schema.GroupVersionKind{}, fmt.Errorf("no GroupVersionKind associated with Go type %T, was the type registered with the Scheme?", obj)
128 case len(gvks) > 1:
129 err := fmt.Errorf("multiple GroupVersionKinds associated with Go type %T within the Scheme, this can happen when a type is registered for multiple GVKs at the same time", obj)
130
131
132 currentGVK := obj.GetObjectKind().GroupVersionKind()
133 if !currentGVK.Empty() {
134
135 for _, gvk := range gvks {
136 if gvk == currentGVK {
137 return gvk, nil
138 }
139 }
140
141 return schema.GroupVersionKind{}, fmt.Errorf(
142 "%w: the object's supplied GroupVersionKind %q was not found in the Scheme's list; refusing to guess at one: %q", err, currentGVK, gvks)
143 }
144
145
146
147
148
149
150 return schema.GroupVersionKind{}, fmt.Errorf(
151 "%w: callers can either fix their type registration to only register it once, or specify the GroupVersionKind to use for object passed in; refusing to guess at one: %q", err, gvks)
152 default:
153
154 return gvks[0], nil
155 }
156 }
157
158
159
160
161 func RESTClientForGVK(gvk schema.GroupVersionKind, isUnstructured bool, baseConfig *rest.Config, codecs serializer.CodecFactory, httpClient *http.Client) (rest.Interface, error) {
162 if httpClient == nil {
163 return nil, fmt.Errorf("httpClient must not be nil, consider using rest.HTTPClientFor(c) to create a client")
164 }
165 return rest.RESTClientForConfigAndClient(createRestConfig(gvk, isUnstructured, baseConfig, codecs), httpClient)
166 }
167
168
169 func createRestConfig(gvk schema.GroupVersionKind, isUnstructured bool, baseConfig *rest.Config, codecs serializer.CodecFactory) *rest.Config {
170 gv := gvk.GroupVersion()
171
172 cfg := rest.CopyConfig(baseConfig)
173 cfg.GroupVersion = &gv
174 if gvk.Group == "" {
175 cfg.APIPath = "/api"
176 } else {
177 cfg.APIPath = "/apis"
178 }
179 if cfg.UserAgent == "" {
180 cfg.UserAgent = rest.DefaultKubernetesUserAgent()
181 }
182
183 if cfg.ContentType == "" && !isUnstructured {
184 protobufSchemeLock.RLock()
185 if protobufScheme.Recognizes(gvk) {
186 cfg.ContentType = runtime.ContentTypeProtobuf
187 }
188 protobufSchemeLock.RUnlock()
189 }
190
191 if isUnstructured {
192
193 cfg = dynamic.ConfigFor(cfg)
194 } else {
195 cfg.NegotiatedSerializer = serializerWithTargetZeroingDecode{NegotiatedSerializer: serializer.WithoutConversionCodecFactory{CodecFactory: codecs}}
196 }
197
198 return cfg
199 }
200
201 type serializerWithTargetZeroingDecode struct {
202 runtime.NegotiatedSerializer
203 }
204
205 func (s serializerWithTargetZeroingDecode) DecoderToVersion(serializer runtime.Decoder, r runtime.GroupVersioner) runtime.Decoder {
206 return targetZeroingDecoder{upstream: s.NegotiatedSerializer.DecoderToVersion(serializer, r)}
207 }
208
209 type targetZeroingDecoder struct {
210 upstream runtime.Decoder
211 }
212
213 func (t targetZeroingDecoder) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
214 zero(into)
215 return t.upstream.Decode(data, defaults, into)
216 }
217
218
219 func zero(x interface{}) {
220 if x == nil {
221 return
222 }
223 res := reflect.ValueOf(x).Elem()
224 res.Set(reflect.Zero(res.Type()))
225 }
226
View as plain text