1
16
17 package dynamic
18
19 import (
20 "context"
21 "fmt"
22 "net/http"
23
24 "k8s.io/apimachinery/pkg/api/meta"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
27 "k8s.io/apimachinery/pkg/runtime"
28 "k8s.io/apimachinery/pkg/runtime/schema"
29 "k8s.io/apimachinery/pkg/types"
30 "k8s.io/apimachinery/pkg/watch"
31 "k8s.io/client-go/rest"
32 )
33
34 type DynamicClient struct {
35 client rest.Interface
36 }
37
38 var _ Interface = &DynamicClient{}
39
40
41
42 func ConfigFor(inConfig *rest.Config) *rest.Config {
43 config := rest.CopyConfig(inConfig)
44 config.AcceptContentTypes = "application/json"
45 config.ContentType = "application/json"
46 config.NegotiatedSerializer = basicNegotiatedSerializer{}
47 if config.UserAgent == "" {
48 config.UserAgent = rest.DefaultKubernetesUserAgent()
49 }
50 return config
51 }
52
53
54 func New(c rest.Interface) *DynamicClient {
55 return &DynamicClient{client: c}
56 }
57
58
59
60 func NewForConfigOrDie(c *rest.Config) *DynamicClient {
61 ret, err := NewForConfig(c)
62 if err != nil {
63 panic(err)
64 }
65 return ret
66 }
67
68
69
70
71 func NewForConfig(inConfig *rest.Config) (*DynamicClient, error) {
72 config := ConfigFor(inConfig)
73
74 httpClient, err := rest.HTTPClientFor(config)
75 if err != nil {
76 return nil, err
77 }
78 return NewForConfigAndClient(config, httpClient)
79 }
80
81
82
83 func NewForConfigAndClient(inConfig *rest.Config, h *http.Client) (*DynamicClient, error) {
84 config := ConfigFor(inConfig)
85
86 config.GroupVersion = &schema.GroupVersion{}
87 config.APIPath = "/if-you-see-this-search-for-the-break"
88
89 restClient, err := rest.RESTClientForConfigAndClient(config, h)
90 if err != nil {
91 return nil, err
92 }
93 return &DynamicClient{client: restClient}, nil
94 }
95
96 type dynamicResourceClient struct {
97 client *DynamicClient
98 namespace string
99 resource schema.GroupVersionResource
100 }
101
102 func (c *DynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
103 return &dynamicResourceClient{client: c, resource: resource}
104 }
105
106 func (c *dynamicResourceClient) Namespace(ns string) ResourceInterface {
107 ret := *c
108 ret.namespace = ns
109 return &ret
110 }
111
112 func (c *dynamicResourceClient) Create(ctx context.Context, obj *unstructured.Unstructured, opts metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
113 outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
114 if err != nil {
115 return nil, err
116 }
117 name := ""
118 if len(subresources) > 0 {
119 accessor, err := meta.Accessor(obj)
120 if err != nil {
121 return nil, err
122 }
123 name = accessor.GetName()
124 if len(name) == 0 {
125 return nil, fmt.Errorf("name is required")
126 }
127 }
128 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
129 return nil, err
130 }
131
132 result := c.client.client.
133 Post().
134 AbsPath(append(c.makeURLSegments(name), subresources...)...).
135 SetHeader("Content-Type", runtime.ContentTypeJSON).
136 Body(outBytes).
137 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
138 Do(ctx)
139 if err := result.Error(); err != nil {
140 return nil, err
141 }
142
143 retBytes, err := result.Raw()
144 if err != nil {
145 return nil, err
146 }
147 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
148 if err != nil {
149 return nil, err
150 }
151 return uncastObj.(*unstructured.Unstructured), nil
152 }
153
154 func (c *dynamicResourceClient) Update(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) {
155 accessor, err := meta.Accessor(obj)
156 if err != nil {
157 return nil, err
158 }
159 name := accessor.GetName()
160 if len(name) == 0 {
161 return nil, fmt.Errorf("name is required")
162 }
163 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
164 return nil, err
165 }
166 outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
167 if err != nil {
168 return nil, err
169 }
170
171 result := c.client.client.
172 Put().
173 AbsPath(append(c.makeURLSegments(name), subresources...)...).
174 SetHeader("Content-Type", runtime.ContentTypeJSON).
175 Body(outBytes).
176 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
177 Do(ctx)
178 if err := result.Error(); err != nil {
179 return nil, err
180 }
181
182 retBytes, err := result.Raw()
183 if err != nil {
184 return nil, err
185 }
186 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
187 if err != nil {
188 return nil, err
189 }
190 return uncastObj.(*unstructured.Unstructured), nil
191 }
192
193 func (c *dynamicResourceClient) UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) {
194 accessor, err := meta.Accessor(obj)
195 if err != nil {
196 return nil, err
197 }
198 name := accessor.GetName()
199 if len(name) == 0 {
200 return nil, fmt.Errorf("name is required")
201 }
202 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
203 return nil, err
204 }
205 outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
206 if err != nil {
207 return nil, err
208 }
209
210 result := c.client.client.
211 Put().
212 AbsPath(append(c.makeURLSegments(name), "status")...).
213 SetHeader("Content-Type", runtime.ContentTypeJSON).
214 Body(outBytes).
215 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
216 Do(ctx)
217 if err := result.Error(); err != nil {
218 return nil, err
219 }
220
221 retBytes, err := result.Raw()
222 if err != nil {
223 return nil, err
224 }
225 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
226 if err != nil {
227 return nil, err
228 }
229 return uncastObj.(*unstructured.Unstructured), nil
230 }
231
232 func (c *dynamicResourceClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error {
233 if len(name) == 0 {
234 return fmt.Errorf("name is required")
235 }
236 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
237 return err
238 }
239 deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
240 if err != nil {
241 return err
242 }
243
244 result := c.client.client.
245 Delete().
246 AbsPath(append(c.makeURLSegments(name), subresources...)...).
247 SetHeader("Content-Type", runtime.ContentTypeJSON).
248 Body(deleteOptionsByte).
249 Do(ctx)
250 return result.Error()
251 }
252
253 func (c *dynamicResourceClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error {
254 if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
255 return err
256 }
257
258 deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
259 if err != nil {
260 return err
261 }
262
263 result := c.client.client.
264 Delete().
265 AbsPath(c.makeURLSegments("")...).
266 SetHeader("Content-Type", runtime.ContentTypeJSON).
267 Body(deleteOptionsByte).
268 SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).
269 Do(ctx)
270 return result.Error()
271 }
272
273 func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
274 if len(name) == 0 {
275 return nil, fmt.Errorf("name is required")
276 }
277 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
278 return nil, err
279 }
280 result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do(ctx)
281 if err := result.Error(); err != nil {
282 return nil, err
283 }
284 retBytes, err := result.Raw()
285 if err != nil {
286 return nil, err
287 }
288 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
289 if err != nil {
290 return nil, err
291 }
292 return uncastObj.(*unstructured.Unstructured), nil
293 }
294
295 func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
296 if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
297 return nil, err
298 }
299 result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do(ctx)
300 if err := result.Error(); err != nil {
301 return nil, err
302 }
303 retBytes, err := result.Raw()
304 if err != nil {
305 return nil, err
306 }
307 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
308 if err != nil {
309 return nil, err
310 }
311 if list, ok := uncastObj.(*unstructured.UnstructuredList); ok {
312 return list, nil
313 }
314
315 list, err := uncastObj.(*unstructured.Unstructured).ToList()
316 if err != nil {
317 return nil, err
318 }
319 return list, nil
320 }
321
322 func (c *dynamicResourceClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
323 opts.Watch = true
324 if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
325 return nil, err
326 }
327 return c.client.client.Get().AbsPath(c.makeURLSegments("")...).
328 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
329 Watch(ctx)
330 }
331
332 func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) {
333 if len(name) == 0 {
334 return nil, fmt.Errorf("name is required")
335 }
336 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
337 return nil, err
338 }
339 result := c.client.client.
340 Patch(pt).
341 AbsPath(append(c.makeURLSegments(name), subresources...)...).
342 Body(data).
343 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
344 Do(ctx)
345 if err := result.Error(); err != nil {
346 return nil, err
347 }
348 retBytes, err := result.Raw()
349 if err != nil {
350 return nil, err
351 }
352 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
353 if err != nil {
354 return nil, err
355 }
356 return uncastObj.(*unstructured.Unstructured), nil
357 }
358
359 func (c *dynamicResourceClient) Apply(ctx context.Context, name string, obj *unstructured.Unstructured, opts metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error) {
360 if len(name) == 0 {
361 return nil, fmt.Errorf("name is required")
362 }
363 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
364 return nil, err
365 }
366 outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
367 if err != nil {
368 return nil, err
369 }
370 accessor, err := meta.Accessor(obj)
371 if err != nil {
372 return nil, err
373 }
374 managedFields := accessor.GetManagedFields()
375 if len(managedFields) > 0 {
376 return nil, fmt.Errorf(`cannot apply an object with managed fields already set.
377 Use the client-go/applyconfigurations "UnstructructuredExtractor" to obtain the unstructured ApplyConfiguration for the given field manager that you can use/modify here to apply`)
378 }
379 patchOpts := opts.ToPatchOptions()
380
381 result := c.client.client.
382 Patch(types.ApplyPatchType).
383 AbsPath(append(c.makeURLSegments(name), subresources...)...).
384 Body(outBytes).
385 SpecificallyVersionedParams(&patchOpts, dynamicParameterCodec, versionV1).
386 Do(ctx)
387 if err := result.Error(); err != nil {
388 return nil, err
389 }
390 retBytes, err := result.Raw()
391 if err != nil {
392 return nil, err
393 }
394 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
395 if err != nil {
396 return nil, err
397 }
398 return uncastObj.(*unstructured.Unstructured), nil
399 }
400 func (c *dynamicResourceClient) ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, opts metav1.ApplyOptions) (*unstructured.Unstructured, error) {
401 return c.Apply(ctx, name, obj, opts, "status")
402 }
403
404 func validateNamespaceWithOptionalName(namespace string, name ...string) error {
405 if msgs := rest.IsValidPathSegmentName(namespace); len(msgs) != 0 {
406 return fmt.Errorf("invalid namespace %q: %v", namespace, msgs)
407 }
408 if len(name) > 1 {
409 panic("Invalid number of names")
410 } else if len(name) == 1 {
411 if msgs := rest.IsValidPathSegmentName(name[0]); len(msgs) != 0 {
412 return fmt.Errorf("invalid resource name %q: %v", name[0], msgs)
413 }
414 }
415 return nil
416 }
417
418 func (c *dynamicResourceClient) makeURLSegments(name string) []string {
419 url := []string{}
420 if len(c.resource.Group) == 0 {
421 url = append(url, "api")
422 } else {
423 url = append(url, "apis", c.resource.Group)
424 }
425 url = append(url, c.resource.Version)
426
427 if len(c.namespace) > 0 {
428 url = append(url, "namespaces", c.namespace)
429 }
430 url = append(url, c.resource.Resource)
431
432 if len(name) > 0 {
433 url = append(url, name)
434 }
435
436 return url
437 }
438
View as plain text