1
16
17 package metadata
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "net/http"
24 "time"
25
26 "k8s.io/klog/v2"
27
28 metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30 "k8s.io/apimachinery/pkg/runtime"
31 "k8s.io/apimachinery/pkg/runtime/schema"
32 "k8s.io/apimachinery/pkg/runtime/serializer"
33 "k8s.io/apimachinery/pkg/types"
34 "k8s.io/apimachinery/pkg/watch"
35 "k8s.io/client-go/rest"
36 )
37
38 var deleteScheme = runtime.NewScheme()
39 var parameterScheme = runtime.NewScheme()
40 var deleteOptionsCodec = serializer.NewCodecFactory(deleteScheme)
41 var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme)
42
43 var versionV1 = schema.GroupVersion{Version: "v1"}
44
45 func init() {
46 metav1.AddToGroupVersion(parameterScheme, versionV1)
47 metav1.AddToGroupVersion(deleteScheme, versionV1)
48 }
49
50
51
52
53
54
55
56 type Client struct {
57 client *rest.RESTClient
58 }
59
60 var _ Interface = &Client{}
61
62
63
64 func ConfigFor(inConfig *rest.Config) *rest.Config {
65 config := rest.CopyConfig(inConfig)
66 config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
67 config.ContentType = "application/vnd.kubernetes.protobuf"
68 config.NegotiatedSerializer = metainternalversionscheme.Codecs.WithoutConversion()
69 if config.UserAgent == "" {
70 config.UserAgent = rest.DefaultKubernetesUserAgent()
71 }
72 return config
73 }
74
75
76
77 func NewForConfigOrDie(c *rest.Config) Interface {
78 ret, err := NewForConfig(c)
79 if err != nil {
80 panic(err)
81 }
82 return ret
83 }
84
85
86
87
88
89
90
91 func NewForConfig(inConfig *rest.Config) (Interface, error) {
92 config := ConfigFor(inConfig)
93
94 httpClient, err := rest.HTTPClientFor(config)
95 if err != nil {
96 return nil, err
97 }
98 return NewForConfigAndClient(config, httpClient)
99 }
100
101
102
103 func NewForConfigAndClient(inConfig *rest.Config, h *http.Client) (Interface, error) {
104 config := ConfigFor(inConfig)
105
106 config.GroupVersion = &schema.GroupVersion{}
107 config.APIPath = "/this-value-should-never-be-sent"
108
109 restClient, err := rest.RESTClientForConfigAndClient(config, h)
110 if err != nil {
111 return nil, err
112 }
113
114 return &Client{client: restClient}, nil
115 }
116
117 type client struct {
118 client *Client
119 namespace string
120 resource schema.GroupVersionResource
121 }
122
123
124
125 func (c *Client) Resource(resource schema.GroupVersionResource) Getter {
126 return &client{client: c, resource: resource}
127 }
128
129
130
131 func (c *client) Namespace(ns string) ResourceInterface {
132 ret := *c
133 ret.namespace = ns
134 return &ret
135 }
136
137
138 func (c *client) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error {
139 if len(name) == 0 {
140 return fmt.Errorf("name is required")
141 }
142
143
144
145
146
147
148 deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
149 if err != nil {
150 return err
151 }
152
153 result := c.client.client.
154 Delete().
155 AbsPath(append(c.makeURLSegments(name), subresources...)...).
156 SetHeader("Content-Type", runtime.ContentTypeJSON).
157 Body(deleteOptionsByte).
158 Do(ctx)
159 return result.Error()
160 }
161
162
163 func (c *client) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error {
164
165 deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
166 if err != nil {
167 return err
168 }
169
170 result := c.client.client.
171 Delete().
172 AbsPath(c.makeURLSegments("")...).
173 SetHeader("Content-Type", runtime.ContentTypeJSON).
174 Body(deleteOptionsByte).
175 SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).
176 Do(ctx)
177 return result.Error()
178 }
179
180
181 func (c *client) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
182 if len(name) == 0 {
183 return nil, fmt.Errorf("name is required")
184 }
185 result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).
186 SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
187 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
188 Do(ctx)
189 if err := result.Error(); err != nil {
190 return nil, err
191 }
192 obj, err := result.Get()
193 if runtime.IsNotRegisteredError(err) {
194 klog.FromContext(ctx).V(5).Info("Could not retrieve PartialObjectMetadata", "err", err)
195 rawBytes, err := result.Raw()
196 if err != nil {
197 return nil, err
198 }
199 var partial metav1.PartialObjectMetadata
200 if err := json.Unmarshal(rawBytes, &partial); err != nil {
201 return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
202 }
203 if !isLikelyObjectMetadata(&partial) {
204 return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema: %#v", partial)
205 }
206 partial.TypeMeta = metav1.TypeMeta{}
207 return &partial, nil
208 }
209 if err != nil {
210 return nil, err
211 }
212 partial, ok := obj.(*metav1.PartialObjectMetadata)
213 if !ok {
214 return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
215 }
216 return partial, nil
217 }
218
219
220 func (c *client) List(ctx context.Context, opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) {
221 result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).
222 SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json").
223 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
224 Do(ctx)
225 if err := result.Error(); err != nil {
226 return nil, err
227 }
228 obj, err := result.Get()
229 if runtime.IsNotRegisteredError(err) {
230 klog.FromContext(ctx).V(5).Info("Could not retrieve PartialObjectMetadataList", "err", err)
231 rawBytes, err := result.Raw()
232 if err != nil {
233 return nil, err
234 }
235 var partial metav1.PartialObjectMetadataList
236 if err := json.Unmarshal(rawBytes, &partial); err != nil {
237 return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadataList: %v", err)
238 }
239 partial.TypeMeta = metav1.TypeMeta{}
240 return &partial, nil
241 }
242 if err != nil {
243 return nil, err
244 }
245 partial, ok := obj.(*metav1.PartialObjectMetadataList)
246 if !ok {
247 return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
248 }
249 return partial, nil
250 }
251
252
253 func (c *client) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
254 var timeout time.Duration
255 if opts.TimeoutSeconds != nil {
256 timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
257 }
258 opts.Watch = true
259 return c.client.client.Get().
260 AbsPath(c.makeURLSegments("")...).
261 SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
262 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
263 Timeout(timeout).
264 Watch(ctx)
265 }
266
267
268 func (c *client) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
269 if len(name) == 0 {
270 return nil, fmt.Errorf("name is required")
271 }
272 result := c.client.client.
273 Patch(pt).
274 AbsPath(append(c.makeURLSegments(name), subresources...)...).
275 Body(data).
276 SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
277 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
278 Do(ctx)
279 if err := result.Error(); err != nil {
280 return nil, err
281 }
282 obj, err := result.Get()
283 if runtime.IsNotRegisteredError(err) {
284 rawBytes, err := result.Raw()
285 if err != nil {
286 return nil, err
287 }
288 var partial metav1.PartialObjectMetadata
289 if err := json.Unmarshal(rawBytes, &partial); err != nil {
290 return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
291 }
292 if !isLikelyObjectMetadata(&partial) {
293 return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema")
294 }
295 partial.TypeMeta = metav1.TypeMeta{}
296 return &partial, nil
297 }
298 if err != nil {
299 return nil, err
300 }
301 partial, ok := obj.(*metav1.PartialObjectMetadata)
302 if !ok {
303 return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
304 }
305 return partial, nil
306 }
307
308 func (c *client) makeURLSegments(name string) []string {
309 url := []string{}
310 if len(c.resource.Group) == 0 {
311 url = append(url, "api")
312 } else {
313 url = append(url, "apis", c.resource.Group)
314 }
315 url = append(url, c.resource.Version)
316
317 if len(c.namespace) > 0 {
318 url = append(url, "namespaces", c.namespace)
319 }
320 url = append(url, c.resource.Resource)
321
322 if len(name) > 0 {
323 url = append(url, name)
324 }
325
326 return url
327 }
328
329 func isLikelyObjectMetadata(meta *metav1.PartialObjectMetadata) bool {
330 return len(meta.UID) > 0 || !meta.CreationTimestamp.IsZero() || len(meta.Name) > 0 || len(meta.GenerateName) > 0
331 }
332
View as plain text